I know mutating state can work against PureComponent (or similar)
Is there other reason not to mutate state?
I wonder if the 3rd way is ok to do?
// The Right Way:
// copy the existing items and add a new one
addItemImmutably = () => {
this.setState({
items: [...this.state.items, this.makeItem()]
});
};
// The Wrong Way:
// mutate items and set it back
addItemMutably = () => {
this.state.items.push(this.makeItem());
this.setState({ items: this.state.items });
};
// is this ok? (mutate but set state with new copy)
addItem3rdWay = () => {
this.state.items.push(this.makeItem());
this.setState({items: [...this.state.items]});
}
Here is an example: You have fired an async method that sends a request with your current state data. In meantime you have executed a function that mutates the state. This will cause the async function to send the mutated state despite the fact it intended to send the state before mutation.
You can think state as your database.
You don't directly mutate your database, you modify your database by API.
setState is the API.
Of course, you can directly mutate your database, but other components will have a hard time retrieving those data, because those are inconsistent right now, because somebody doesn't use the API that framework provides for you.
If you really like the way mutating state, you can use Vue, Vue is designed like that.
Related
I have a situation that an item outside the component might influence the item in the backend. ex: change the value of one of the properties that are persisted, let's say the item status moves from Pending to Completed.
I know when it happens but since it is outside of a component I need to tell to the component that it is out of sync and re-fetch the data. But from outside. I know you can pass props calling the render method again. But the problem is I have a reducer and the state will pick up the last state and if I use an prop to trigger an effect I get into a loop.
Here is what I did:
useEffect(() => {
if (props.effect && !state.effect) { //this runs when the prop changes
return dispatch({ type: props.effect, });
}
if (state.effect) { // but then I get here and then back up and so on
return ModelEffect[state.effect](state?.data?.item)}, [state.status, state.effect, props.effect,]);
In short since I can't get rid of the prop the I get the first one then the second and so on in an infinite loop.
I render the root component passing the id:
render(html`<${Panel} id=${id}/>`,
document.getElementById('Panel'));
Then the idea was that I could do this to sync it:
render(html`<${Panel} id=${id} effect="RELOAD"/>`,
document.getElementById('Panel'));
any better ways to solve this?
Thanks a lot
I resolved it by passing the initialized dispatch function to a global.
function Panel (props) {
//...
const [state, dispatch,] = useReducer(RequestReducer, initialData);
//...
useEffect(() => {
//...
window.GlobalDispatch = dispatch;
//...
}, [state.status, state.effect,]);
with that I can do:
window.GlobalDispatch({type:'RELOAD'});
what i want to do is dispatch an action in my set interval function and not in get initial props and save my data in store and how to get that data back from store in react app it was simple just import action form action file and call like this this.props.actionName() but how do i do this in next and to get data from store we map state to props how can it be done in next thanks here my function which i want to implement in
this.fetchCryptoData().then(data => {
var Keys = Object.keys(data.DISPLAY);
this.setState(
{
crypto_head_coins: Keys
},
() => {
// // this.props.update_array([]); // update_array() is my action i haven't imported it
let rate_updated = [true, true, true, true]; // i want my store updated_array data here
for (let i = 0; i < this.state.crypto_head_coins.length; i++) {
//my code here
// this.props.store.dispatch(update_rate_array(rate_updated)) //it says cant read property
// of dispatch of undefined
// i want to dispatch my action here not in getinitialprops
this.setState({ rate_updated });
}
);
});
I use NextJS sometimes, It is the same as a Create-React-App essentially.
I just noticed your question does not include 'React-Redux', You will need to install/save 'React-Redux' and 'Redux' to use connect/dispatch, etc. I have a sample boilerplate on Github.
Another missing piece for converting this into an action.. is perhaps redux-thunk, to handle promises.(Try without it first.)
More information on redux-thunk here.
https://github.com/reduxjs/redux-thunk
You are setting state twice(once in the callback of another), which is going to cause multiple re-renders. (Unless ShouldComponentUpdate is implemented) Might want to re-consider this design.
Implement your MapDispatch to Props
After doing so you can simplify the line calling it, like the below using destructing.
// this.props.store.dispatch(update_rate_array(rate_updated)) //it says cant read property
let update_rate_array = {this.props}
update_rate_array(rate_updated)
You should implement your MapDispatchToProps removing some complexity in the naming and calling.
I have uploaded some simple examples to Github, and there is also an identical related CodeSandbox.
To receive your updated information from State, use MapStateToProps.
Example here.
I have a React component that maintains state for several child components. Via componentDidMount() I am calling this function in the parent component from the child components:
change = (fieldset, field, data) => {
this.setState({
[fieldset]: {
...this.state[fieldset],
[field]: data,
}
})
}
Think form/fieldset/field for the usage pattern, but with fields calling the above function.
The problem I'm having is that I believe I'm confusing React by calling this function so many times in quick succession, because state is not updated for all but one or two items.
I've tried using Object.assign() to avoid mutating state, but for the most part state has not updated correctly even at the point where I begin to read current start.
Is this against React best practices? Is there a better way for child components to call setState in a parent component?
Since the way you update the state depends on the state itself you need to use a function instead of an object.
change = (fieldset, field, data) => {
this.setState(prevState => ({
[fieldset]: {
...prevState[fieldset],
[field]: data,
}
}))
}
Functions will be applied one after another. So you wont override any pending changes.
From docs
this.setState({quantity: this.state.quantity + 1})
this.setState({quantity: this.state.quantity + 1})
Subsequent calls will override values from previous calls in the same
cycle, so the quantity will only be incremented once. If the next
state depends on the previous state, we recommend using the updater
function form, instead.
I have App component, which loads JSON data from the server.
And after data is loaded, I update state of child component.
Now my function looks like this:
componentDidMount() {
setTimeout(()=> {
if (this.state.users.length !== this.props.users.length) {
this.setState({users: this.props.users});
this.setState({tasks: this.getTasksArray()});
}, 500);
}
I use setTimeout to wait if data is loaded and sent to child. But I'm sure, it is not the best way
May be, it's better to use redux instead of setTimeout.
Parent component loads data:
componentWillMount() {
var _url = "/api/getusers/";
fetch(_url)
.then((response) => response.json())
.then(users => {
this.setState({ users });
console.log("Loaded data:", users);
});
}
Parent sends props with:
<AllTasks users={this.state.users} />
So, my question is: what is the best way to watch changes in child component?
I mean in this particular situation.
Yes, this is not the correct way because api calls will be asynchronous and we don't know how much time it will take.
So instead of using setTimeout, use componentWillReceiveProps lifecycle method in child component, it will get called whenever you change props values (state of parent component).
Like this:
componentWillReceiveProps(newProps){
this.setState({
users: newProps.users,
tasks: this.getTasksArray()
})
}
One more thing, don't call setState multiple times within a function because setState will trigger re-rendering so first do all the calculations then do setState in the last and update all the values in one call.
As per DOC:
componentWillReceiveProps() is invoked before a mounted component
receives new props. If you need to update the state in response to
prop changes (for example, to reset it), you may compare this.props
and nextProps and perform state transitions using this.setState() in
this method.
Update:
You are calling a method from cWRP method and using the props values inside that method, this.props will have the updated values after this lifecycle method only. So you need to pass the newProps values as a parameter in this function and use that instead of this.props.
Like this:
componentWillReceiveProps(newProps){
this.setState({
users: newProps.users,
tasks: this.getTasksArray(newProps)
})
}
getTasksArray(newProps){
//here use newProps instead of this.props
}
Check this answer for more details: componentWillRecieveProps method is not working properly: ReactJS
I found the problem.
I have getTasksArray() function that depends of props and uses this.props.users.
So, when I update state like this:
this.setState({
users: newProps.users,
tasks: this.getTasksArray()
})
getTasksArray() function uses empty array.
but when I split it to 2 lines and add setTimeout(fn, 0) like this:
this.setState({users: newProps.users});
setTimeout(()=> { this.setState({ tasks: this.getTasksArray() }, 0)
getTasksArray() function uses array that is already updated.
setTimeout(fn, 0) makes getTasksArray() to run after all other (even if I set timeout to 0 ms).
Here is the screenshot of console.log's without setTimeout:
And here is the screenshot with setTimeout:
My code works, but I have a best practice question: I have an array of objects in the state, and a user interaction will change a value of one object at a time. As far as I know, I'm not supposed to change the state directly, i should always use setState instead. If I want to avoid that with any price, I will deep clone the array by iteration, and change the clone. Then set the state to the clone. In my opinion avoiding to change the state that I will change later anyway is just decreasing my performance.
Detailed version:
this.state.data is an array of objects. It represents a list of topics in a forum, and a Favorite button will toggle, calling clickCollect().
Since I have an array in the state, when I change the is_collected property of one item, I need to create a copy of the array to work with, and after changing to the new value, I can set it to the state.
var data = this.state.data.slice(0);
data[index].is_collected = !data[index].is_collected;
this.setState({data: data});
var data = this.state.data : This would copy the pointer to the array and push(), shift(), etc would alter the state directly. Both data and this.state.data will be affected.
var data = this.state.data.slice(0) : This makes a shallow clone, push and shift doesn't change the state but in my clone I still have pointers to the elements of the state's array. So if I change data[0].is_collected, this.state.data[0].is_collected gets changed as well. This happens before I call setState().
Normally I should do:
var data = [];
for (var i in this.state.data) {
data.push(this.state.data[i]);
}
Then I change the value at index, setting it to true when it's false or false when it's true:
data[index].is_collected = !data[index].is_collected;
And change state:
this.setState({data: data});
Consider my array is relatively big or enormously big, I guess this iteration will reduce the performance of my APP. I would pay that cost if I knew that it is the right way for any reason. However, in this function (clickCollect) I always set the new value to the state, I'm not waiting for a false API response that would say to stop making the change. In all cases, the new value will get into the state. Practically I call setState only for the UI to render again. So the questions are:
Do I have to create the deep clone in this case? (for var i in ...)
If not, does it make sense to make a shallow clone (.slice(0)) if my array contains objects? The changes are being made on the objects inside of the array, so the shallow clone still changes my state, just like a copy (data = this.state.data) would do.
My code is simplified and API calls are cut out for simplicity.
This is a beginner's question, so a totally different approach is also welcome. Or links to other Q & A.
import React from 'react';
var ForumList = React.createClass({
render: function() {
return <div className="section-inner">
{this.state.data.map(this.eachBox)}
</div>
},
eachBox: function(box, i) {
return <div key={i} className="box-door">
<div className={"favorite " + (box.is_collected ? "on" : "off")} onTouchStart={this.clickCollect.bind(null, i)}>
{box.id}
</div>
</div>
},
getInitialState: function() {
return {data: [
{
id: 47,
is_collected: false
},
{
id: 23,
is_collected: false
},
{
id: 5,
is_collected: true
}
]};
},
clickCollect: function(index) {
var data = this.state.data.slice(0);
data[index].is_collected = !data[index].is_collected;
this.setState({data: data});
}
});
module.exports = ForumList;
Personally I don't always follow the rule, if you really understand what you are trying to do then I don't think it's a problem.
var data = this.state.data.slice(0);
data[index].is_collected = !data[index].is_collected;
this.setState({data: data});
In this case, mutating state and calling the setState again like this is fine
this.state.data[index].is_collected = !this.state.data[index].is_collected;
this.setState({data: this.state.data});
The reason you should avoid mutating your state is that if you have a reference to this.state.data, and calling setState multiple times, you may lose your data:
const myData = this.state.data
myData[0] = 'foo'
this.setState({ data: myData })
// do something...
// ...
const someNewData = someFunc()
this.setState({ data: someNewData })
myData[1] = 'bar' // myData is still referencing to the old state
this.setState({ data: myData }) // you lose everything of `someNewData`
If you really concerned about this, just go for immutable.js
Muting the state directly breaks the primary principle of React's data flow (which is made to be unidirectional), making your app very fragile and basically ignoring the whole component lifecycle.
So, while nothing really stops you from mutating the component state without setState({}), you would have to avoid that at all costs if you want to really take advantage of React, otherwise you would be leapfrogging one of the library's core functionalities.
If you want follow react best practices, you should do shallow copy of all your array, when you change any property. Please look into "immutable" library implementation.
But, from my experience, and from my opinion, setState method should be called if you have "shouldCompomenentUpdate" implementations. If you think, that your shallow copy will be consume much more resources, then react virtual dom checks, you can do this:
this.state.data[0].property = !this.state.data[0].property;
this.forceUpdate();
If I understood your question right, you have an array of objects and when a property of a single object in array changes,
Create a deep clone of the array and pass to setState
Create a shallow clone and pass to setState
I just checked with the redux sample todo app and in case of a single property of an object changes you've to create a fresh copy of that single object not the entire array. I recommend you to read about redux and if possible use to manage the state of your app.