react.js does not update DOM after having changed state array - javascript

I am trying to implement a game in react where I have the board as an two dimensional array in the initial state of the parent class. Tiles are rendered by iterating through that array. I pass those children a function as a prop so that they can change that state array.
Now, when I use that function to change the array, the HTML does not update. The array gets updated when I call setState but it never rerenders. I tried this.forceUpdate() but still no luck. What I then did was to pass a function from that child to the parent through the function to update that child's state and this works, but I need the function from the parent to call itself recursively to update the board. I feel like I might have hit an anti-pattern. How could I change my code in order for the DOM to update, please?
There is parts missing but those are all the components involved. statusBoard is the internal version of the board featuring the solution. I hope this is clear.

Whenever you are linking props and state together you have to create a componentWillReceiveProps function like so:
componentWillReceiveProps: function(nextProps) {
this.setState({
positionX: nextProps.columnPosition,
positionY: nextProps.rowPosition
});
}
When games state changes and passes it down as props to Field, fields own internal state doesn't get updated at that point because it is relying on its own state. this is an anti pattern and you should avoid Field having to have any state and just rely on its props it gets passed and have a parent component that is handling the state of everything.
https://facebook.github.io/react/tips/props-in-getInitialState-as-anti-pattern.html

Related

How to force an update of a subcomponent?

Lit Documentation says:
When called with no arguments, requestUpdate() schedules an update,
without calling a hasChanged() function. But note that
requestUpdate() only causes the current component to update. That
is, if a component uses the code shown above, and the component
passes this.myArray to a subcomponent, the subcomponent will detect
that the array reference hasn't changed, so it won't update.
So how to force an update of a subcomponent (even if the subcomponent's attribute doesn't change)?
render() {
return html`
<ul>
${this.myArray.map(
(item) => html`
<li>
${item.text}
<my-subcomponent foo="${item.bar}"></my-subcomponent>
</li>
`
)}
</ul>
`;
}
Edit: Repro in Lit Playground
Thank you for the lit.dev playground repro, I was able to identify some issues that should resolve your issue.
Before jumping into the various fixes, why is the subcomponent not re-rendering? LitElements render as a function of their properties & state. If properties don't change, they don't re-render.
The subcomponent has two reactive properties, myArray and id. myArray is only set once on construction from localstorage and never updated - so it will never trigger an update. The id number also never changes for a given subcomponent so will never trigger an update. Because neither properties change, the subcomponent doesn't re-render.
Option 1: Pass the new array to the sub-component.
Fix 1 in Lit Playground
The only change I've made is to pass myArray to sub-component explicitly, (note .myArray=${this.myArray}):
<li>${item.text} (<sub-component id=${item.id} .myArray=${this.myArray}></sub-component>)</li>
This works because now when the parent updates this.myArray, the parent passes this new array to the sub-component. The sub-component then notices that the reactive property myArray has changed and a render is scheduled.
Option 2: Only pass the item to the sub-component
This is a larger change, but more maintainable and simpler. Instead of passing both the array and item id to each sub-component, only pass the item that the sub-component cares about.
This does less work on the sub-component because each sub-component doesn't need to loop through the whole array to find the element that it cares about.
Working fixed playground from repro.
This approach requires a change in changeMyArray. Instead of mutating the items, new items need to be returned.
changeMyArray() {
this.myArray = this.myArray.map(
(item) => {
return {...item, colored: !item.colored};
}
);
}
Breaking down that example. this.myArray.map already returns a new array so the array spread is not required. Within the map each item that is modified must return a new object. This is similar to how redux works and the linked article may provide more helpful details.
Now the parent render function can be updated to pass the item directly to the subcomponent with: <li>${item.text} (<sub-component .item=${item}></sub-component>)</li>.
Now when the item changes, the sub-component automatically re-renders.
Option 3: Manually calling a method that triggers an update
I would not recommend this over option 2, but wanted to show how you could trigger the sub-component to re-render.
Working sample: https://lit.dev/playground/#gist=fb38e52bc4d35dd74485407eb19db84f
There are two changes. In the parent, 'main-component', changeMyArray method I've added a loop that calls a method on each sub-component:
this.shadowRoot.querySelectorAll('sub-component')
.forEach(component => component.refresh())
And on the sub-component I've defined refresh as:
refresh() {
this.myArray = JSON.parse(localStorage.getItem('myArray'));
}
Because the sub-component now has the new version of this.myArray from localstorage it will trigger a re-render.
The cons of this approach and why option 1 or 2 is better:
Each sub-component needs to manually parse the array out of localstorage.
This approach requires manually managing when renders happen instead of letting the state flow down through properties.
There's a video called Event communication between web-components by Elliott on the Lit team that discusses state and events further.

Why React keep componentWillReceiveProps and shouldComponentUpdate methods both?

when i use react ,i find these two life cycle are too similar, componentWillReceiveProps receive nextProps as argument, shouldComponentUpdate receive nextProps and nextState as arguments, so i think shouldComponentUpdate can do the same thing and more, why react keep componentWillReceiveProps method, i wonder what's difference between these two methods
They have two different roles and execute on different situations:
shouldComponentUpdate will be called every time a prop or something in the state changes (or React think that has changed). It's function is to determine if the component should re-render by returning a boolean: true if the component should re-render (this is the default return value), or false if it shouldn't.
You can access the current and next state and props, to compare and decide if it really should re-render or not. You should not use this method for other reason.
On the other side, componentWillReceiveProps will only be called if the props changed (or seem to have changed). If only the state changes, this method won't be called.
Also, this won't decide if the component should re-render. You can use this method to, for example, change some state, or make an API call.
Check out these links:
componentWillReceiveProps: https://developmentarc.gitbooks.io/react-indepth/content/life_cycle/update/component_will_receive_props.html
shouldComponentUpdate: https://developmentarc.gitbooks.io/react-indepth/content/life_cycle/update/using_should_component_update.html
componentWillReceiveProps - as the function name states this is called whenever new props will be passed to the component and you can trigger an action depending on the new prop state
shouldComponentUpdate - is a filter function which decides if the component tree should be re-rendered. This function can serve as an additional filter where you change are happening which don't require a re-render
More info here

Why use props in react if you could always use state data?

I understand that there's two ways to pass components data: props and state. But why would one need a prop over a state? It seems like the state object could just be used inside the component, so why pass the prop parameters in markup?
Props are set externally by a parent component. E.g.;
render() {
return <ChildComponent someProp={someValue}/>;
}
State is set internally, and often triggered by an user event within a child. E.g.;
handleUserClickedButton: () {
this.setState({
buttonClicked: true
});
},
render() {
return <button onClick={this.handleUserClickedButton}/>;
}
So, props are a way for data to go from parent to child. State is a way for data to be managed within a singular component, and possibly have changes to that data triggered by children. In effect, they represent data traveling in 2 opposite directions, and the way in which they are passed is entirely unique.
There are two ways to "pass" or access data from outside your component but state is not one of them.
The two ways are:
Props - which a parent component pass down to the child component.
Context - which you can "skip" the direct parent in the tree.
The state is an internal object which no other component has access to it unless you pass it explicitly (via the two ways mentioned above).
So basically your question is not accurate as you can't really compare the two.
I think what you are really asking is why using a state-less instead of a state-full component.
Which you can find an answer here in Stack-overflow or in other websites.
Edit
A followup to some of your comments.
why does the child not just have a shared state? for example, each
component (or sub-component) could just do a "this.state" to get the
current state of the program
The same way you can't share or access private objects in other
functions.
This is by design, you share things explicitly and you will pass
only what the component needs. For example, look it this page of
stack-overflow, lets say the voting buttons are components, why
would i pass them the whole state if it only needs the vote count
and 2 onClick event listeners? Should i pass the current logged in
user or maybe the entire answers rendered in this page?
so you can't pass state between a parent to child? for example, can't
the parent change the state and then the child gets the new state
This is exactly what the props or context should do, provide an API for sharing data between parents and children though we keep it in a one way data flow, from parents to children, you can't pass props upwards. but you invoke handlers passed down to your child components and pass data through that handler.

React - updating state during render produces errors

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.

Mutating props in React

I have a React component which receives an array or objects via props. Something I would like to do is have an event re-order these props. However, it seems that React re-rendering only occurs when state changes.
Right now I have the ordering handled in the parent object and pass the method to handle the ordering through as a prop, but ideally I want the component responsible for rendering these objects to also handle the ordering.
Chucking props into state seems bad, but what's the best thing to do here?
Props are immutable, but in your case it seems that your data does not change, only the sort order changes, so you can:
keep your data and sort functions as props
store your sort order in state
maybe use getInitialState to return the default sort order
when sort order is changed, state is set so re-render happens
As i understand React needs to change the state to trigger the render. If you want to keep the sort logic attached to you component you have to create a copy of the array.
getInitialState: function () {
return {
items: this.props.items.slice(0)
};
},
As implemented here.

Categories

Resources