0

I have a state named questions In a React component.

const [questions, setQuestions] = useState([]);

and a useEffect method that runs only when the component is rendered the first time.

useEffect(() => {
  let questionsArray = [];
  //some work is done here to populate the questionsArray
  setQuestions(questionsArray);
},[]);

I have a useCallback method that is created when the component is rendered for the first time.

const updateQuestion = useCallback(() =>{
  let new_questions = [...questions];
  //do some work here
},[]);

The problem is that questions state inside the updateQuestion callback is empty because when the component is rendered the first time, the questions state is empty, and the updateQuestion callback uses the stale questions state for some reason. However, the useEffect populates the questions state array, and the questions state inside updateQuestion callback is not up-to-date. I solved this problem by adding questions.length to the dependencies array of useCallback like the following

const updateQuestion = useCallback((index) =>{
  let new_questions = [...questions];
  //do some work here
},[questions.length]);

So, when the questions state is populated with new data in the useEffect, the updateQuestion callback is re-created, and the useCallback will have up-to-date data in questions state. But, are there any other ways that I can get the up-to-date questions state inside the useCallback without re-creating the callback?

Each item in questions array is used to render a child components named QuestionCard

questions.map((question, index)=>{
        return  (
            <li className="list-group-item border-0" key={question.id}>
               <QuestionCard props={question} index={index} updateQuestion={updateQuestion} />
             </li>
             );
        })

The problem is that QuestionCard is a memoized component. If I append an item to the questions state array, the existing items in questions state are the same, they don't need to be rerendered. Now, If i pass questions.array into the dependencies array, the callback is re-created, every child components is rerendered. But, I can't just skip using useCallback and just create a native function. Because, updateQuestion callback is used to update individual question item insides the questions array. If I update a single question, every question will be rerender. If I use useCallback, only the question i am updating is rerendered while i am updating it .

EternalObserver
  • 517
  • 7
  • 19
Rongeegee
  • 866
  • 3
  • 10
  • 30
  • 2
    Why not just `[questions]`? If the content changes but the length doesn't, then you'd have a problem using your approach of `questions.length`. – ggorlen Jan 16 '23 at 03:31
  • because, the questions state is a list of data that will be used to render a list of Child components. If I update any of the data in any item of the questions array, the callback will be recreated, and the callback is passed to each Child components that are memoized. Passing [questions] will rerender every memoized child component. @ggorlen – Rongeegee Jan 16 '23 at 03:34
  • 2
    Isn't that what you want? Otherwise, those child components would have stale questions, no? I think more detail and preferably a [mcve] is needed here. – ggorlen Jan 16 '23 at 03:39
  • @ggorlen I am just wondering if I am able to keep the states inside a useCallback up-to-date without re-creatinng the callback and pass anything to the dependencies array. If I append an item to the questions state array, the existing items in questions state are the same, they don't need to be rerendered. Now, If i pass questions.array into the dependencies array, the callback is re-created, every child components is rerendered – Rongeegee Jan 16 '23 at 03:45
  • If that's the case, just skip using `useCallback` entirely. The function will be created each render and use the current value of any state – Phil Jan 16 '23 at 04:10
  • @Phil I can't. Because, updateQuestion callback is used to update individual question item insides the questions array. If I update a single question, every question will be rerender. If I use useCallback, only the question i am updating is rerendered while i am updating it – Rongeegee Jan 16 '23 at 04:17
  • @Rongeegee then your approach is incorrect. useCallback is not intended for this purpose. Instead of passing the `updateQuestion` as callback, make it local to the Question component and pass the `question` state and its update function as props or context. – EternalObserver Jan 16 '23 at 05:45
  • @EternalObserver are you saying I need to update the question data in each QuestionCard component locally? Well, I also need to update it in the questions state in parent component? Can I update the state of parent component in a child component without passing a callback to a child component? – Rongeegee Jan 16 '23 at 16:16
  • @Rongeegee using memoized child - QuestionCard component with useMemo will not render every question , only new updated question will be re rendered . I created sample codesandbox - https://codesandbox.io/s/goofy-framework-g8z8kq?file=/src/App.js Are you looking for something like this? – Naga Sai A Jan 16 '23 at 23:08
  • @NagaSaiA put a useEffect that has console.log(index) in your questionCard compoent. You will see that the questionCards are rerendered each time you add a new question – Rongeegee Jan 17 '23 at 02:02
  • @Rongeegee you can use Context API for updating state inside parent if the nesting is deep and you don't want to pass callbacks as props. – EternalObserver Jan 17 '23 at 05:16
  • @Rongeegee, i added console log and seeing console log only once for new added question and not displaying multiple console logs - https://codesandbox.io/s/goofy-framework-g8z8kq?file=/src/QuestionCard.js That single re render is required for showing new question – Naga Sai A Jan 18 '23 at 00:05
  • @NagaSaiA inside your addQuestions method, you call setQuestions. doesn't it modify the questions state which in turn triggers the re-creation of updateQuestion callback? – Rongeegee Jan 20 '23 at 14:44
  • It will trigger recreation but it is only for new question card but not for memorized question card component@Rongeegee… if you try to print question card props .. you will not seeing loaded question card props and only new card props will be printed – Naga Sai A Jan 21 '23 at 00:38
  • @NagaSaiA actually in QuestionCard, change remove the dependency array from useEffect and put console.log(index). You will every questionCard has a console.log executed if you add a new questionCard – Rongeegee Jan 21 '23 at 04:46

0 Answers0