1

All the following code is in a function react component (using FluentUI) with the following state -

const [columns, setColumns] = React.useState<IColumn[]>([]);

I am assigning a method to an object property inside a useEffect hook that's meant to run only once as shown below -

React.useEffect(
     () => {
        .....
        .....
        column.onColumnClick = (event, selectedColumn) => setSortingState(selectedColumn);
     },
[]);

The setSortingState function is defined above the useEffect hook as follows -

const setSortingState = (selectedColumn: IColumn) => {
      const newColumns: IColumn[] = columns.slice();
      ..........
      ..........
      setColumns(newColumns);
 }

Note that whenever the column is clicked, its state is well-defined. I have verified this using breakpoints in developer tools. However, after clicking the column, when the setSortingState method is triggered, the state becomes empty (i.e columns = []). I don't know why this is happening. Please help. Ideally, it should pick up the latest state value. I can't comprehend why it's resetting the state.

EDIT: Will the following work ? I am using 2 states inside the setSortingState method which I need to update -

const setSortingState = (selectedColumn: IColumn) => {
  setColumns(columns => {
    const newColumns: IColumn[] = columns.slice();
    const newColumnState = columnSortState.slice();
    ..........
    ..........
    return newColumns;
  });
  setColumnState(newColumnState);
}
coolest-duck
  • 105
  • 1
  • 1
  • 10

1 Answers1

1

It's an issue of stale state enclosure. The setSortingState is using a columns state value from the initial render cycle closed over in callback scope. The state isn't "reset" per se, it's just that the code isn't using the latest state to update from.

Use a functional state update for each state to update from the previous state instead of whatever is closed over in callback scope. In a functional state update the latest state value is passed to the provided updater function.

Example:

const setSortingState = (selectedColumn: IColumn) => {
  setColumns(columns => {
    const newColumns: IColumn[] = columns.slice();
    ...
    ...
    return newColumns;
  });
  setColumnState(columnsState => {
    const newColumnState = columnSortState.slice();
    ...
    return newColumnState;
  });
}
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • @Shreyansh Generally, yes. Are these two independent states? Do you have an example or is this a side-question? – Drew Reese Apr 05 '22 at 06:59
  • I have edited the question, can you pls check that ? – coolest-duck Apr 05 '22 at 07:02
  • @Shreyansh Is `columnSortState` the separate state? Is there anything between these two states that is dependent? – Drew Reese Apr 05 '22 at 07:03
  • It's a seperate state (independent)...it's also well-defined before the function executes, but like in case of `columns`, it is not taking the latest state value inside the function. – coolest-duck Apr 05 '22 at 07:06
  • @Shreyansh In this case a separate state update should be sufficient. – Drew Reese Apr 05 '22 at 07:07
  • the way I have edited the question, will it work ? Will the `columnSortState` be updated with the new value of `newColumnState` ? If not, can you tell me what should be the code ? – coolest-duck Apr 05 '22 at 07:09
  • Also, @Drew, in your answer, how will `columns` get the latest state value ? – coolest-duck Apr 05 '22 at 07:10
  • @Shreyansh They *way* you've written it in your question? No, I suspect it won't work since it doesn't look like `columnSortState` would be in scope in the updater function for the `columns` state. Use two separate state updates, one for each state you are updating. – Drew Reese Apr 05 '22 at 07:11
  • So, 2 state updates in the same function right ? Basically like you said for `setColumns`, I should have for `setColumnSortState` , within the same method? – coolest-duck Apr 05 '22 at 07:13
  • @Shreyansh I see, when you use a functional update the latest state value is passed to the function, so `column` will be the latest state for *that* function, and `columnState` the latest for the other. https://reactjs.org/docs/hooks-reference.html#functional-updates – Drew Reese Apr 05 '22 at 07:14
  • Yes, 2 state updates in the `setSortingState` function. I've updated my answer. – Drew Reese Apr 05 '22 at 07:14
  • Okay, and @Drew, can you pls explain how will your code/answer take the latest values for the state variables ? I am still confused there. – coolest-duck Apr 05 '22 at 07:17
  • @Shreyansh The latest state is passed to the function by React. It's the link I added in my answer and a couple comments up regarding functional updates. – Drew Reese Apr 05 '22 at 07:18
  • I did it your way, but still the older value seems to be taken. I applied breakpoints to check that in `setColumns(columns => {...})`, the `columns` (on left side of -arrow) seem to be empty when checked using breakpoints. – coolest-duck Apr 05 '22 at 07:26
  • @Shreyansh Would it be easy for you to create a *running* [codesandbox](https://codesandbox.io/) demo of the code you are working with that is having some trouble updating that we could then inspect and debug live? It's getting late for me but if you can, can you create one and share a link to it here, and I'll check it out when I can? – Drew Reese Apr 05 '22 at 07:32
  • 1
    @Shreyansh Ok, great! Glad to help. I'm going to clean up a bunch of my half of this conversation in the comments. Cheers and good luck! – Drew Reese Apr 05 '22 at 07:38
  • I would like to chat with you sometime. Is there any place where we can do that like linkedIn, facebook etc ? – coolest-duck Apr 05 '22 at 07:49
  • @Shreyansh You can shoot me a DM on linkedin. – Drew Reese Apr 05 '22 at 07:54
  • Have sent you a connection request. Once you accept it, we can chat. – coolest-duck Apr 05 '22 at 08:00