Ad

How Pass A Function Into An Own React Context Provider Which Lets You Edit The State?

I have the following code:

import React, { useState } from 'react'

export const NumberContext = React.createContext()

export function NumberProvider({ children }) {
  const [numbers, setNumbers] = useState([])

  function addNumber(number) {
    const copy = [...numbers]
    copy.push(number)
    setNumbers(copy)
  }

  return (
    <NumberContext.Provider value={[numbers, addNumber]}>
      {children}
    </NumberContext.Provider>
  )
}
export function App() {
  return (
      <NumberProvider>
        <DisplayNumbers />
        <AlterNumbers />
      </NumberProvider>
  )
}
export function DisplayNumbers() {
  const [numbers, addNumbers] = useContext(NumberContext)

  return (<p>{numbers}</p>)
}

If I now call the addNumber()function via the Context api within the provider like this

export function AlterNumbers() {
  const [numbers, addNumber] = useContext(NumberContext)

  function alterNumbers(){
    addNumber(1)

    setTimeout(() => {
      addNumber(2)
    }, 3000)
  }

  return (<button onClick={alterNumbers}>)
}

numbersis equal to [2] instead of [1, 2]

The state in my provider function NumberProvider does not update like the one in the NumberContext.Provider. How can I prevent this from happening? How would a design pattern that accomplishes this look like?

Ad

Answer

Because setters of useState are asynchronous, one call overrides the other:

const NumberConsumer = () => {
 const [numbers, addNumber] = useContext(NumberContext);

 addNumber(1);               // Will update numbers to [1] in "future"
 setTimeout(() => {          // Will update numbers to [2] in "future"
    addNumber(2);            // The call within the timeout,
                             // is not aware of the previous state
 }, 3000);

 return <h1>{JSON.stringify(numbers)}</h1>;
};

You should use the functional useState:

setState(prevState => {
  // Object.assign would also work
  return {...prevState, ...updatedValues};
});

As discussed in the comments, you should change the logic to something more robust with useEffect:

export function ToastProvider({ children }) {
  const [toasts, setToasts] = useState([])
  const [key, setKey] = useState(0)

  function showToast(label, message) {
    setKey(key + 1)
    setToasts([
      ...toasts,
      {
        key,
        label,
        message,
      },
    ])
  }

  useEffect(() => {
    setTimeout(() => {
      const copy = [...toasts]
      copy.pop()
      setToasts(copy)
    }, 5000)
  }, toasts);

  return (
    <ToastContext.Provider value={[toasts, showToast]}>
      {children}
    </ToastContext.Provider>
  )
}
Ad
source: stackoverflow.com
Ad