0

I'm working trying to add an object created from data returned in an AXIOS request to state in a React Application.

The object itself appears to be fine and even prints out as expected without errors but when I go to add it to a hook, nothing happens regardless of what code I use. I've tried several combinations and approaches, most of them can be seen in this older post from a couple of years ago which seems to describe the same thing that I'm trying to do. nearly every post or bit of information I've found so far suggests doing something much like this but nothing has done the trick.

I've attached my code below showing the entire function and the hook in question.

    const [queryResultData, setQueryResultData] = useState([])

    function queryResultDataHandler(resultData){
        for(let i=0; i < resultData.length; i++){
            const data=resultData[i];
            let book={
                title: data.title,
                publisher: data.publisher,
                copyright: data.publishedDate
            }
            setQueryResultData([...queryResultData,book]);
        }
    }

I've also included a screenshot showing the contents of each object and the array of data after each attempt to add the data. enter image description here

Any input on why this is happening would be greatly appreciated as I can't see what I'm doing wrong here.


New Update to issue

I've attached all of the relevant code below.

Primary Pane with problematic method.

All other hooks and functions included.

function AddBookAutomaticPane() {
    
    const [queryResultData, setQueryResultData] = useState([])
    const [selectedBookData, setSelectedBookData] = useState([])
    const [currentPane, setCurrentPane] = useState(
        <AddBookAutomaticSearch queryResultDataHandler={queryResultDataHandler} />)

    function switchPaneHandler(paneName){
        if (paneName === "search"){
            setCurrentPane(<AddBookAutomaticSearch queryResultDataHandler={queryResultDataHandler} switchPaneHandler={switchPaneHandler} />);
        } else if (paneName === "displayResults"){
            setCurrentPane(<AddBookAutomaticDisplayResults queryResultsData={queryResultData} setSelectedBookData={setSelectedBookData}/>);
        } else if (paneName === "previewAndfinalize"){
            setCurrentPane(<AddBookAutoPreviewAndFinalize selectedBookData={selectedBookData} />);
        }
    }

    function queryResultDataHandler(resultData){
        const bookData = resultData.map(({title,
               publisher,
               publishedDate
            }) => ({
                title,
                publisher,
                copyright: publishedDate
            }))
        setQueryResultData((oldQueryResultData)=>[...oldQueryResultData,...bookData]);

        switchPaneHandler("displayResults");
    }

    return (
        <div id="AddBookAutomatic">
            {currentPane}
        </div>
    );
}

export default AddBookAutomaticPane;

Pane being switched to.

There's nothing of note inside the component right now besides a header element to show when it's created.

function AddBookAutomaticDisplayResults(props){

    return (
        <section>
            <h1>Display Results</h1>
            {props.queryResultsData.map(() => (
                <BookDetailsPreviewPane />
            ))}
        </section>

    );
};

export default AddBookAutomaticDisplayResults;
TeaDrinker
  • 199
  • 1
  • 11
  • 1
    that does not really make sense. where do you have `console.log`? what does debugging with breakpoints shows? state is not updated at all at next re-render? – skyboyer Aug 25 '23 at 09:38
  • The console.log is located after the setter method. The debugger shows nothing too. I’m really starting to think this is an async issue so I may look into that angle some more. – TeaDrinker Aug 25 '23 at 13:30
  • 1
    right after `set*` call it will not reflect updates in the value. State does not magically mutates by call to `set...`. Only on next re-render `useState` will store into a variable recent value. – skyboyer Aug 25 '23 at 17:18
  • Thanks for the help so far. I clearly don't understand something. I thought switching to a new pane was supposed to cause a re-render and yet when I have the code do just that, nothing seems to have been updated. I've attached all of the relevant code (roughly 40 lines) to the original issue, can you see where I'm going wrong because I simply cannot see it. – TeaDrinker Aug 26 '23 at 06:34
  • 1
    aside of quite untypical "saving components into state", I don't see any issues here. root cause lays elsewhere. – skyboyer Aug 26 '23 at 14:15
  • So then there’s something deeper going on here. That’s good to know (I think) and I’ll start looking elsewhere for a solution. Thank you for your time! – TeaDrinker Aug 27 '23 at 00:25
  • So after re-reading the entire section of React documentation on state and hook methods then reading it again, I found my problem. I re-wrote most of the code I shared earlier as well as a separate class that was being used that I didn't think was relevant (it was) to make better use of the useEffect() method for updating state. Things seem to be working quite well now and I'll type a full solution tomorrow. I'm sure there's a better way to go about it than what I did but I'll save that for the future. Thanks again for your help! It's really appreciated! – TeaDrinker Aug 28 '23 at 01:08
  • good to hear! yes, please, add your solution as answer-to-self, this may help somebody else with the similar issue – skyboyer Aug 28 '23 at 13:30

2 Answers2

3

If you want to update some piece of state and it depends on the current state, you should always use the setState((previousState) => newState) function.

In your case, you need to change:

setQueryResultData([...queryResultData,book]);

to

setQueryResultData((oldQueryResultData) => [...oldQueryResultData, book]);

You can read about this special behavior of the setState function returned by the useState() hook in the Official React Docs under the Parameters > nextState

Oscar Arranz
  • 631
  • 2
  • 9
  • 23
  • Thanks for the reply. I forgot to mention that I already tried this approach and it never worked. It was among the suggestions in the post I mentioned in the original question but neglected to explicitly mention it. Sorry. – TeaDrinker Aug 24 '23 at 18:38
2

Alternatively to @Oscar Arranz's answer, you better transform whole the array and append it once:

const [queryResultData, setQueryResultData] = useState([])

function queryResultDataHandler(resultData){
    const transformedData = resultData.map(({ 
      title, 
      publisher, 
      publishedDate 
    }) => ({
      title,
      publisher,
      copyright: publishedDate
    }))
    setQueryResultData([...queryResultData, ....transformedData]);
}

This will trigger re-rendering just once. While calling setQueryResultData for many times should - at least if I recall correctly - trigger multiple waste re-renders.

PS anyway I'm surprised you did not see any new items added. I expected your code always add single - and last one - item from the payload.

skyboyer
  • 22,209
  • 7
  • 57
  • 64
  • 2
    This! Definitely avoid calling setState multiple times by computing the whole array first. I'd still advise to use a function to set the state and do `setQueryResultData((oldQueryResultData) => [...oldQueryResultData, ...transformedData])` though. – Oscar Arranz Aug 24 '23 at 15:37
  • I didn’t think about it working like that. I’ll see about making the changes. Regarding the strangeness of the issue, I’m probably going to have to post the whole file since it’s starting to seem like there’s something larger going on here. All indications seem to state that what I’m seeing shouldn’t be happening. I have already tried @OscarArranz suggestion and it made no difference. – TeaDrinker Aug 24 '23 at 18:49
  • I just updated the issue with a the results of your proposed fix. The code certainly looks cleaner and runs faster now but the issue itself is still happening. – TeaDrinker Aug 24 '23 at 22:32