Conditional rendering in hyper.Component not updating DOM - javascript

I wonder why following snippet is not updating DOM
const { hyper, wire } = hyperHTML;
class Input extends hyper.Component {
get defaultState() {
return { error: false };
}
onclick() {
this.setState(prev => ({ error: !prev.error }));
}
render() {
const textField = wire()
`
<div onconnected=${this} ondisconnected=${this}>
<input type="text" value="foo" onclick=${this}>
<label>bar</label>
</div>
`;
let finalNode;
if (this.state.error) {
finalNode = this.html `
<div>
${textField}
<p>
some name
</p>
</div>
`;
} else {
finalNode = this.html `
<div>
${textField}
</div>
`;
}
return finalNode;
}
}
document
.getElementById('root')
.appendChild(new Input().render());
I would expect it would it to render textField first and upon click to render p element along. I can see that render call is made but resulting element does not end up in DOM.

With hyper.Component the best way to go is to make the content conditional, not the root as a whole.
In order to do that, you can simply use a ternary, or an array.concat(...) or any other way that will update the component.
It's like the difference between updating an instance and replacing it.
From the inside, the component has no power to replace itself on its parent, unless you explicitly do so.
However, this example is easier than it looks like, and you can see it working on this Code Pen.
Moreover, if you don't specify onconnected and ondisconnected callbacks in the class, there's no need to specify them on the node.
const { hyper, wire } = hyperHTML;
class Input extends hyper.Component {
get defaultState() { return { error: false }; }
onclick() {
this.setState(prev => ({ error: !prev.error }));
}
render() { return this.html`
<div>
<div>
<input type="text" value="foo" onclick=${this}>
<label>bar</label>
</div>
${this.state.error ?
wire(this, ':extra')
`<p> some name </p>` :
null
}
</div>`;
}
}
document.body.appendChild(new Input().render());
I hope this helped.

Related

Unable to render html divs with React?

I'm trying to generate several divs based off an array - but I'm unable to. I click a button, which is supposed to return the divs via mapping but it's returning anything.
class History extends Component {
constructor(props) {
super(props);
this.state = {
info: ""
};
this.generateDivs = this.generateDivs.bind(this);
}
async getCurrentHistory(address) {
const info = await axios.get(`https://api3.tzscan.io/v2/bakings_history/${address}?number=10000`);
return info.data[2];
}
async getHistory() {
const info = await getCurrentHistory(
"tz1hAYfexyzPGG6RhZZMpDvAHifubsbb6kgn"
);
this.setState({ info });
}
generateDivs() {
const arr = this.state.info;
const listItems = arr.map((cycles) =>
<div class="box-1">
Cycle: {cycles.cycle}
Count: {cycles.count.count_all}
Rewards: {cycles.reward}
</div>
);
return (
<div class="flex-container">
{ listItems }
</div>
)
}
componentWillMount() {
this.getHistory();
}
render() {
return (
<div>
<button onClick={this.generateDivs}>make divs</button>
</div>
);
}
You are not actually rendering the the divs just by invoking the generateDivs function, the JSX it is returning is not being used anywhere.
To get it to work you could do something like -
render() {
return (
<div>
<button onClick={this.showDivs}>make divs</button>
{this.state.isDisplayed && this.generateDivs()}
</div>
);
}
where showDivs would be a function to toggle the state property isDisplayed to true
The main point is that the JSX being returned in the generateDivs function will now be rendered out in the render function. There is many ways to toggle the display, that is just one straight forward way

How to make HTML elements from string, then select an element in React?

What I want to do is to fetch a website, then convert it into HTML, but not put the entire HTML into the React DOM, first select an element (and obviously it's children), then put this specific element into the React DOM.
What I want is something like this:
convertHtml = () => {
fetch(url)
.then(res => res.text())
.then(htmlString => {
/*for example the htmlString looks like this:
"<html>
<body>
<div>
<p id="elementWhatIWant">
I want to earn this element (not the text inside)
</p>
</div>
</body>
</html>"
*/
//what I want:
return stringToHtml(htmlString).getElementById("elementWhatIWant")
})
}
render = () => <div className="App">
{this.convertHtml()}
</div>
I did some research, but as I saw, there's only one way to get an element in React, and it's the ref. Because it's a string, we can't put ref on any element directly. The thing what I thought is that we can parse the string, and
search the elementWhatIWant element, and put the ref on it as a string, or
search the elementWhatIWant element, search it's closing tag, and return just this part of the string
Neither of them seems like a good approach, that's why I'm asking: What is the best way to earn what I want?
As previously mentioned, if these are simple HTML elements, you should consider using react-html-parser. It will solve your issue, in a much safer way, as opposed to dangerouslySetInnerHTML below. See my codesandbox here for more info:
class App extends React.Component {
constructor() {
super();
this.state = {
html: null
};
this.fetchHtml = this.fetchHtml.bind(this);
}
async fetchHtml() {
const html = await ajaxPlaceholder();
console.log("Got new html!", html);
const parsed = $(html).find("span");
// From https://stackoverflow.com/a/5744268/4875631
this.setState({
html: parsed[0].outerHTML
});
}
render() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
Escaped: <div>{this.state.html}</div>
<br />
Not Dangerous: <div>{parser(this.state.html)}</div>
<button onClick={this.fetchHtml}>Fetch some Html</button>
</div>
);
}
}
I've created a codesandbox here which demonstrates the dangerouslySetInnerHTML functionality. As mentioned, you want to use dangerouslySetInnerHTML={{ __html: HTML_YOU_WANT_TO_SET }} to set html. Please use it with caution:
dangerouslySetInnerHTML is React’s replacement for using innerHTML in the browser DOM. In general, setting HTML from code is risky because it’s easy to inadvertently expose your users to a cross-site scripting (XSS) attack. So, you can set HTML directly from React, but you have to type out dangerouslySetInnerHTML and pass an object with a __html key, to remind yourself that it’s dangerous.
const ajaxPlaceholder = () => {
return new Promise(resolve => {
setTimeout(() => resolve("<h1>Test Return</h1>"), 500);
});
};
class App extends React.Component {
constructor() {
super();
this.state = {
html: null
};
this.fetchHtml = this.fetchHtml.bind(this);
}
async fetchHtml() {
const html = await ajaxPlaceholder();
console.log("Got new html!", html);
this.setState({
html
});
}
render() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
Escaped: <div>{this.state.html}</div>
<br />
Dangerous: <div dangerouslySetInnerHTML={{ __html: this.state.html }} />
<button onClick={this.fetchHtml}>Fetch some Html</button>
</div>
);
}
}

Show element based on input being focused

I'm trying to make a piece of code a little more efficient. The concept is that when you focus the textarea, you are shown a paragraph tag that tells you the characters remaining. Currently i have:
import React, { Component } from "react";
class Idea extends Component {
state = {
showCharactersRemaining: false
};
calculateRemaining() {
return 15 - this.props.body.length;
}
onTextAreaFocus = () => {
this.setState(state => {
return { showCharactersRemaining: true };
});
};
onBlur = () => {
this.setState(state => {
return { showCharactersRemaining: false };
});
};
render() {
const { title, body } = this.props;
const { showCharactersRemaining } = this.state;
return (
<div className="idea">
<input type="text" name="title" value={title} />
<textarea
rows="3"
name="body"
maxlength="15"
value={body}
onFocus={this.onTextAreaFocus}
onBlur={this.onBlur}
/>
{showCharactersRemaining && (
<p>{this.calculateRemaining()} characters remaining.</p>
)}
</div>
);
}
}
export default Idea;
Which works but also relies on having two methods attached to said textarea to make it work. Is there a smarter way in react of doing this?
CSS can handle it for you, removing the necessity for state and either event handler. Run the snippet to see it work (I removed the logic for counting characters to keep this example simple)
.charcount {
display: none;
}
textarea:focus+.charcount {
display: block;
}
<div className="idea">
<textarea rows="3" name="body" maxlength="15" value="foo"></textarea>
<p class="charcount">XX characters remaining.</p>
</div>

rendering multiple elements after onClick event in React

I'm having problems trying to render two react elements inside a react component after a onClick event. Wondering if that's even possible? I'm sure I'm messing up the ternary operator, but I cannot think on another way to do what I'm trying to do ?
TL;DR: "When I click a button I see elementA and elementB"
Here is a snippet of the code:
import React, { Component } from 'react';
class MyComponent extends Component {
constructor(props) {
super(props)
this.state = { showElement: true };
this.onHandleClick = this.onHandleClick.bind(this);
}
onHandleClick() {
console.log(`current state: ${this.state.showElement} and prevState: ${this.prevState}`);
this.setState(prevState => ({ showElement: !this.state.showElement }) );
};
elementA() {
<div>
<h1>
some data
</h1>
</div>
}
elementB() {
<div>
<h1>
some data
</h1>
</div>
}
render() {
return (
<section>
<button onClick={ this.onHandleClick } showElement={this.state.showElement === true}>
</button>
{ this.state.showElement
?
null
:
this.elementA() && this.elementB()
}
</section>
)
}
}
export default MyComponent;
You just inattentive.
elementA() {
return ( // You forget
<div>
<h1>
some data
</h1>
</div>
)
}
And the same in element B.
And if You want to see both components you should change Your ternary to
{ this.state.showElement
?
<div> {this.elementA()} {this.elementB()}</div>
:
null
}
Another "and", for toggling showElement in state just enough
this.setState({showElement: !this.state.showElement });
Try this instead, (I will add comments into the code trying to explain what's going on):
function SomeComponentName() { // use props if you want to pass some data to this component. Meaning that if you can keep it stateless do so.
return (
<div>
<h1>
some data
</h1>
</div>
);
}
class MyComponent extends Component {
constructor(props) {
super(props)
this.state = { showElement: false }; // you say that initially you don't want to show it, right? So let's set it to false :)
this.onHandleClick = this.onHandleClick.bind(this);
}
onHandleClick() {
this.setState(prevState => ({ showElement: !prevState.showElement }) );
// As I pointed out in the comment: when using the "reducer" version of `setState` you should use the parameter that's provided to you with the previous state, try never using the word `this` inside a "reducer" `setState` function
};
render() {
return (
<section>
<button onClick={ this.onHandleClick } showElement={this.state.showElement === false}>
</button>
{ this.state.showElement
? [<SomeComponentName key="firstOne" />, <SomeComponentName key="secondOne" />]
: null
}
</section>
)
}
}
export default MyComponent;

ReactJS clearing an input from parent component

I'm teaching myself react with a super simple app that asks the user to type a word presented in the UI. If user enters it correctly, the app shows another word, and so on.
I've got it almost working, except for one thing: after a word is entered correctly, I need to clear the input element. I've seen several answers here about how an input element can clear itself, but I need to clear it from the component that contains it, because that's where the input is checked...
// the app
class AppComponent extends React.Component {
constructor() {
super();
this.state = {
words: ['alpha', 'bravo', 'charlie'],
index: 0
};
}
renderWordsource() {
const word = this.state.words[this.state.index];
return <WordsourceComponent value={ word } />;
}
renderWordinput() {
return <WordinputComponent id={1} onChange={ this.onChange.bind(this) }/>;
}
onChange(id, value) {
const word = this.state.words[this.state.index];
if (word == value) {
alert('yes');
var nextIndex = (this.state.index == this.state.words.count-1)? 0 : this.state.index+1;
this.setState({ words:this.state.words, index:nextIndex });
}
}
render() {
return (
<div className="index">
<div>{this.renderWordsource()}</div>
<div>{this.renderWordinput()}</div>
</div>
);
}
}
// the input component
class WordinputComponent extends React.Component {
constructor(props) {
this.state = { text:''}
}
handleChange(event) {
var text = event.target.value;
this.props.onChange(this.props.id, text);
}
render() {
return (
<div className="wordinput-component">
<input type="text" onChange={this.handleChange.bind(this)} />
</div>
);
}
}
See where it says alert('yes')? That's where I think I should clear the value, but that doesn't make any sense because it's a parameter, not really the state of the component. Should I have the component pass itself to the change function? Maybe then I could alter it's state, but that sounds like a bad idea design-wise.
The 2 common ways of doing this is controlling the value through state in the parent or using a ref to clear the value. Added examples of both
The first one is using a ref and putting a function in the child component to clear
The second one is using state of the parent component and a controlled input field to clear it
class ParentComponent1 extends React.Component {
state = {
input2Value: ''
}
clearInput1() {
this.input1.clear();
}
clearInput2() {
this.setState({
input2Value: ''
});
}
handleInput2Change(evt) {
this.setState({
input2Value: evt.target.value
});
}
render() {
return (
<div>
<ChildComponent1 ref={input1 => this.input1 = input1}/>
<button onClick={this.clearInput1.bind(this)}>Clear</button>
<ChildComponent2 value={this.state.input2Value} onChange={this.handleInput2Change.bind(this)}/>
<button onClick={this.clearInput2.bind(this)}>Clear</button>
</div>
);
}
}
class ChildComponent1 extends React.Component {
clear() {
this.input.value = '';
}
render() {
return (
<input ref={input => this.input = input} />
);
}
}
class ChildComponent2 extends React.Component {
render() {
return (
<input value={this.props.value} onChange={this.props.onChange} />
);
}
}
ReactDOM.render(<ParentComponent1 />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
I had a similar issue: I wanted to clear a form which contained multiple fields.
While the two solutions by #noveyak are working fine, I want to share a different idea, which gives me the ability to partition the responsibility between parent and child: parent knows when to clear the form, and the items know how to react to that, without using refs.
The idea is to use a revision counter which gets incremented each time Clear is pressed and to react to changes of this counter in children.
In the example below there are three quite simple children reacting to the Clear button.
class ParentComponent extends React.Component {
state = {revision: 0}
clearInput = () => {
this.setState((prev) => ({revision: prev.revision+1}))
}
render() {
return (
<div>
<ChildComponent revision={this.state.revision}/>
<ChildComponent revision={this.state.revision}/>
<ChildComponent revision={this.state.revision}/>
<button onClick={this.clearInput.bind(this)}>Clear</button>
</div>
);
}
}
class ChildComponent extends React.Component {
state = {value: ''}
componentWillReceiveProps(nextProps){
if(this.props.revision != nextProps.revision){
this.setState({value : ''});
}
}
saveValue = (event) => {
this.setState({value: event.target.value})
}
render() {
return (
<input value={this.state.value} onChange={this.saveValue} />
);
}
}
ReactDOM.render(<ParentComponent />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
EDIT:
I've just stumbled upon this beautifully simple solution with key which is somewhat similar in spirit (you can pass parents's revision as child's key)
Very very very simple solution to clear form is add unique key in div under which you want to render form from your child component key={new Date().getTime()}:
render(){
return(
<div className="form_first_step fields_black" key={new Date().getTime()}>
<Form
className="first_step">
// form fields coming from child component
<AddressInfo />
</div>
</Form>
</div>
)
}

Categories