Updating child props from parent state in ReactJS - reactjs

I'm trying to understand how I can structure a ReactJS app with different "pages" or "views".
I have the following component as my base app and I'm using a currentState property in the React state to switch between which Components are active in the view.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {currentState: 'Loading', recipes: []};
this.appStates = {
'Loading': <Loading/>,
'Home': <RecipeList recipes={this.state.recipes}/>
}
}
dataLoaded(data) {
this.setState({
recipes: data,
currentState: 'Home'
});
}
componentDidMount() {
// AJAX Code that retrieves recipes from server and then calls dataLoaded
}
render() {
return this.appStates[this.state.currentState];
}
}
Which does the job, but the component never receives the updated recipes array when the dataLoaded callback is fired.
How can I cause the to update its props based on the updated state in the App?
Or am I approaching this whole thing the wrong way?

I think that your aproach isn't really react-like, and you have at least a couple of concepts that can be improved.
First of all, I would definitely use react-router to achieve any complex navigation between pages/Components in React.js. Implementing it yourself is more complicated and error-prone. react-router will allow you to assign Components to different routes easily.
Second, I think that you should almost never store things in the context this directly. Basically because it leads to errors like yours here: not realizing that appStates isn't changing at all. React's state is a great tool (which must sometimes be replaced/complemented with others like Redux) to store your application's state.
In the case of storing in the state what should be rendered in the Component, you should probably complement the react-router functionality with simple flags in the state initializated in the constructor that allow you to know what should you return in the render function.
Here is an example that shows how can you tell a component to change its view dynamically between loading and loaded by using just React's state. Of course, you could recreate a very similar behaviour making an AJAX call in componentDidMount and changing the state to stop loading when it's done instead of using a button.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {loading: true};
this.stopLoading = this.stopLoading.bind(this);
}
stopLoading() {
this.setState({
loading: false,
});
}
render() {
let view=<div><h1>loading</h1><button onClick={this.stopLoading}>FINISH</button></div>;
if(!this.state.loading){
view=<h1>loaded</h1>;
}
return <div>{view}</div>;
}
}
ReactDOM.render(
<App />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>

Your constructor method is only executed when the component mounts, at which point recipes is empty, and passes that empty array to appStates. Essentially, appStates never changes.

What I would recommend doing is pushing all the component code in the constructor to inside the render function. The beauty of react is that you can completely re-render everything at very little cost. React will do the hard work of comparing the difference in DOMs and only re-render the minimal differences.

I agree with #Ezra Chang. I think the code could be adjusted making use of just the state and the javascript spread function to pass the App props to the child RecipeList:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {currentState: 'Loading', recipes: [],'Loading': <Loading/>,
'Home': <RecipeList recipes={this.state.recipes} {...this.props}/>};
}
//I assume you want to pass the props of this App component and the data from the back-end as props to RecipeList.
dataLoaded = (data) => {
this.setState({
recipes: data,
currentState: 'Home',
'Loading': <Loading/>,
'Home': <RecipeList recipes={data} {...this.props}/>
});
}
componentDidMount() {
// AJAX Code that retrieves recipes from server and then calls dataLoaded
}
render() {
return this.state[this.state.currentState];
}
}

Related

ReactJS: Deep nested state

Let's say I have this React component:
class SomeComponent extends Component {
constructor(props) {
super(props);
this.state = {
topObject: {
childObject1: {
grandChildObj1: {
attr1: this.props.val1,
attr2: this.props.val2
}
},
childProp: 1
},
topProp: 2
};
}
render() {
return (
<div>
<span>{this.state.topObject.childObject.grandChildObject.attr1}
</span>
</div>
// ...
)
}
changeDeepNestedStateValue(val) {
// need code here to change the state
// set topObj.childObject.grandChildObject.attr1
// to the 'val' argument
}
}
What code would I need inside the function 'changeDeepNestedStateValue' so that it changes the state immutably so that React detects the change and re-renders?
Are deep-nested state values a bad practice or anti-pattern? If so, is there an optimal structure to a state, a flat one maybe?
Deep nesting is not necessarily an anti-pattern but just makes your code harder to maintain and reason about. In order to trigger a re-render and update component state, all you have to do in the changeDeepNestedStateValue function is call this.setState({ topObject: {...} }) with whatever new state you want to update. Optionally, this.setState also takes a function that exposes the previous state of the component as seen below.
this.setState((prevState) => {
if (prevState.topProp === val) {
doSomething();
}
});
I'd recommend having a look at immer
https://github.com/mweststrate/immer
It provides a super easy way to work with nested objects in terms of immutablilty
But yes, flatter state with out of the box react state management is better practice

How to refresh props with React/Redux when user enters a container

I have CompetitionSection which repeats all the competitions from database. When user clicks on one, it redirects him to a Competition Page, loads for a second and renders the page with all the details in it. So far, so good.
But when users goes back to the Competition Section and then click on the second competition, it instantly loads up the previous competition, 0 loading time.
From my point of view, what is failing is that the props of the component are not updating when I render the component (from the second time). Is not a router problem, which was my first instinct because I'm seeing the route.params changing acordingly, but the actions I dispatch to change the props are not dispatching. Here's a bit of code of said component.
class CompetitionPage extends React.Component {
componentWillMount() {
let id = getIdByName(this.props.params.shortname)
this.props.dispatch(getCompAction(id));
this.props.dispatch(getCompMatches(id));
this.props.dispatch(getCompParticipants(id));
this.props.dispatch(getCompBracket(id));
}
render() {
let { comp, compMatches, compBracket, compParticipants } = this.props
...
I tried every lifecycle method I know. component Will/Did Mount, component Will/Did update and I even set shouldUpdate to true and didn't do the trick. As I understand, the problem will be solved with a lifecycle method to dispatch the actions everytime an user enters Competition Page and not just for the first time. I'm running out of options here, so any help will be appreciated.
NOTE: I'm a newbie at React/Redux so I KNOW there are a couple of things there are anti-pattern/poorly done.
UPDATE: Added CompetitionsSection
class CompetitionsSection extends React.Component {
render() {
const {competitions} = this.props;
return (
...
{ Object.keys(competitions).map(function(comp, i) {
return (
<div key={i} className={competitions[comp].status ===
undefined? 'hide-it':'col-xs-12 col-md-6'}>
...
<Link to={"/competitions/"+competitions[comp].shortName}>
<RaisedButton label="Ver Torneo" primary={true} />
</Link>
...
It helps to better understand the lifecycle hooks. Mounting a component is when it is placed on the DOM. That can only happen once until it is removed from the DOM. An UPDATE occurs when new props are passed or setState is called. There are a few methods to troubleshoot when updates are not happening when you think they should:
Ensure that you are changing state in componentDidMount or componentDidUpdate. You cannot trigger an update in componentWillMount.
Make sure that the new props or state are completely new objects. If you are passing an object down in props and you are just mutating the object, it will not trigger an update. For instance, this would not trigger a update:
class CompetitionPage extends React.Component {
constructor(props) {
super(props)
this.state = {
competitions: [ compA, compB ]
}
}
triggerUpdate() {
this.setState({
competitions: competitions.push(compC)
})
}
componentDidMount() {
triggerUpdate()
}
render() {
return(
<div>
Hello
</div>
)
}
This is due to the fact that a new competition is being appended to the array in state. The correct way is to completly create a new state object and change what needs to be changed:
const newCompetitions = this.state.competitions.concat(compC)
this.setState(Object.assign({}, this.state, { competitions: newCompetitions }))
Use ComponentWillRecieveProps on an update to compare previous and current prop values. You can setState here if clean up needs to be done:
Read more about this method in the React documentation:
https://facebook.github.io/react/docs/react-component.html#componentwillreceiveprops

React — Passing data between siblings and iterating it

I have two questions, the first is about passing data between components and the second is about the component hierarchy.
Right now, in the Data component I am trying to set the name property and pass it to a ListItem component that should iterate based on the API request. I tried many things without success. What am I doing wrong? Does the data needs to be iterated when setting the new state? Am I passing it correctly?
Basic pseudocode
Read data from API
Set data to the component state
Create siblings components based on data stored
Render components
The second question is about the hierarchy of the components. I keep reading around the web that the data request should be setup at the top and separated. Having this in place, the other components would feed from this data and execute. Finally, the App component would render all this components accordingly. From the example below, am I going to the right track? Was I correct to creating a component specific for data request or should this be done in the App component?
I understand these questions might be basic but it is something that I am really struggling to understand and I would appreciate if someone can help me understand or point me to a basic example that I can digest.
Thank you in advance. (Sorry, I had more than two questions.)
class App extends React.Component {
render() {
return (
<ul>
<ListItem name={this.state.name} />
</ul>
)
}
}
class Data extends React.Component {
constructor(props) {
super(props)
this.state = {
name: '',
requestFailed: false,
}
}
componentDidMount() { // Executes after mouting
fetch('https://api.tfl.gov.uk/BikePoint')
.then((response) => {
return response.json()
}).then((d) => {
console.log('parsed json', d[0].commonName)
this.setState({
name: d.commonName
});
}).catch(function(ex) {
console.log('parsing failed', ex)
this.setState({
requestFailed: true
})
})
}
render() {
if(this.state.requestFailed) return <p>Request failed.</p>
if(!this.state.name) return <p>Loading</p>
const namesList = names.map(function(name, index){
return <ListItem key={index} name={this.state.name} />
})
return <ul>{ namesList }</ul>
}
}
class ListItem extends React.Component {
render() {
return <li> { this.props.name } </li>
}
}
ReactDOM.render(<App />, document.getElementById('app'));
CodePen
Where to start -
First, your App component needs to render Data component. React works in the way that parent element always renders children elements and what is not rendered doesn't exist.
Then, you need to remap response to names, if that is what you wanted to do - I am not sure.
In render method, you want to take name out of mapping function, not from state. I also removed name state, you really want to keep names instead of one name. There is a lot of small thing I had to adjust to make it work, so I will just post working code pen here, so you can see what needed to be done.
https://codepen.io/anon/pen/eEmqxX?editors=0010

React Child Component Not Updating After Parent State Change

I'm attempting to make a nice ApiWrapper component to populate data in various child components. From everything I've read, this should work: https://jsfiddle.net/vinniejames/m1mesp6z/1/
class ApiWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
response: {
"title": 'nothing fetched yet'
}
};
}
componentDidMount() {
this._makeApiCall(this.props.endpoint);
}
_makeApiCall(endpoint) {
fetch(endpoint).then(function(response) {
this.setState({
response: response
});
}.bind(this))
}
render() {
return <Child data = {
this.state.response
}
/>;
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
data: props.data
};
}
render() {
console.log(this.state.data, 'new data');
return ( < span > {
this.state.data.title
} < /span>);
};
}
var element = < ApiWrapper endpoint = "https://jsonplaceholder.typicode.com/posts/1" / > ;
ReactDOM.render(
element,
document.getElementById('container')
);
But for some reason, it seems the child component is not updating when the parent state changes.
Am I missing something here?
There are two issues with your code.
Your child component's initial state is set from props.
this.state = {
data: props.data
};
Quoting from this SO Answer:
Passing the intial state to a component as a prop is an anti-pattern
because the getInitialState (in our case the constuctor) method is only called the first time the
component renders. Never more. Meaning that, if you re-render that
component passing a different value as a prop, the component
will not react accordingly, because the component will keep the state
from the first time it was rendered. It's very error prone.
So if you can't avoid such a situation the ideal solution is to use the method componentWillReceiveProps to listen for new props.
Adding the below code to your child component will solve your problem with Child component re-rendering.
componentWillReceiveProps(nextProps) {
this.setState({ data: nextProps.data });
}
The second issue is with the fetch.
_makeApiCall(endpoint) {
fetch(endpoint)
.then((response) => response.json()) // ----> you missed this part
.then((response) => this.setState({ response }));
}
And here is a working fiddle: https://jsfiddle.net/o8b04mLy/
If the above solution has still not solved your problem I'll suggest you see once how you're changing the state, if you're not returning a new object then sometimes react sees no difference in the new previous and the changed state, it's a good practice to always pass a new object when changing the state, seeing the new object react will definitely re-render all the components needing that have access to that changed state.
For example: -
Here I'll change one property of an array of objects in my state, look at how I spread all the data in a new object. Also, the code below might look a bit alien to you, it's a redux reducer function BUT don't worry it's just a method to change the state.
export const addItemToCart = (cartItems,cartItemToBeAdded) => {
return cartItems.map(item => {
if(item.id===existingItem.id){
++item.quantity;
}
// I can simply return item but instead I spread the item and return a new object
return {...item}
})
}
Just make sure you're changing the state with a new object, even if you make a minor change in the state just spread it in a new object and then return, this will trigger rendering in all the appropriate places.
Hope this helped. Let me know if I'm wrong somewhere :)
There are some things you need to change.
When fetch get the response, it is not a json.
I was looking for how can I get this json and I discovered this link.
By the other side, you need to think that constructor function is called only once.
So, you need to change the way that you retrieve the data in <Child> component.
Here, I left an example code: https://jsfiddle.net/emq1ztqj/
I hope that helps.
Accepted answer and componentWillReceiveProps
The componentWillReceiveProps call in accepted answer is deprecated and will be removed from React with version 17 React Docs: UNSAFE_componentWillReceiveProps()
Using derived state logic in React
As the React docs is pointing, using derived state (meaning: a component reflecting a change that is happened in its props) can make your components harder to think, and could be an anti-pattern. React Docs: You Probably Don't Need Derived State
Current solution: getDerivedStateFromProps
If you choose to use derived state, current solution is using getDerivedStateFromProps call as #DiogoSanto said.
getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing. React Docs: static getDerivedStateFromProps()
How to use componentWillReceiveProps
This method can not access instance properties. All it does describing React how to compute new state from a given props. Whenever props are changed, React will call this method and will use the object returned by this method as the new state.
class Child extends React.Component {
constructor() {
super(props);
// nothing changed, assign the state for the
// first time to teach its initial shape.
// (it will work without this, but will throw
// a warning)
this.state = {
data: props.data
};
}
componentWillReceiveProps(props) {
// return the new state as object, do not call .setState()
return {
data: props.data
};
}
render() {
// nothing changed, will be called after
// componentWillReceiveProps returned the new state,
// each time props are updated.
return (
<span>{this.state.data.title}</span>
);
}
}
Caution
Re-rendering a component according to a change happened in parent component can be annoying for user because of losing the user input on that component.
Derived state logic can make components harder to understand, think on. Use wisely.

redux check state at componentWillMount

in my react component, I have two attributes in the state, one in local react state and the other in Redux store.
componentWillMount() {
this.props.fetchExercise(this.props.params.id);
}
constructor(props) {
super(props);
this.state = {editeMode: false}
}
function mapStateToProps(state) {
return {currentExercise: state.currentExercise}
}
export default connect(mapStateToProps, {fetchExercise})(createNewExercisePage);
so according to the path; /new-exe/:id currentExercise in Redux is either empty or something is fetched. editeMode is in React. now I want to check if I have something in currentExercise editemode:true else it should be false (according to false and true I am showing different buttons).
I tried it (with lodash) in componentWillMount(){... this.setState({editeMode:_.isNull(this.props.currentExercise)})}
but it does not work, it reamins false.
generaly in these cases that first should fetch something then check it, what should be the approach.
You should avoid introducing any side-effects or subscriptions in componentWillMount (docs). The documentation also says that "setting state in this method will not trigger a re-rendering", so I guess that means that the setted value will be ignored.
You are not going to change the value of the editeMode entry in the store unless the value of this.props.currentExercise changes, and so it does not serve much purpose to keep track of the changes in order to update the store. Just use the value directly. In your particular case, I would do the following:
componentWillMount() {
this.props.fetchExercise(this.props.params.id);
}
constructor(props) {
super(props);
this.state = {}
}
render(){
const editeMode = _.isNull(this.props.currentExercise);
// The rest of your render logic, using editeMode instead of this.state.editeMode
}
function mapStateToProps(state) {
return {currentExercise: state.currentExercise}
}
export default connect(mapStateToProps, {fetchExercise})(createNewExercisePage);
Put the code in
componentWillReceiveProps.
componentWillReceiveProps(nextProps) {
this.setState({ editeMode: !nextProps.currentExercise) });
}
Redux will make sure the props get updated.
You should also consider putting the editMode state in Redux instead.

Resources