skip to Main Content

I have a context like this

ProductProvider.js

import React, { createContext, useMemo } from 'react'

const INITIAL_HANDLER = {
  sortType: null,
  offset: 0
}

export const ProductProviderContext = createContext({
  ...INITIAL_HANDLER,
  setHandler: () => null
})

export default ({ children }) => {
  const [data, setData] = React.useState(INITIAL_HANDLER)

  const setHandler = (value) => {
    setData({ ...data, ...value })
  }

  const contextValue = useMemo(() => ({
    ...data,
    setHandler
  }), [data])

  return (
    <ProductProviderContext.Provider value={contextValue}>
      {children}
    </ProductProviderContext.Provider>
  )
}

And in one of my child component that has been wrapped inside ProductProvider I want to call setHandler but I want to access the last value of data like the useState callback function so when I access a ProductProviderContext from child, I can use setHandler like useState (either passing changed value directly or using callback to get previous data value).

Example usage that I expect setHandler to accept:

setHandler({ sortType: 'popular' })
// or I can use it like this
setHandler(prevData => ({ offset: prevData.offset++ }))

How do I refactor the setHandler so I can call setHandler like above?

3

Answers


  1. Hmmm, you can try in ProductProvider.js

    ...
    
    const setHandler = (value) => {
       setData((prevData) => (typeof value === 'function' ? value(prevData) : { ...prevData, ...value }));   };
    
    ...
    
    Login or Signup to reply.
  2. I usually pass two data, setData in Provider. You can try this:

    ...
    <ProductProviderContext.Provider value=[data, setData]>
          {children}
    </ProductProviderContext.Provider>
    ...
    
    Login or Signup to reply.
  3. The setData state updater function already does exactly what you are asking to do, so you can just pass it down directly to consumers if you like.

    export default ({ children }) => {
      const [data, setData] = React.useState(INITIAL_HANDLER);
    
      const contextValue = useMemo(() => ({
        ...data,
        setHandler: setData // <-- pass setData callback down
      }), [data]);
    
      return (
        <ProductProviderContext.Provider value={contextValue}>
          {children}
        </ProductProviderContext.Provider>
      );
    }
    

    Or you can update setHandler to consume a value that is either a value or a callback function that is passed to the setData state updater function.

    export default ({ children }) => {
      const [data, setData] = React.useState(INITIAL_HANDLER);
    
      const setHandler = (valueOrCallback) => {
        setData(valueOrCallback);
      };
    
      const contextValue = useMemo(() => ({
        ...data,
        setHandler
      }), [data]);
    
      return (
        <ProductProviderContext.Provider value={contextValue}>
          {children}
        </ProductProviderContext.Provider>
      )
    }
    

    Either implementation should allow you to pass a value directly.

    setHandler({ sortType: 'popular' });
    

    Or pass an updater callback. Be sure to not mutate the current state though (e.g. you still need to apply a mutable state update!).

    setHandler(prevData => ({ 
      ...prevData,                // <-- shallow copy current state
      offset: prevData.offset + 1 // <-- don't mutate prevData.offset
    }));
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search