React state updated in child component but not in parent - reactjs

I am aware that changing the state is asynchronous and requires using the previous state.
But lately I have encountered a strange problem. After the state is modified the change is visible in the child (grand-child in fact) component but during callback to the parent the change is not visible there.
Here is the pseudocode
const Parent = () => {
const [items, setItems] = React.useState([]);
... some method
const changeState = (...) => {
setItems( items => [...items, {id: 100, value: "Test"}]);
}
const callback = (id) => {
const v = items.find( i => i.id == id );
// ERROR: v is undefined
}
return(<Child items={items} callback={callback} />);
}
const Child = ({items, callback) => {
...
const onClick = () => {
callback(100);
}
...
}
The scenario is as follows:
The changeState method is called in Parent
the Child is rendered with the correct data
when the Item is rendered it has a onClick method with it's id as a param
onClick in Child is called
callback is called in the parent with id (which had been passed from Parent to Child)
there is an error because ... id does not exist in the Parent's state.
Any ideas where to look for the source of the problem?

I fully agree with CHess
Your code semms to be correct in my eyes. And as you pointed out, the parent component must have contained the valid itemsstate at some point. Otherwise it could not update it in the child component.
One thing I thought of:
Could it be that the setItems function is called from somewhere else? This could explain the strange behaviour.
I think a more complete code example could help getting to the bottom of this.

Related

onCallback React Hook - best practice query

This is more of a best practice question than anything. But I'm wondering if any inline functions within a React component should typically be wrapped with an onCallback for performance? Under what circumstances would I not wrap a function like this with onCallback?
For example:
const ToolSearch = (props: ToolsSearchProps) => {
const handleOnClick = () => {
alert('do something');
}
return (
<NoCardOverflow>
<ToolSearchCollapse openState={filtersOpen} onClick={handleOnClick} />
</NoCardOverflow>
);
};
In this example should I be doing this:
const ToolSearch = (props: ToolsSearchProps) => {
const handleOnClick = useCallback(() => {
alert('do something');
},[]);
return (
<NoCardOverflow>
<ToolSearchCollapse openState={filtersOpen} onClick={handleOnClick} />
</NoCardOverflow>
);
};
I will try to explain the useCallBack with an example. We all know the definition of useCallBack, but when to use is the trick part here. So, let me take an example.
const RenderText = React.memo(({ text }) => {
console.log(`Render ${text}`);
return (<div>{text}</div>);
});
const App = () => {
const [count, updateCount] = React.useState(0);
const [lists, updateLists] = React.useState(["list 1"]);
const addListItem = () => {
updateLists([...lists, "random"]);
};
return (
<div>
{`Current Count is ${count}`}
<button onClick={() => updateCount((prev) => prev + 1)}>Update Count</button>
{lists.map((item: string, index: number) => (
<RenderText key={index} text={item} />
))}
</div>
);
};
ReactDOM.render(<App />, document.querySelector("#app"));
jsFiddle: https://jsfiddle.net/enyctuLp/1/
In the above, there are two states.
count - Number
lists - Array
The RenderText just renders the text passed to it. (which is the item in the list). If you click on the Update Count button, the RenderText will not re-render because it is independent from the main component (App) and during the updateCount, only the App component will re-render, since it needs to update the count value.
Now, pass the addListItem into RenderText component and click on the Update Count button and see what happens.
jsFiddle: https://jsfiddle.net/enyctuLp/2/
You can see that the RenderText will re-render even though there is no change in the list array and this is BECAUSE :
when the count is updated, the App will re-render
which, will re-render the addListItem
which causes the RenderText to re-render.
To avoid this, we should use useCallBack hook.
jsFiddle: https://jsfiddle.net/enyctuLp/4/
Now, the addListItem function has been memoized and it will only change when the dependency are changed.
ToolSearchCollapse is your child component, and so wrapping your handleClick function makes sense. Coz every time the parent component re-renders a new reference of handleclick will be passed to ToolSearchCollapse, and your child will re-render every time a parent state changes, (from states that aren't passed to the child also). Using useCallback will not allow creating new references, and thus you can control when your child should render.
If you don't pass this fn to the child, I don't see any reason to use useCallback.

how to trigger an alert on state change

I am passing a variable data that is subjected to change from a child component. In the parent component, I am receiving the variable data like this:
<ChildComponent sendDataToParent={sendDataToParent}/>;
let passedVal;
const sendDataToParent = (val) => {
passedVal = val;
};
So I want an alert to be triggered anytime the passed variable changes.
So I bind the passed data to a state, and then use useEffect hook. But the useEffect does not trigger anytime the passedValue changes. Though if click the button it shows the changed passed data.
export default function ParentComponet() {
const [isSelected, setIsSelected] = useState(passedVal);
const showPassedData = () => { // This triggers on click of a button
setIsSelected(passedVal);
alert(passedVal)
};
useEffect(() => { // It does not trigger after passedVal changes
setIsSelected(passedVal);
alert(passedVal)
}, [passedVal])
return (
<div>
<Button
onClick={showPassedData}
>
Show Passed Data
</Button>
</div>
);
}
That does not work because React can't detect changes of a normal variable using useEffect. It can only detect state/props changes. In your case passedVal is just a variable, even if you change it, it will not trigger useEffect.
So instead of this
let passedVal;
const sendDataToParent = (val) => {
passedVal = val;
};
you can do this:
const [passedVal, setPassedVal] = useState();
<ChildComponent sendDataToParent={setPassedVal}/>;
sendDataToParent does not really send data to parent component. If you want to do that you'd need to use smth like Redux or React's context. It's a bit hard to see the complete structure of your components from the code you provided but I think an easier way would be to pass a callback from the parent component to the child, that will be invoked when value changes.
export default function ParentComponent() {
const onDataChange = (val) => {
alert(val)
}
return (
// ...
<ChildComponent onDataChange={onDataChange}/>
)
}

React change state from another component without passing setState method

I have a custom table component and it has two props: items and columns. I want to implement a sorting feature. Sorting is not a big deal. I am sorting the items inside the table component but when items are sorted, also the state that stores the items must be changed which is outside of my table component. I don't want to pass setState method cause of my component is generic. It would be very useless if I pass setState method everywhere.
How do popular libraries solve this problem without need of pass a state-altering method? Do they copy the state to an internal state and then modify it or something? I hope I could explain my problem.
It might help to think in terms of controlled vs uncontrolled components. You may be familiar with this from core elements like <input>s, where you can either pass in a defaultValue prop, and let the input handle everything ("uncontrolled"), or you can pass in value and onChange and handle things yourself ("controlled"). You can design your table component as either a controlled component or uncontrolled component too.
Doing it as an uncontrolled component, you might pass in a prop that sets the initial sorting, but afterwards everything is handled by the table. The parent won't get notified and won't update its state:
const Parent = () => {
const [items, setItems] = useState(/* some array */);
return <MyTable items={items} defaultSort="asc" />
}
const MyTable = ({ items, defaultSort }) => {
const [sort, setSort] = useState(defaultSort ?? 'asc');
const sortedItems = useMemo(() => {
if (sort === 'asc') {
return [...items].sort(/* insert sort function here */)
} else {
return [...items].sort(/* insert sort function here */)
}
}, [items, sort]);
return (
<>
<button onClick={() => setSort(sort === 'asc' ? 'dsc' : 'asc')}>
Change Sort
</button>
{sortedItems.map(() => /* etc */)}
</>
)
}
If instead you do a controlled component, then the parent is in charge of the state, and the child just notifies the parent of relevant changes
const Parent = () => {
const [items, setItems] = useState(/* some array */);
const [sort, setSort] = useState('asc');
const sortedItems = useMemo(() => {
if (sort === 'asc') {
return [...items].sort(/* insert sort function here */)
} else {
return [...items].sort(/* insert sort function here */)
}
}, [items, sort]);
return <MyTable items={sortedItems} onSortToggled={() => setSort(sort === 'asc' ? 'dsc' : 'asc')} />
}
const MyTable = ({ items, onSortToggled}) => {
return (
<>
<button onClick={onSortToggled}>
Change Sort
</button>
{items.map(() => /* etc */)}
</>
)
}
If you add in some extra code to check for undefineds, it's possible to make your table support both controlled and uncontrolled modes, based on which set of props it is passed. But it should just be one or the other; you shouldn't try to have both components be managing state simultaneously, as this just adds opportunities for the states to get out of sync and bugs to be introduced.
the state that stores the items must be changed which is outside of my table component
If this is one of your requirements, then you're basically doing the controlled component version, and thus you must accept a function from the parent component which describes how to do so. The parent component is the only one who knows what state they have and how to update it.

React access state after render with functional components

I'm a bit of a newbie with React functional components, I have a child and parent components with some state that gets updated with useEffect, which state apparently resets back to its initial values after render.
Parent has a list of users it passes to its child:
Parent:
const Parent = () => {
const [users, setUsers] = useState([])
const getUsers = () => {
setUsers(["pedro", "juan"])
}
useEffect(() => {
getUsers()
}, []);
return <div>
<Child users={users} />
}
Child:
const Child = () => {
const [users, setUsers] = useState([])
useEffect(() => {
setUsers(props.users)
}, [[...props.users]]);
}
If I for any reason try to access state (users) from either my child or parent components I get my initial value, which is an empty array, not my updated value from getUsers(), generally with a Parent Class component I'd have no trouble accessing that info, but it seems like functional components behave diffently? or is it caused by the useEffect? generally I'd use a class component for the parent but some libraries I use rely on Hooks, so I'm kind of forced to use functional components.
There are a couple of mistakes the way you are trying to access data and passing that data.
You should adapt to the concept of lifting up state, which means that if you have users being passed to your Child component, make sure that all the logic regarding adding or removing or updating the users stays inside the Parent function and the Child component is responsible only for displaying the list of users.
Here is a code sandbox inspired by the code you have shared above. I hope this answers your question, do let me know if otherwise.
Also sharing the code below.
import React, { useState } from "react";
export default function Parent() {
const [users, setUsers] = useState([]);
let [userNumber, setUserNumber] = useState(1); // only for distinctive users,
//can be ignored for regular implementation
const setRandomUsers = () => {
let newUser = {};
newUser.name = `user ${userNumber}`;
setUsers([...users, newUser]);
setUserNumber(++userNumber);
};
return (
<div className="App">
<button onClick={setRandomUsers}>Add New User</button>
<Child users={users} />
</div>
);
}
const Child = props => {
return (
props.users &&
props.users.map((user, index) => <div key={index}>{user.name}</div>)
);
};
it doesnt make sense to me that at Child you do const [users, setUsers] = useState([]). why dont you pass down users and setUsers through props? your child's setUser will update only its local users' state value, not parent's. overall, duplicating parent state all around its children is not good, you better consume it and updating it through props.
also, once you do [[...props.users]], you are creating a new array reference every update, so your function at useEffect will run on every update no matter what. useEffect doesnt do deep compare for arrays/objects. you better do [props.users].

React Hooks: Setting parent state before setting child state

I've observed an interesting nuance of useState that I'm wanting to understand more deeply. The scenario:
Parent component owns some some local state created via useState
Parent passes an event handler function as a prop to a child component. This handler sets state in the parent.
Child component also has local state created by useState
When a certain event happens in the child, both the callback prop and the local state setter are called
My observation is that if the callback is run first, the a re-render of the parent is triggered which clobbers setting the child state. If I reverse the order things "seem" to be fine.
Contrived example:
Parent Component
const Parent = () => {
const [, setParentData] = useState();
function onData(data: string) {
setParentData(data);
}
return (
<Child
onData={onData}
/>
)
};
Child Component
const Child = ({
onData
}) => {
const [childData, setChildData] = useState();
function onClick() {
const data = Date.now;
onData(data);
// this call gets clobbered if onData is called first
// but is fine if I swap them
setChildData(data);
}
return (
<div onClick={onClick}>{ childData }</div>
);
};
Anyone know if ensuring child state setters are called before the parent callback is sufficient to keep all state updates, or are there other async types of things here where I may still have a race condition of some sort?

Resources