I have a component that is rendering <select> with <option> elements. When any change occurs, I would like to change the state of the component to keep the value of the currently selected option. As far as I know I don't have any other alternative for keeping this value since the props in React JS have to be immutable.
The problem comes when I notify the parent for the change. I do this using a callback from handleChange to parent's handleChangefunction. So in the child element I actually call the handleChangefunction, set the new state and call the callback (parent's handleChange). But then in the parent function when I ask for the value of the state property I receive the older one (seems like the new one is still not set).
So any ideas?
I would suggest using a single data flow pattern (like Flux or Reflux) to structure your react apps and avoid that kind of mistake and complicated reverse data flows.
From what I understood of your question, without Flux, you could do something like this.
var React = require("react");
var ParentComponent = React.createClass({
handleChange: function(newOption){
console.log("option in child component changed to " + newOption);
},
render: function(){
return (
<div>
<ChildComponent handleChange={this.handleChange}/>
</div>
)
}
});
var ChildComponent = React.createClass({
getInitialState: function(){
return {
selectedOption: 0
};
},
handleChange: function(){
var option = this.refs.select.getDOMNode().value;
this.setState({ selectedOption: option});
// I'm passing the actual value as an argument,
// not this.state.selectedOption
// If you want to do that, do it in componentDidUpdate
// then the state will have been set
this.props.handleChange(option);
},
render: function(){
return (
<div>
<h4>My Select</h4>
{this.state.selectedOption}
<select ref="select"
onChange={this.handleChange}>
<option>1</option>
<option>2</option>
<option>3</option>
</select>
</div>
)
}
});
Edit
Added a couple of forgotten semi-colons. I'm coding too much Python these days.
Edit2
Changed the code. Your problem might be that if you call the parent's handleChange with the value from the state (this.state.selectedOption), the state won't be set yet so you have to give the actual value as an argument instead. If you really want to use this.state.selectedOption, call parent's handleChange in componentDidUpdate.
Related
I have the following hyperHTML component, everything is working as intended, the only issue is that the entire component DOM re renders on every this.setState() call.
My question:
Is there a way to update the notification string on the DOM without re rendering the entire component DOM ?
const { hyper } = hyperHTML;
class searchComponent extends hyper.Component {
constructor(data) {
super();
this.setState({ notification: '' });
}
onChange() {
// ...
if ( condition ) {
this.setState(() => ({ notification: 'notification text!' }));
}
// ...
}
async listTags() {
//...
// async content
//...
}
render() {
this.html `
<div id="search_container">
<select name="tags" id="tags_search" class='form-control m0' multiple="multiple" onChange=${this.onChange}>
${{
any: this.listTags(),
placeholder: 'incoming..!',
}}
</select>
<div class="search_notification">
<!-- I want to re render only this part of the DOM -->
${this.state.notification}
</div>
</div>
`
}
}
There are few gotchas in your code.
To start with, that onChange won't work as expected, you are losing the context as it is. You either just use onchange HTML event, and you call the method onchange and you use simply this as event listener:
const { hyper } = hyperHTML;
class SearchComponent extends hyper.Component {
onchange() {
this instaneof SearchComponent; // true
}
render() { return this.html`
<div id="search_container">
<select onchange=${this}>
${...}
</select>
</div>`
}
}
or you bind the onChange method within the constructor.
You also forgot to return this.html inside render(), which is something needed if you want to place your component on the DOM.
However, assuming these were irrelevant errors there for quick demo code sake, you need to understand that async world is async.
If you have an asynchronous operation there's no way you can tell if that resolved or not, you just pass it along, and this is exactly what you are doing.
You are changing the state of the component expecting that previously async operations would not be triggered again.
But how can the component know if such state change would produce a different layout? The TL;DR answer is that it cannot, the slightly longer one is that you have an asynchronous, unrelated, behavior, attached to each updates but you want that asynchronous behavior to be triggered only once, and not per update.
Possible solutions
You can either create a sub component a part, and instantiate it once during the SearchComponent initialization (constructor) or simply use a placeholder that makes more sense than the current one.
Indeed, right now you are creating output like <select>incoming..!</select> which makes no sense as standard HTML.
A select can have option tags, using it as a generic container of some text is like abusing its potentials.
What you want is something like a disabled=${this.optionsResolved} on the select, and ${this.options} as array of its content.
In this way you have no changes whatsoever while resolving your options, and a proper list of options once that happens.
You can see an example in this Code Pen
I want to update an array each time a checkbox is toggled to true. With this current code, if I click on a checkbox it will log that it's false. Even though I have just updated the state. Does setState just take some time, like an API call? That doesn't make sense to me.
import React, {Component} from 'react';
class Person extends Component {
constructor(props) {
super(props);
this.state = {
boxIsChecked: false
};
this.checkboxToggle = this.checkboxToggle.bind(this);
}
checkboxToggle() {
// state is updated first
this.setState({ boxIsChecked: !this.state.boxIsChecked });
console.log("boxIsChecked: " + this.state.boxIsChecked);
if (this.state.boxIsChecked === false) {
console.log("box is false. should do nothing.");
}
else if (this.state.boxIsChecked === true) {
console.log("box is true. should be added.");
this.props.setForDelete(this.props.person._id);
}
}
render() {
return (
<div>
<input type="checkbox" name="person" checked={this.state.boxIsChecked} onClick={this.checkboxToggle} />
{this.props.person.name} ({this.props.person.age})
</div>
);
}
}
export default Person;
I have tried onChange instead of onClick. I feel like I'm already following advice I've read about for the basic component formulation from here and here. Is the fact I'm using Redux for other values affecting anything? Is there a way to just read what the checkbox is, instead of trying to control it? (The checkbox itself works fine and the DOM updates wether it's checked or not correctly.)
I know that this thread is answered but i also have a solution for this, you see the checkbox didn't update with the provided value of setState, i don't know the exact reason for this problem but here is a solution.
<input
key={Math.random()}
type="checkbox"
name="insurance"
defaultChecked={data.insurance}
/>
by giving the key value of random the checkbox gets re-rendered and the value of the checkbox is updated, this worked for me. Also i am using hooks, but it should work for class based implementation to.
reference: https://www.reddit.com/r/reactjs/comments/8unyps/am_i_doing_stupid_or_is_this_a_bug_checkbox_not/
setState() is indeed not reflected right away:
Read here in the docs:
setState() enqueues changes to the component state and tells React
that this component and its children need to be re-rendered with the
updated state. This is the primary method you use to update the user
interface in response to event handlers and server responses.
Think of setState() as a request rather than an immediate command to
update the component. For better perceived performance, React may
delay it, and then update several components in a single pass. React
does not guarantee that the state changes are applied immediately.
setState() does not always immediately update the component. It may
batch or defer the update until later. This makes reading this.state
right after calling setState() a potential pitfall. Instead, use
componentDidUpdate or a setState callback (setState(updater,
callback)), either of which are guaranteed to fire after the update
has been applied. If you need to set the state based on the previous
state, read about the updater argument below.
setState() will always lead to a re-render unless
shouldComponentUpdate() returns false. If mutable objects are being
used and conditional rendering logic cannot be implemented in
shouldComponentUpdate(), calling setState() only when the new state
differs from the previous state will avoid unnecessary re-renders.
and here some experiments
So it might be better to catch the event and check it, and not depend on the this.setState()
Something like that:
handleChange: function (event) {
//you code
if (event.target.checked) {
console.log("box is true. should be added.");
this.props.setForDelete(this.props.person._id);
}
}
and
render() {
return (
<div>
<input type="checkbox" name="person" checked={this.state.boxIsChecked}
onChange={this.handleChange.bind(this)}/>
{this.props.person.name} ({this.props.person.age})
</div>
);
}
You can't access the updated value of state just after calling setState().
If you want to do stuff with updated value of state, you can do like this. i.e inside setState() callback function
checkboxToggle() {
// state is updated first
this.setState({ boxIsChecked: !this.state.boxIsChecked },()=>{
console.log("boxIsChecked: " + this.state.boxIsChecked);
if (this.state.boxIsChecked === false) {
console.log("box is false. should do nothing.");
}
else if (this.state.boxIsChecked === true) {
console.log("box is true. should be added.");
this.props.setForDelete(this.props.person._id);
}
});
React solution:
HTML:
<input type="checkbox" ref={checkboxRef} onChange={handleChange}/>
JS:
const checkboxRef = useRef("");
const [checkbox, setCheckbox] = useState(false);
const handleChange = () => {
setCheckbox(checkboxRef.current.checked);
};
Now your true/false value is stored in the checkbox variable
To expand no newbies answer; what worked for me:
remove preventDefault() from onChange Event-Listener but this didn't feel right for me since I want only react to update the value. And it might not apply in all cases like OPs.
include the checked value in the key like key={"agree_legal_checkbox" + legalcheckboxValue} checked={legalcheckboxValue}
I got both these solutions from newbies answer but having a math.random key just felt wrong. Using the value for checked causes the key only to change when when the checked value changes as well.
(not enough reputation to simply post this as a comment, but still wanted to share)
I arrived here trying to figure out why my checkbox, rendered using the HTM tagged template library, wasn't working.
return html`
...
<input type="checkbox" checked=${state.active ? "checked" : null} />
...
`;
It turns out I was just trying too hard with that ternary operator. This works fine:
return html`
...
<input type="checkbox" checked=${state.active} />
...
`;
I'm new to React and am trying to update the state of a parent component from the child everytime an onChange action happens. The onchange action comes from an input box that when letters are typed it updates the state of searchInputVal with the value of what has been typed. I have a parent <App/> component with the following properties and states here:
updateSampleFilteredState(filteredSamples) {
this.setState({
samples: filteredSamples
});
},
getInitialState () {
return {
samples:allSamples,
searchInputVal:""
}}
I pass the properties and states down to a child component here:
updateNewSampleState(filteredSamples){
return (
this.props.updateSampleFilteredState(filteredSamples)
)
}
render() {
const filteredSamples = this.props.samples.filter(sample => {
return sample.sampleFamily.toLowerCase().indexOf(this.props.searchInputVal.toLowerCase()) !== -1;
});
this.updateNewSampleState(filteredSamples);
return <div className="samples-container-inner-styling">
{
filteredSamples.map((sample) => {
return (...
Before I added the line this.updateNewSampleState(filteredSamples); the child component would render out the filtering just fine but obviously not update the state of sample with the new filtered state. When I the line this.updateNewSampleState(filteredSamples); to execute the function in the component to set the new state I get a list of re-occuring errors that eventually make my app crash. The errors say something about an anti pattern. I'm not sure how else to update the state?
You should't be updating the state from the render function, and you are facing the reason why that's a bad way to do things. Every time you call the setState the component re-renders, so if you call it inside the render function it will be called again and so on... You should ask yourself why are you calling that function there. I guess you could just do it in the onChange function you are using for the input.
As already mentioned by #César, setting the state in the renderer doesn't make sense, since setting the state triggers a rerender of the component, so you basically get something like an infinite render loop.
Given that you are computing filteredSamples only from the props, you could compute that state in the constructor:
The constructor is the right place to initialize state.
However, note the following when deriving state from props in the constructor:
It's okay to initialize state based on props if you know what you're doing. [...]
Beware of this pattern, as it effectively "forks" the props and can lead to bugs. Instead of syncing props to state, you often want to lift the state up.
If you "fork" props by using them for state, you might also want to implement componentWillReceiveProps(nextProps) to keep the state up-to-date with them. But lifting state up is often easier and less bug-prone.
I am trying to create an filter list which filter the list on typed input. But don't know why the if statement is not working.
Here is the function I wrote: `
filterList (event) {
var updatedList = this.props.array;
var filterText = this.state.text;
updatedList = updatedList.filter(function(item){
console.log(filterText);
if(item.name === filterText){
console.log('check', item.name);
}
});
Can someone help me out on this here is a link to my codepen: LINK
Your filter needs to update the array in the component state to force a re-render. You don't update props to force a re-render, props are more for initial data, or consistent data, which can be updated from the top level.
Change the handle function to pass the text to the filterList function, and return the result
handleChange(event) {
var array = this.filterList(event.target.value);
this.setState({ text: event.target.value, array: array });
},
filterList (filterText) {
var updatedList = this.props.array;
return updatedList.filter(function(item){
return item.name === filterText;
});
}
Update getInitialState to use the props:
getInitialState() {
return { text:'', array: this.props.array};
}
Then use the state instead of props in your render:
var arrayComponents = this.state.array.map(function(photo) {
return <li className="photo photo-name">{photo.name} <img className="photo" src={photo.link}/></li>;
})
First, when I run your codepen example, I'm not seeing any errors indicating that this.state.text is undefined. It's not filtering anything, but it is displaying the value of this.state.text. So the issue is not quite what your question suggests it is...
So how to we get this thing filtering? Basically, the idea with ReactJS components is that everything related to the current state of your component should be in this.state, and any decisions about what to display based on that state should be in the render method. And keep in mind that any time you change the state using the setState method, it's going to trigger a re-rendering of your component.
With that in mind, I'd set things up like this:
your component receives a list of photos via the array prop (I'd probably call it something else, since it's not very descriptive and it's awfully close to a reserved word in Javascript)
the state for the component has one value: text (again, not very descriptive)
the component has a handleChange handler for the input element. I would connect this to the onChange handler for the input element - don't worry, it will be called every time the value in the input element changes. This should be the only event handler on the input element and all it should do is call this.setState({ text: event.target.value });
do the list filtering in the render method. The key here is that you don't need to keep a filtered list of your photos - all you're doing with it is rendering it, so do it only when you need to (or rather, when React thinks you need to and calls the render method). So your render method will look something like this:
render() {
var myFilteredList = this.props.array.filter(function(item){
console.log(filterText);
if(item.name === filterText){
console.log('check', item.name);
return true;
}
return false;
});
var arrayComponents = myFilteredList.map(function(photo) {
return <li className="photo photo-name">{photo.name} <img className="photo" src={photo.link}/></li>;
});
return (
<div>
<h1>Hello, {this.props.name}</h1>
<p>{this.state.text}</p>
<input type="text" onKeyUp={this.handleChange} />
<ul>
{arrayComponents}
</ul>
</div>
);
}
That's it. This has a couple of advantages:
it keeps it simple - don't maintain any state for the component outside of this.state and if you only need it for rendering, don't maintain it at all.
it keeps your code cleaner - the way I've approached it, your component has only two methods: handleChange and render
it (should be) more performant - React is pretty good at deciding when to render based on state changes, and it tends to be better when components have minimal state. (I say "should be" simply because I haven't tested it out at all).
I'm trying to get my head around best practice regarding state in react components. I started creating a form by writing a TextField component as follows
var TextField = React.createClass({
render: function() {
const {value, title, placeholder} = this.props;
return (<div>
{title}
<input type="text"
value={value}
placeholder={placeholder}
onChange={this.handleChange} />
</div>);
},
handleChange (evt){
this.props.onChange(evt.target.value);
}
});
This is a controlled component. So the parent container has to pass a value in for the input in via props and change that value when there is a change. It seems like this is the usual approach.
My problem comes when I want to create a numeric field. For this example assume that my numeric field will allow non numeric characters to be input (the field just won't validate). I don't like the idea of having the validation of that field within the parent so this is what I wrote
var NumericField = React.createClass({
getInitialState: function(){
return{
value : ""
}
},
componentWillReceiveProps: function(nextProps) {
if(this.validate(nextProps.value)){
this.setState({value:nextProps.value});
}
},
validate : function(input) {
return !isNaN(input);
},
render: function() {
const {value} = this.state;
const {title} = this.props;
return (<div>
{title}
<input type="text"
value={value}
onChange={this.handleChange} />
</div>);
},
handleChange (evt){
this.setState({value:evt.target.value});
if(this.validate(evt.target.value)){
this.props.onChange(evt.target.value);
}
}
});
This allows the parent to set the value and update it via the "value" prop, but the "onChange" prop will only trigger when the content is valid. It feels to me like I've used a different pattern for each, and that's not good. Not sure if that's a valid feeling?
I suppose I just wanted to ask if my approach to the numeric field seems reasonable or if there is a better pattern to follow?
Just in case someone wants to know why I want a numeric field to work this way, I don't, it's just a simplified example. A more valid example would be a text area for json, that only called onChange when the content was valid json.
Appreciate any feedback
Setting state by passing in props is generally frowned upon.
Props should be immutable (like your NumericField component's title)
If you want to set an initial value it should come from the controller or store the parent component is getting it from, eg.
getInitialState() {
return({
value: FormDataStore.getInitialNumericFieldValue()
});
}
After that, any changes to the value should be handled by the NumericField component. If you need to validate do so before setting the new state, eg.
handleChange(evt) {
if (this.validate(evt.target.value)){
this.setState({
value: evt.target.value
});
/* You can also pass the new validated value
up to the parent component to hold on to
till you're ready to process the form*/
this.props.onChange(evt.target.value);
}
}
Your state will now only ever hold (and subsequently the parent component will only ever receive) the last validated value so you could even display valid/invalid message if this.state.value === input, but that's extra
Incidentally, your TextField component should also follow this pattern. Passing a changed value up to the parent just to have it passed down again as a prop defeats the purpose of having a child component. In that case I would have the JSX (and any validation process) all in the parent component instead of abstracting a child. Unless the child component could be re-used. But then I'd still let the child handle its own state.