Child component do not see Parent Component state updates - reactjs

I'm new to React and i'm facing an issue related to state updates.
I have a Parent Component. In the Parent Component constructor, i create multiple instance of a Child Component.
Using the state of the Parent Component, i display one of the Child Component instance.
Instances of Child Component have some Parent Component state value passed as props.
The Parent Component state looks like this (i have simplify the code so it can be clearer)
displayedContainer: {...} // An Instance of Child Component
isLoading: false
The Parent Component constructor looks like this
constructor(props) {
super(props);
// Create state
this.state = {
isLoading: false,
displayedContainer: null
};
// Create all Child Component (Container)
this.defaultComponent = <Container
isLoading={this.state.isLoading}
></Container>
// Others Child Component are created the same way as above.
// To clearify the code i have removed them.
}
And here is the render method
render() {
return (
<div>
{this.state.displayedContainer}
<div className="container-left-bar"></div>
</div>
)
}
From there, i can switch from one Child Component display to another so the state.displayedContainer is working. But when the state.isLoading is getting updated, Child Components doesn't detect it. I think it's because i'm creating the Child Component in the constructor.
How should i do if i want to keep the logic of creating Child Components before rendered it but fix the issue of state updates not detected ?
Thanks for the help !

The problem is that you render the <Container /> only once, in the constructor. The rendered instance is in the memory (this.defaultComponent) and therefore when you call this.setState the child never gets updated - is not notified about the change of any prop. This code should go to render() method.
Think of it like this:
When React determines this.setState (e.g. you want to display other container then the current one), React calls render() method, and should rerender <Container .../> with updated props. But since the code for rendering the component is not in the render() method - code that tells the <Container .../> to use newest isLoading prop from the state, <Container /> never really gets updated with new value of the isLoading prop (or any other prop).
You should achieve something like this:
render() {
...
let renderCurrentContainer = null
if (...) {
renderCurrentContainer = <Container isLoading={this.state.isLoading} ...otherPropsHere... />
}
else if (...) {
renderCurrentContainer = ...
}
else if (...) {
renderCurrentContainer = ...
}
return <...>
{renderCurrentContainer}
</...>
}
If you're asking what to put into the if condition, you need to somehow mark which component to render currently, I'll leave that to your creativity, but you can use something like currentRenderedContainerIndex which can have values {0, 1, 2}, or currentRenderedContainer string from enum e.g. {'FIRST_COMPONENT', 'SECOND_COMPONENT', 'THIRD_COMPONENT'}
Then you would go with something like this:
if (currentRenderedContainer === 'FIRST_COMPONENT') {
renderCurrentContainer = <Container isLoading= {this.state.isLoading} ...otherPropsHere... />
}
else if (currentRenderedContainer === 'SECOND_COMPONENT') {
renderCurrentContainer = ...
}
else if (currentRenderedContainer === 'THIRD_COMPONENT') {
renderCurrentContainer = ...
}

Related

how to update both parent and child state before render without extra renders in React 15

If I use setState in the child and place a callback in the parent to update the parent state that propagates to child props, then I end up with two render calls.
One for the update to the child state, and one for the prop changing. I can manually use shouldComponentUpdate to ignore the prop change if I want, but the render won't be ready until the state updates.
I know all this can be done easily in react 16-18, but migrating is not simple at the moment.
I am wondering if the solution is to make the child state the source of truth. I can solve all my problems this way, but I thought in react you typically made the parent the source of truth.
Parent Component
Child Component
ChildComponent
function = () => {
this.setState ( {updatedStateProperty}, callback())
}
ParentComponent
callback = () => {
this.setState ( {propSentToChild})
}
What happens is the child component changes state, then render occurs, then the callback occurs, prompting another render.
I want to either
A. change child state, then have the callback called before render
or
B. update child state, then ignore the parents passed props
I can do B, but I'm unsure whether it is proper form to basically make the child's version of the shared state the source of truth
I think you're kind of close. What you really want to do is pass the state to the parent, handle setting the state there, and let the new state trickle down to your child component via props. This is a fairly common pattern for react.
class Parent extends React.Component {
constructor() {
this.state = { foo: "bar", bing: "baz" }
}
stateUpdater(newState) {
this.setState({ ...this.state, ...newState });
}
render() {
return <Child
prop1={this.state.foo}
prop2={this.state.baz}
stateUpdater={this.stateUpdater}
/>
}
}
class Child extends React.Component {
handleClick = () => {
this.props.stateUpdater({ foo: 'bazaar' });
}
render() {
return <div>
The foo is {this.props.foo} and the baz is {this.props.baz}.
<button onClick={this.handleClick}>Click Me!</button>
</div>
}
}

useState with arrays not rerendering

I am facing issue while using useState hook with array. I checked various resources on stackoverflow, but could not fix it.
my basic code snippet looks like :
const [users, setUsers] = useState([]);
function addNewContact(user) {
const newUsers = [...users,user];
console.log(newUsers);
setUsers(newUsers);
}
<CardContainer users={users}></CardContainer>
class CardContainer extends Component {
constructor(props) {
super(props);
console.log("this -> ");
console.log(this.props.users);
this.state = {
users: this.props.users
}
}
render() {
//console.log(this.state.users)
return (
<div class="row row-cols-1 row-cols-md-2 g-4">
{
this.state.users.map(user => {
return <Card id={user.phone} title={user.name} email={user.email} phone={user.phone}></Card>
})
}
</div>
)
}
}
export default CardContainer;
I am able to see updated array in the console, but the component using it is not rendering again. Can anyone please help me on this.
The issue is due to you're storing the prop in the state of the child component, which is assigned on component initialization and component initialization/constructor only run one, until its remounted. After that, whenever, the state changes in the parent component, the child component is not re-rendering, because it uses its own state for map.
This below code only runs once on the component initialization.
this.state = {
users: this.props.users
}
In the child component, you can directly use the props and the child component will always re-render on change in the parent component. Instead of this.state.users.map you can directly map the array from props like this this.props.users.map. This way,the component will re-render on state change in the parent compoenent.
As #Junaid said, constructor is only called once before component mounting. If you really need to set a separate state inside the child component, then you can use componentDidUpdate(prevProps) react life cycle method. Make sure to compare previous and current props in order to avoid infinite loop of re-rendering.
componentDidUpdate(prevProps) {
if (this.props.users !== prevProps.users) {
this.setState({ users: this.props.users });
}
};

Why aren't parent Props Equal to Child state when Child state actually reference props from parent

I am passing props from Parent component into Child's state but They are out of sync.
What I tried:
State Updates May Be Asynchronous, I have taken care of that using a call back instead of returning an object.
Objects are passed by reference, but the prop i used is a string.
I am using React 16 and es6 syntax
class Parent extends React.Component {
state = {
isHidden: false
}
render() {
console.log('PROPS from PARENT', this.state.isHidden)
return <div>
<Child isOpen={this.state.isHidden} />
<button onClick={this.toggleModal}>Toggle Modal</button>
</div>
}
toggleModal = () => this.setState(state => ({isHidden: !state.isHidden}))
}
class Child extends React.Component {
state = {
isHidden: this.props.isOpen
}
render() {
console.log('STATE of CHILD:',this.state.isHidden)
return <p hidden={this.state.isHidden}>Hidden:{this.state.isHidden}</p>
}
}
ReactDOM.render(<Parent/>, document.getElementById('app'));
Here a codepen PEN - notice the redered element is supposed to be hidden based on the state(state depends on props from parent)
Use componentWillReceiveProps which call when changes in props occur.
class Child extends React.Component {
state = {
isHidden: this.props.isOpen
}
componentWillReceiveProps(props) {
if (props.isOpen != this.state.isHidden)
this.setState({
isHidden: props.isOpen
})
}
render() {
console.log('STATE of CHILD:', this.state.isHidden)
return <p hidden = {
this.state.isHidden
} > Hidden: {
this.state.isHidden
} < /p>
}
}
If you remove the state definition from the child, which is not needed, and use only the props that is passed from the parent, I believe that the behaviour of the child will make sense.
If you want to use state in the child, the constructor setting is not enough, you need the set the child state when props changes.
Console.log is asynchronous, so you cannot rely on it here.
Your Child's state does not change with prop change since component does not know anything about state change in your constructor. This is a common pitfall when you depend on your props to construct your local state. You can use componentWillReceiveProps as shown in #Nishant Dixit's answer. But, starting with React 16.3 we have getDerivedStateFromProps function (lifecylce method) for this.
static getDerivedStateFromProps( props, state) {
if( props.isOpen === state.isHidden) {
return null;
}
return {
isHidden: props.isOpen,
}
}
Here, we are comparing our prop and state, if there is a change we are returning desired state. No need to use this.setState.
Related API change blog post including async rendering:
https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html
Although the other answers will make the code work, there is actually a more elegant solution :)
Your child component does not need any state as the state is managed by the Parent (which manages the isHidden property and passes it to the child). So the child component should only care about props.
Try writing the component like this and I believe it should work:
class Child extends React.Component {
render() {
return <p hidden={this.props.isHidden}>Hidden:{this.props.isHidden}</p>
}
}
Dan Abramov who works on the React team tweeted about this problem - essentially saying that you should think hard about whether you can just use props before using state in a component
https://twitter.com/dan_abramov/status/979520339968516097?lang=en

How to force dom re render in react

I am trying to force a child component to re-render. I have tried this.forceUpdate();, but it does not work. I put console.log statements in my <PostList /> component, and none of them are ever called--not componentDidMount, nor componentWillMount, componentWillReceiveProps, none of them. It's as if the <PostList /> component is never initialized. I am sure it is though, because I know for a fact items.count retrieves my items. Here is my render method:
render() {
const items = this.state.posts;
const postList = items.count > 0 ? (<PostList comingFromSearch={true} xyz={items} />) : (<div></div>)
const navBar = <NavigationBar />
return (
<div><br/>{navBar}
<div className="container">
<h3>Search Results for {this.state.searchTerm}</h3>
<div className="row">
<div className="col-x-12">{postList}</div>
</div>
</div>
</div>
)
}
And here is my api call:
retrieveSearch(term) {
Helpers.searchWithTerm(term).then((terms) => {
const postsWithTermsInTitle = terms.titleResults
this.setState({posts: postsWithTermsInTitle})
this.forceUpdate();
}).catch((error) => {
console.log("error searching: " + error);
})
}
I should note, on my previous page, i had another ` component, and maybe react is using that one instead of this one? I want to force it to use this instance.
If this.forceUpdate(); does not make the whole DOM re-render, how can I do that?
thanks
your PostList and NavigationBar Components might not update because they only update when their props are changed (shallow compare).
PostList might not update when changing the inner content of the array, because the component will shallow compare the new state with the previous one. Shallow comparing an array will basically checked against its length property. which does not change in this case.
Quick Solution
Sometimes you need to update a List, without changing any of its props or the length of the list. To achieve this, just pass a prop to the component and keep incrementing it instead of calling force update.
retrieveSearch(term) {
Helpers.searchWithTerm(term).then((terms) => {
const postsWithTermsInTitle = terms.titleResults
this.setState((curState) => ({posts: postsWithTermsInTitle, refreshCycle: curState.refreshCycle+1}))
this.forceUpdate();
}).catch((error) => {
console.log("error searching: " + error);
})
}
render() {
...
<PostList
...
refreshCycle={this.state.refreshCycle}
/>
...
}
Right solution
The right solution is to provide an itemRenderer which you is a function that returns the an individual item from the list. This function is passed as a prop to the component.
This way you have control over how the items inside the list will appear, also changes inside the itemRenderer function will cause a component update.
itemRenderer(itemIndex) {
return <div>{this.props.item[itemIndex]}</div>;
}
render() {
...
<PostList
itemRenderer={this.itemRenderer.bind(this)}
itemsLength={items.length}
/>
...
}
The itemRenderer will be called inside the PostList in a loop (of length itemsLength). each loop will be passed the index of the current iteration, so you can know which item of the list to return from the function.
This way you can also make your list more scalable and more accommodating.
You can check an implementation of such solution on a list package like this one: https://www.npmjs.com/package/react-list
You can force a re-render of a component and all its children by changing the state of the component. In the constructor add a state object:
constructor(props) {
super(props)
this.state = {
someComponentState: 'someValue'
}
}
Now whenever you do:
this.setState(someComponentState, 'newValue')
It will re-render the component and all its children.
This of course assumes your component is a class based component, not a functional component. However, if your component is a functional component you can easily transform it to a class based component as follows:
class ComponentName {
constructor() {
// constructor code
}
render() {
// render code
}
}
export default ComponentName
Understand that componenet level state is not the same as redux state but is exposed only inside the component itself.

How do I change state on a component two levels up?

I'm learning React and I've started extracting components. I understand how to bind an event like onClick on a child component, but what about a grandchild?
Example:
I have a List component. It has ListRow children. Within each ListRow child, I have a button component for deleting that particular row from the parent (List). My thoughts are that thedeleteRowclick handler would be on theListcomponent so that I could then set the state. However, I can't seem to find a way to call the grandparent's (List`) eventHandler.
<List /> // has event handler as well as state for the list items
<ListRow />
<DeleteButton /> //when clicking this i want to delete parent <ListRow />
Am I just supposed to pass the onclick down the chain?
When creating components you have to decide whether or not a component is a functional component or a component that needs to manage state. Here is an example where you have a "Grandparent" that passes down functionality to it's child and the child to it's child. If a component does not need to manage state you make it a "functional component" like the "Parent" and "Child" examples below:
class GrandParent extends Component {
handleState = (obj) => {
this.setState(obj);
}
render() {
return (
<Parent handleState={this.handleState} />
);
}
}
function Parent(props) {
render() {
return (
<Child handleState={props.handleState} />
);
}
}
function Child(props) {
render() {
return (
...
);
}
}
You want to pass it down along and wherever you need to call the function you can use it as props.handleState() from whatever component that you send it to.
You could try something this:
// grandparent function that goes into parent
heirloom()
{
console.log("grandparent says hi");
//something happens
}
// everything else (put into all subsequent children)
heirloom()
{
this.props.heirloom();
}
<List heirloom="this.heirloom">
<ListRow heirloom="this.heirloom" />
<DeleteButton onClick="this.heirloom"/>
My syntax may be off and this may or may not work, I haven't had the chance to play around with React for a while. If it does, great! If it doesn't, let's just hope someone with a better answer comes along ^^

Resources