Search code examples
reactjstypescriptreact-nativereact-hooksstate

How to properly update a state variable when focusing on a screen in React Native?


I have a state variable in my React Native Project. It needs to be updated when a specific function is called. However, the setState method being asyncronous results in my variable being updated after it should.

What I need to happen is:

  • The screen starts with said variable as an empty array;
  • When the screen is focused, it fills the array, and creates components on screen according to the array;

What is happening is:

  • The screen starts with said variable as an empty array;
  • When the screen is focused, I try to fill the array using setState;
  • When trying to create the components, because the array is empty, nothing happens;
  • The array is filled after the page is already built.

To illustratehow this is being managed, this is what my code looks like:

const MyComponent: FC<MyComponentProps> = ({/*my component props*/}) => {
  const [rows, setRows] = useState<Array<boolean>>([]);

  useEffect(() => {
    console.log('rows on useEffect: ', rows);
  }, [rows]);

  async function OnFocus(): Promise<void> {

    await GetApplication();
  }

  async function GetApplication(): Promise<void> {
      const _state: ApplicationParameters = await ctrl.getApplicationParameters(
        ctrl.ctrl.getDevice()
      );
      
      console.log('_state.rows: ', _state.rows);
      console.log('rows before setRows()', rows);
      setRows(_state.rows!);

      console.log('rows after setRows()', rows);
    }

    return(
        <RowBars theme={theme} rows={rows} />
    )
}
 

The log for the above code shows:

LOG  _state.rows:  [true, true, true, true, true, true, true, true, true, true, true]
LOG  rows before setRows() []
LOG  rows after setRows() []
LOG  rows on useEffect() [true, true, true, true, true, true, true, true, true, true, true]
LOG  rows on useEffect() [true, true, true, true, true, true, true, true, true, true, true]

If something triggers the state to update later, the array appears filled, but nothing is built.

The component where my variable is later used is as follows:

interface RowBarsProps {
  theme: AppColors;
  rows: Array<boolean>;
}

export const RowBars: FC<RowBarsProps> = ({ theme, rows }: RowBarsProps) => {
  const [Rows] = useState<number>(rows.length);

  const _rows = [];
  for (let i = 0; i < Rows; i++) {
    _rows.push(<RowBar theme={theme} isOn={rows[i]} />);
  }

  return <RowToRowLiteBars>{_rows}</RowToRowLiteBars>;
};

I tried:

  • Updating rows on useEffect. However, I don't have a reliable variable to watch on the dependency array of useEffect;
  • Initializing rows from an inherited props variable. However, because when rows is updated the props variable hasn't been yet updated, it tries to set rows from an undefined value.

Solution

  • Looking closely on my code I found out that the problem was on the child component. What was happening was that it wasn't built for perceiving the changes in the parent component.

    What I did was to create a useEffect() to watch for changes in the coming rows.

    Don't know if it is the best practice, but it worked pretty well.

    export const RowBars: FC<RowBarsProps> = ({ theme, rows }: RowBarsProps) => {
      const [rowBars, setRowBars] = useState<RowBarsState>([]);
    
      // Updates the row bars according to `rows` current state
      useEffect(() => {
        setRowBars(() => {
          const newRowBars = rows.map((row) => <RowBar theme={theme} isOn={row} />);
          return newRowBars;
        });
      }, [rows, theme]);
    
      return <RowToRowLiteBars>{rowBars}</RowToRowLiteBars>;
    };