Sharing a State Array - javascript

Have kinda a unique question, in my code I have a listener to a database that loads down objects into an array.
All I do when I load it in is
AddObject(obj){
this.setState({
Data: [...this.state.Data, obj]
});
}
Pretty simple. However this listener function, there is no exact time when need data will be added. When I go to use that Data sent in Data, I went to pull it out of the Data Array, however I am worried if I try copying data out of the array, or removing the "seen" data, I will get weird behaivor if my listener function triggers and I try adding data to the array at the same time.
Is there some sort of a way to do this? I guess you could call this a shared resource
Ideally, I would have something like this:
loadDataIN(){
var LengthToGrab = this.state.Data.length;
//we need to remove this length, now any new data will be added to index 0
}
Does this make sense? basically I am trying to figure out the best way to remove data from this array, and not have to worry about overwritting, or losing data. Maybe some sort of processing que

From official doc
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.
You don't need to worry that two kinds of situation would have conflict in the same time.
setState() enqueues the pending state before the changes be rendered.
In fact, no matter how mechanism be implemented, React is a framework of JavaScript which is working on a model event-loop.
So if you want to pull out the data from this.state.Data:
loadDataIN(){
this.setState(function(prevState, props) {
// this.fetchData = prevState.Data;
return {
Data: []
};
});
}

Related

Sequential dependent functions in Vuex action

I'm currently building a Vue app that consumes data from the Contentful API. For each entry, I have a thumbnail (image) field from which I'd like to extract the prominent colours as hex values and store them in the state to be used elsewhere in the app.
Using a Vuex action (getAllProjects) to query the API, run Vibrant (node-vibrant) and commit the response to the state.
async getAllProjects({ commit }) {
let {
fields: { order: order }
} = await api.getEntry("entry");
let projects = order;
projects.forEach(p =>
Vibrant.from(`https:${p.fields.thumbnail.fields.file.url}`)
.getPalette()
.then(palette => (p.fields.accent = palette.Vibrant.hex))
);
console.log(projects);
// Commit to state
commit("setAllProjects", projects);
}
When I log the contents of projects right before I call commmit, I can see the hex values I'm after are added under the accent key. However, when I inspect the mutation payload in devtools, the accent key is missing, and so doesn't end up in the state.
How do I structure these tasks so that commit only fires after the API call and Vibrant have run in sequence?
You cannot add a property to an object in Vue and have it be reactive; you must use the Vue.set method.
Please try replacing that forEach block with the following, which adds the new property using Vue.set:
for (i=0; i<projects.length; i++)
Vibrant.from(`https:${projects[i].fields.thumbnail.fields.file.url}`)
.getPalette()
.then(palette => (Vue.set(projects[i].fields, accent, palette.Vibrant.hex)))
);
UPDATE: changing the format from forEach to a conventional for loop may be gratuitous in this case, since the assignment being made is to an object property of projects and not to a primitive.
I'm not spending a lot of time on StackOverflow, and if the above answer works, I am happy for you indeed.
But I expect from that answer you will get console warnings telling you not to mutate state directly.
Now when this happens, it's because while Vue.set(), does in fact help Vue understand reactively a change has been made, potentially deeply nested in an object.
The problem here is that since you are looping the object, changing it all the time, the commit (Mutator call) is not the one changing state - Vue.set() is actually changing it for every iteration.

React state gets "corrupted"

I am working on a simple React.JS frontend piece.
I essentially have a browsing SPA for historical data. The design has a bunch of filters that I need to populate one at a time, starting the top one in my logic hierarchy.
I do something like:
componentWillMount() {
if (!this.props.advertisers) {
this.props.loadAdvertisers();
}
}
Once advertisers array has been loaded, I map it to options of a select component (using react-select), set the selection to the first item in the list and load the next list - campaigns.
As far as I understand, the best way to do this is still componentWillReceiveProps() and I am a little perplexed how this should be done differently, given that componentWillReceiveProps is being phased out.
My code looks like:
componentWillReceiveProps(nextProps) {
if (nextProps.advertisers && !this.props.advertisers) {
const advertiser = nextProps.advertisers[0];
this.setState({
advertiser: {
value: advertiser['id'],
label: advertiser['name']
}
});
nextProps.loadCampaigns(advertiser['id']);
}
// if campaigns list changed, reload the next filtered array
if (nextProps.campaigns && this.props.campaigns !== nextProps.campaigns) {
...
}
This worked fine, until I decided to add a loading indicator. I mapped state's loading property e.g. for campaigns it gets exposed via this.props.campaignsLoading then do:
return (this.props.campaignsLoading || this.props...Loading || ...) ?
<ProgressIndicator> : <MainContentPanel>
The problem is now, my state does not get set correctly inside componentWillReceiveProps().
The project is using #rematch and I initially tried this with #rematch/loading plugin and when the problem happened, thought the plugin does it wrong, somehow. Then, I mapped loading properties manually, and just added two more dispatches to manually set the loading flag.
All the props are being set/unset correctly, but my state is not being set and nothing works. Any suggestions?
When you do
if (nextProps.advertisers && !this.props.advertisers) {
You are not comparing the next and the previous props. "this.props.advertisers" is probably already set so you never go into the setState line. Although using componentWillReceiveProps is no longer the recommended way to go (You Probably Don't Need Derived State), what you probably want to do roughly is:
if (nextProps.advertisers && nextProps.advertisers !== !this.props.advertisers) {

React object.map not updating due to the key

The basic problem I'm having is that I'm updating my data using an AJAX request and then setting the state of data, but in the view, the data never update even though my state is updated. I've figured out that it's because the key is the same (which it should be because I'm just using the ID mongoose provides).
If I do things like set the state to nothing and then set then set the new data then it updates. Or if I set the key to a randomly generated key it also works, but both of these don't feel right. I want to know what the correct way to re-render an updated object when using the map function.
It looks something like this:
Object.keys(this.state.data).map((i) => (
<MyComponent key={ this.state.data[i]._id }
))
And then I have a basic AJAX request that does this.setState({ data: response.data }).
How do I make react update the data in the view for the updated item properly?
Can you add the index as a prefix to the _id?
key={ `${i}_${this.state.data[i]._id}`}
I am not really sure it is related to the key, if you console.log(this.state.data) just before your code it gets updated when it should? it seems to me that the props are not getting passed to the component.
I mean I often use the index of map in the key in the lists I never had any problem with that.

React Native - populating ListView with async data

I'm getting a list of items as a prop through the redux store. This prop is asyncronous.
My ListView works with syncronous hardcoded data. So the problem is populating with the prop when I actually receive it.
I'm making a ListView out of this item prop data. What's the best practice on life cycle choices to setState with this prop? I've tried componentDidMount - but that's too early and only called once. I tried componentWillReceiveProps - I get the prop, but my view doesn't change. I also tried componentWillUpdate and I get too many calls! I'm tempted to put it in the render function because I know I can grab that prop when I get it but that makes the render function no longer pure.
Thanks for any help!
this.setState({
dataSource: this.state.dataSource.cloneWithRows(this.props.items);
})
The solution was that I actually needed to convert items as a List from immutable to a JS Array for the ListView. Not what I originally thought the problem was in the question.
Here's the solution:
componentWillReceiveProps(newProps){
if(newProps.items){
this.setState({
dataSource: this.state.dataSource.cloneWithRows(newProps.items.toJS())
})
}
}
I'm still wondering if I'm using best practice by using componentWillReceiveProps. I'm also wondering if it's a little strange that I have to convert from a List to an Array, but if it's purely presentational that is ok, right?
When using componentsWillReceiveProps, it's good practice to check whether those props have changed. Also not sure why you have to do .toJS(), you just need to return a new array so React knows to update.
componentWillReceiveProps(newProps){
if (newProps.reasonOptions !== this.props.reasonOptions){
this.setState({
dataSource: this.state.dataSource.cloneWithRows([...newProps.items])
});
}
}

React - Changing the state without using setState: Must avoid it?

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.

Categories