Question

React use hooks (useContext) inside of useReducer

I have a reducer inside useContext/provider. I need to validate an action using values from another Provider. Is there a way to retrieve those values from a Provider from within the useReducer hook? Or do I have to try to feed them in as function arguments. Ex:

Reducer

export default function reducer(state: State, action: Action): State {
    switch(action.type) {
        case "set-amount": {
            const { amount } = action.state
            const { maxAmount } = useFooContext()

            if (amount > maxAmount) // do something

            return { ...state, amount }
        }
    }
}

Provider

export function Provider(...) {
    const [state, dispatch] = useReducer(reducer, {}, initState)
    return ...
}

=== EDIT per @Drew

Wondering if I'm following what you said, does this look right?

// provider
export default function Provider( ... ) {
    const foo = useFooContext()
    const fooRef = React.useRef(foo)

    const reducerFn = useMemo( () => {
        fooRef.current = foo
        return reducer( fooRef )
    }, [foo])

    const [state, dispatch] = useReducer( reducerFn, {}, initState)

    return ( ... )
}

// reducer
export default function(ref: React.MutableRefObject<Foo>) {
    return function(state: State, action: Action): State {
        ...
        fooRef.current.value != value
        ...
    }
}
 2  28  2
1 Jan 1970

Solution

 2

You can't call React hooks in nested functions, this breaks React's Rules of Hooks, only call hooks at the top-level in React functions and custom hooks.

I suggest re-writing the reducer function to curry the Foo context value when it's instantiated.

Example:

const reducer = (fooContext: FooContext) => (state: State, action: Action) => {
  switch(action.type) {
    case "set-amount": {
      const { amount } = action.state;
      const { maxAmount } = fooContext;

      if (amount > maxAmount) // do something

      return { ...state, amount }
    }

    ...
  }
};

export default reducer;
export function Provider(...) {
  const fooContext = useFooContext();

  const reducerFn = useMemo(() => reducer(fooContext), []);

  const [state, dispatch] = useReducer(reducerFn, {}, initState);

  return ...
}

If you need to access non-static values then you could return a "getState" method the fooContext could access, or save fooContext into a React ref that is passed to the curried reducer function so it can access whatever the current context value is each time.

2024-07-23
Drew Reese