Monday, May 20, 2024
 Popular · Latest · Hot · Upcoming
7
rated 0 times [  10] [ 3]  / answers: 1 / hits: 8377  / 4 Years ago, fri, june 26, 2020, 12:00:00

I'm trying to add a key-value pair to an object using useState inside map function. However, the spread operation in setContainer({...container, [data.pk]: data.name}); seems to be ineffective.


The code snippet below exactly illustrates the problem that I'm trying to solve. In fact, in my original code container is declared at another ContextProvider component and referenced using useContext, but I replaced it with useState for simpliticy.


I guess it has to do with the asynchronous nature of setState hook, but I don't fully understand what's going on under the hood.


Please let me know what is causing this issue and how to yield the outcome I expected.
Thank you.




const {useState, useEffect} = React;

const myData = [{pk: 1, name:'apple'},
{pk: 2, name:'banana'},
{pk: 3, name:'citrus'},
]

const Example = () => {
const [container, setContainer] = useState({});

useEffect(()=>{
myData.map(data=>{
console.log(`During this iteration, a key-value pair (key ${data.pk} and value '${data.name}') should be added to container`);
setContainer({...container, [data.pk]: data.name});
})
}, [])

return (
<div>
<div>result I expected: {{'1': 'apple', '2': 'banana','3': 'citrus'}}</div>
<div>result: {JSON.stringify(container)}</div>
</div>
);
};

// Render it
ReactDOM.render(
<Example />,
document.getElementById(react)
);

<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=react></div>




Update


The main reason I wanted to use useState in loops was to use the state as a container of refs of components. However, I found it inefficient to do so, because it caused too many rerenders and was hard to determine when the loop finished. After a lot of searching, I ended up declaring useRef({}) in the Context Provider and use it as a refs container (which seems to fit the actual purpose it was created I guess). The refs container can be used as following:


const refsContainer = useRef({});

import React from 'react';

const Notes = () => {
const setRef = (noteId) => (element) => {
refsContainer.current[noteId] = element
};
return (
<Masonry>
{pubData?.map(publication => {
return <PublicationCard innerRef={setRef(publication.noteId)}/>
})}
</Masonry>
);
};

export default Notes;

const pubCardRef = refsContainer.current[rank.note.noteId];
window.scrollTo({top: pubCardRef?.offsetTop - 80, behavior: 'smooth'})

More From » reactjs

 Answers
3

Here's what's going on in your component step by step.



  1. container and setContainer are initialized using the useState hook

  2. the effect in which you map over myData is registered, also it will only run once in the entire life of the component because of the empty array [] you passed as the second argument

  3. the callback you registered in the effect is run. this is what it could look like if you console.log'd the container as well as the key-value pair


(key 1 value 'apple'); container {}
(key 2 value 'banana'); container {}
(key 3 value 'citrus'); container {}


Wait what, shouldn't container be updating on each iteration?



Well yes, but not in the way you expect. The container referenced in useEffect retains its original {} value in each iteration of myData.map. What's effectively going on then is


setContainer({ ...{}, 1: 'apple ' }) // RERENDER
setContainer({ ...{}, 2: 'banana' }) // RERENDER
setContainer({ ...{}, 3: 'citrus' }) // RERENDER

citrus is the final display since it was the last rerender.


Here's a way you can achieve your desired result




const {useState, useEffect} = React;

const myData = [{pk: 1, name:'apple'},
{pk: 2, name:'banana'},
{pk: 3, name:'citrus'},
]

const Example = () => {
const [container, setContainer] = useState({});

useEffect(()=>{
setContainer(myData.reduce((obj, data) => ({ ...obj, [data.pk]: data.name }), {}))
}, [])

return (
<div>
<div>result I expected: {{'1': 'apple', '2': 'banana','3': 'citrus'}}</div>
<div>result: {JSON.stringify(container)}</div>
</div>
);
};

// Render it
ReactDOM.render(
<Example />,
document.getElementById(react)
);

<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=react></div>




[#3366] Tuesday, June 23, 2020, 4 Years  [reply] [flag answer]
Only authorized users can answer the question. Please sign in first, or register a free account.
calicinthias

Total Points: 447
Total Questions: 101
Total Answers: 118

Location: Botswana
Member since Sat, Dec 31, 2022
1 Year ago
calicinthias questions
Sun, Jan 2, 22, 00:00, 2 Years ago
Wed, Jan 13, 21, 00:00, 3 Years ago
Mon, Aug 10, 20, 00:00, 4 Years ago
Fri, May 15, 20, 00:00, 4 Years ago
;