Reputation: 73
I have a parent and a child components.
Parent component has state variable that I want to access.
I pass a function to child component and call it there onClick.
However, the value of the state variable is out of date - not the current value.
function Parent(props) {
const [items, setItems] = useState([]);
function getItems () {
console.log(items); // always prints "[]"
}
useEffect(() => {
thirdPartyApiCall().then((fetchedItems) =>
let newItems = fetchedItems.map(item => <Child id={item.id} getItems={getItems}/>)
setItems(newItems); // populates items array. "{items}" elements correctly displayed on web page
);
}, []);
}
function Child(props) {
return <button onClick={props.getItems}>{props.id}</button>
}
Upvotes: 2
Views: 1432
Reputation: 125
You might want to look at how javascript closure works.
Will give a basic idea of what's happening.
useEffect
would only run once, due to the dependency. So when the child components were rendered items
was an empty array([]
). Your child components would always have this instance of items
as useEffect
did not get called with updated items
.
Hope this helped.
EDIT:1
You might want your parent component to look like this.
function Parent(props) {
const [items, setItems] = useState([]);
function getItems () {
console.log(items); // always prints "[]"
}
useEffect(() => {
thirdPartyApiCall().then((fetchedItems) =>
setItems(newItems);
);
}, []);
return newItems.map(item => <Child key={item.id} id={item.id} getItems={getItems}/>)
}
Upvotes: 3
Reputation: 18493
Well, yes, of course clicking on the button logs []
. In this:
<Child id={item.id} getItems={getItems}/>
getItem
gets evaluated as a function that logs the items and the items are []
, so []
is logged. The reason why it doesn't logs the new items is because, as your code is being written, React cannot really know when to update the component, because no variable is used in the render phase.
This is generally a bad practice to use JSX elements outside of Render because they're not really normal JS variables with predictable behavior. The fact that it compiles is more a hack than a feature.
Keep vanilla variables in the state, and let the components do the JSX rendering. This approach is slightly different than yours, but works as expected, as clicking on either button logs all the items:
const fakeApiCall = () => Promise.resolve([
{id: 10},
{id: 20},
{id: 30},
]);
const Parent = () => {
const [items, setItems] = React.useState([]);
React.useEffect(() => {
fakeApiCall().then(setItems)
})
const getItems = () => console.log(items);
return (
<div>
{items.map(item => (
<Child
id={item.id}
key={item.id}
getItems={getItems}/>
))}
</div>
)
}
const Child = ({id, getItems}) => (<button onClick={getItems}>{id}</button>);
window.onload = () => ReactDOM.render(<Parent/>, document.querySelector('#root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Upvotes: 2