I'm using Redux as an initial state in a component used to edit the name of a current selected item (containing these properties : "name", and "selected"). It's used as a local initial state, because I want to update the Redux global state only when I click on a save button (that I didn't implemented yet).
constructor(props) {
super(props)
this.state = {
item: this.props.item
}
}
My render function contains an editing name input :
<TextField
id={"name"}
value={this.state.item.name}
onChange={e => this.updateName(e.target.value)}
/>
The updateName() function is :
updateName = (value) => {
var newItem = this.state.item
newItem.name = value
this.setState({item: newItem})
}
Here, I can selected an item, the default's value of the input changes following the name of the item I selected (when I select an item, the state of Redux is updated, so is the state of the component and the text in the input). Everything is working as intented.
When I write into the input, the written text changes, but the action of selecting an item do not update the text of the input anymore (it should follow the name of the selected item from Redux, like before I entered text).
Is it because the reference to the this.props.item (and afterward the updates of Redux) is lost as soon as I update the component state with this.setState() ? But how can I correct this problem ?
Keep in mind that the constructor is called only once, while your input is a controlled component, commanded by your state.
What you need to do is to update your component state based on redux state so, to this in the componentWillReceiveProps method.
Please note: updating the state using a prop is considered an anti-pattern.
Related
I have a project where I'm displaying cards that contain attributes of a person in a textfield, and the user can edit the textfield to directly change that person's attribute values. However every time they're editing the textfield it causes a rerender of all cards which slows down the app. Here is an example:
export default Parent() {
const [personList, setPersonList] = useState(/* list of person objects*/);
const modifyPerson(index, property, value) {
const newPersonList = _.cloneDeep(personList);
newPersonList[index][property] = value;
setPersonList(newPersonList);
}
const children = personList.map((person, index) => {
<Person
modifyPerson={modifyPerson}
index=index
/*properties on the person */
/>
});
return <div> {children} </div>
}
export default Person(props) {
const fields = /* assume a list of these textfields for each property */
<TextField
value={props.name}
onChange={(e) => modifyPerson(props.index,"name",e.target.value)}
value={props.name} >
return {fields};
}
So essentially when the child's text field is updated, it triggers a state change in the parent that stores the new value, then refreshes what the Child looks like. There's no button that the user clicks to "save" the values-- as soon as they edit the textfield it's a permanent change. And also the parent needs to know the new values of every Person because there's some functions that require knowledge of the current state of the person list. The Person component contains images that slow down the rendering if done inefficiently.
Is there a better way to make this design more performant and reduce rerenders? I attempted to use useCallback to preserve the functions but I don't it works in this specific design because the property and values are different-- do I have to create a new "modifyPerson" for each exact attribute?
Use React.memo()
React.Memo will check the props passed to the component and only if the props changes , it will re-render that particular component.
So, if you have multiple Person component which are getting props which are explicit to those Person, and when you change a particular Person component which leads to Parent state getting updated, only that Person component which you had modified will re-render because its props will change(assuming that you pass the changed value to it).
The other components will have the same props and wont change.
export default React.memo(Person(props) {
const fields = /* assume a list of these textfields for each property */
<TextField
value={props.name}
onChange={(e) => modifyPerson(props.index,"name",e.target.value)}
value={props.name} >
return {fields};
})
As others have already said React.memo() is the way to go here, but this will still re-render because you recreate modifyPerson every time. You can use useCallback to always get the same identity of the function, but with your current implementation you would have to add personList as dependency so that doesn't work either.
There is a trick with setPersonList that it also accepts a function that takes the current state and returns the next state. That way modifyPerson doesn't depend on any outer scope (expect for setPersonList which is guaranteed to always have the same identity by react) and needs to be created only once.
const modifyPerson = useCallback((index, property, value) {
setPersonList(currentPersonList => {
const newPersonList = _.cloneDeep(currentPersonList);
newPersonList[index][property] = value;
return newPersonList;
})
}, []);
I have a parent Component A that computes some initial value for his child Component B. Actually i compute the initial value in componentDidMount() of Component A and pass it to B through props:
<ComponentB initialValue={this.state.value} handleChange={this.handleChange}/>
And in component B i do
this.state = {value: this.props.initialValue}
After this, Component B should manage his state independently. But every time it's value change, i want it to update A's state but with less information.
In Component B:
const onChange = (event) => {
this.setState({value: event.target.value + "only in B"});
this.props.handleChange(this.state.value);
}
In ComponentA:
const handleChange = (value) => {
this.setState({value: value});
}
The problem is that A always has the last word and value in B doesn't keep the "only in b" suffix. A reset the value of B's state to his internal state value through the initialValue props when it renders again.
So basically, what i want to do is to inherit the initial value from A at first render, and then update A state when a change occurs in B without erasing B's state.
Hey even though you may do this better way if I understood correctly I think you can achieve what you want by just using:
this.props.value
on B Component:
<input defaultValue={this.props.value} onChange={this.onChange}/>
and then when you handle update just pass in event.target.value to parent (A component)
I'm not 100% sure if that's what you are asking but here's example:
https://codepen.io/pegla/pen/qBNxqBd?editors=1111
Any change in the parent component will trigger a re-render of the child component. I believe the best approach for your situation is after inheriting the initial value from A, set some local state variable to the B component to manage that state specifically so it's separate from A.
I have action and middleware, where I make a fetch request.
I get a data in mapStateToProps.
But I want use this data in my state and change them in setState.
How I can add mapStateToProps into this.state?
I want filter my data on client side.
If I dispatch my action I was make request to my server, it does not have to be done, because we have all list in our store.
You can achieve this by updating component's state once a redux store is updated (say, dispatched fetch success action) and component receives new values.
However, this is not idiomatic in most cases, since now you have to deal with possible desync of local component state and external redux state (which can be updated from outside of our component AND these new values will be passed down to our component).
Of course, you can ignore those changes and do nothing on props change.
Or reverse - update the state of component every time new props arrive.
Here is an example of syncing props and state:
// get `value` from store and pass it as prop to our component
const mapStateToProps = state => ({ value: state.someKey.value })
class Component extends React.Component {
// new props has been passed
// this method receives new props and current state
// returns next state values, or null if no changes required
static getDerivedStateFromProps(props, state) {
// some complex comparison logic goes here
// if true, then desync happened, let's update local state
if (state.value !== props.value) {
// this value will be at `this.state.value`
return { value }
}
// else, no update needed for our state
return null
}
// initial values taken from props
state = { value: this.props.value }
// update local state
onChange = e => this.setState({ value: e.target.value })
// let's render props value as label and state values as input value
render() {
return (
<label>
Props value is {this.props.value}
<input value={this.state.value} onChange={this.onChange} />
</label>
)
}
}
Note, that getDerivedStateFromProps is a relatively new lifecycle method (similar to "old" componentWillReceiveProps) and this use case is mentioned in official docs as a possible sign of bad design.
TL;DR duplicating state is bad, especially if we have to sync it manually.
I'm reading the Fullstack React book, and in their example on form validation, they create their own Field component (pg. 204 - 212), and then store the field value in both the Field state and parent state, which is confusing to me. Their Field component has a value prop as well as a state containing value. The parent component needs to know about each field value, so that it can do form validation as a whole, and so it also has a state containing value.
Within Field, they handle value changes by both setting the Field state when the input value changes, and by using getDerivedStateFromProps when the value prop changes:
//(within Field)
getDerivedStateFromProps(nextProps) {
return {value: nextProps.value}
}
onChange = evt => {
const name = this.props.name;
const value = evt.target.value;
const error = this.props.validate ? this.props.validate(value) : false;
this.setState({value, error});
this.props.onChange({name, value, error});
};
They also sync the value state in the other direction to the parent by calling the parent's onInputChange function (passed as the onChange prop):
//(within parent component)
onInputChange = ({name, value, error}) => {
const fields = Object.assign({}, this.state.fields);
const fieldErrors = Object.assign({}, this.state.fieldErrors);
fields[name] = value;
fieldErrors[name] = error;
this.setState({fields, fieldErrors});
};
The book doesn't really explain why they duplicate the state like this, except to say,
"There are only two pieces of data that Field will need, the current
value and error. Like in previous sections where our form component
needed that data for its render() method, so too does our Field
component."
and also
"One key difference is that our Field has a parent, and sometimes this
parent will want to update the value prop of our Field. To allow this,
we’ll need to create a new lifecycle method,
getDerivedStateFromProps() to accept the new value and update the
state."
I'm just a beginner, but in my mind, it would make more sense to ditch the value state altogether within Field, and have it just passed in as a prop. When the input changes, call the onChange method with Field, and call parent's onInputChange within that. Have onInputChange update the parent state about the field's value, and pass down the field value as a prop to the field. The way it's done now seems sort of redundant and more error prone. Any insight as to why they do it this way?
Haven't read the book, but here I will explain why I would write such a code.
The main point in having the two states is to make the Field component more generic.
In that specific case, the parent happens to also save the value in his state, and the Field component becomes a controlled component by updating his state from the received props on getDerivedStateFromProps.
However there is still the possibility to use the Field component as an uncontrolled component, then the Field's state would be the only source of truth.
In both cases there's only a single source of truth, which maintains React's way of doing things, however the Field component can be used in both a controlled and uncontrolled form.
I have a textbox in my Meteor + React application. I want to sync its value to a Mongo collection. However, I don't want to update the collection after every keystroke, only when the user has stopped typing for a few seconds.
The textbox in my render() function looks like this:
<input type="text" ref="answer" onChange={this.onChange} value={this.state.someValue} />
I store the textbox value in this.state instead of this.data because this.data reflects the Mongo collection, which might have not been updated yet.
So far, all of this works.
The problem:
If another client updates the collection, I want the textbox to show the updated value. For this I have to update this.state inside the getMeteorData() function, but that's disallowed, and I get an error: "Calling setState inside getMeteorData can result in infinite loop".
Right now I have a workaround where I manually update the textbox value in componentDidMount() and getMeteorData(), but it feels hackish and I don't like it at all.
Is there a better way to do this? Can I maybe force state updates inside getMeteorData() if I promise I'll be a good boy and behave nicely?
I would get rid of getMeteorData at all and turn to createContainer. Data flow gets clear and simple most of the time, including this specific case. Here it goes.
First thing first, create a container to fetch data.
export default theContainer = createContainer(() => {
// Subscribe to the publication which publishes the data.
const subscription = Meteor.subscribe(...);
// Derive the data for the input box and form the props to pass down.
const props = {
answer: getAnswer(subscription)
};
return props;
}, theComponent);
theContainer acts as a container component and transferes the contained data to the presentational component theComponent by props. Be noted that the function given to createContainer is responsive, meaning that changes to reactive data sources in that function trigger rerun and result in rerender of theComponent.
By now we are all armed. Since data in the Mongo collection (Minimongo exactly) is synced by the props passed down, theComponent is aware of the synchronization by a prop transition.
export default class theComponent extends React.Component {
...
componentWillReceiveProps(nextProps) {
if (this.props.answer !== nextProps.answer) {
this.setState({
answer: nextProps.answer
});
}
}
render() {
return <input value={this.state.answer} onChange={this.onChange} />;
}
}
While such transition occurs, the upcoming value is updated to the state, and this controlled component will render the input based on the updated new value.
On the other hand, while the user starts typing, the change handler this.onChange updates the user's input to the state with every key stoke for this is a controlled component. However, the handler updates the Mongo collection (again, Minimongo exactly) only when the preset duration has elapsed to save data transmission.