简体   繁体   中英

using useContext with custom hooks vs useContext + useReducer

I recently enterd a project and saw something that I have not seen before when it comes to useContext in react.

The global state in the project uses hooks which it sends in to a context and then when calling that hook later on the global state is accesable. The problem I saw with this is that ther is no defined global state in one place, you can create a hook with state and update functions, send it in to a provider and get access to it anywhere in the project..

Code:

const initialState = {
  id: "MyId",
  currency: 'currency',
};


function useCurrencyState() {
  initialState.currency = 'newCurrency'
  const [currency, setCurrency] = React.useState(initialState);

  return {
    currency
  };
}


export const [useCurrency, CurrencyStoreProvider] = createStoreProvider(useUserState);

The provider:

export function createStoreProvider(useHook) {
  const [useContextConsumer, ContextProvider] = generateContext();

  const StoreProvider = ({ children }) => {
    const state = useHook();

    return <ContextProvider value={state}>{children}</ContextProvider>;
  };

  return [useContextConsumer, StoreProvider];
}

generate context func:

export function generateContext() {
  const context = React.createContext(undefined);

  const useContextConsumer = () => {
    const c = React.useContext(context);
    if (!c) {
      throw new Error('Component must be wrapped with <Container.Provider>');
    }
    return c;
  };

  return [useContextConsumer, context.Provider];
}

the store:

const StoreProvider = ({ children }) => (
  <CurrencyStoreProvider>
      {children}
  </CurrencyStoreProvider>
);

export default StoreProvider;

and when you want to use the useCurrency you would

import { useCurrency } from 'store/currency';

 const { currency} = useCurrency ();

The above example is for one hook. The project has 8 of them which follow the same pattern and the project has 8 nested providers.

My inital thought to this is that it is mutating state anonymously since it doesnt have a global defined state and no reducer who catches the action in order to update the global state.

Am I right? Is this the not so recommended way to handle state? If im worng what is this pattern called if it has a name?

I was about to recommend to change to using a context + useReducer with action and dispatch but I need to get a better understanding of the above.

Code sample, provided above, has two parts. One is state management (done with useState , and second is state provider (done with context). Lets discuss them separately.

Generally, useState , useReducer and Redux reducer are all the same. They all allow to have some state and render components based on it. They are different in how they allow to manipulate state, especially in complex cases.

  1. useState is a simplest aspproach. All you can do is to
  const [state, setState] = useState()
  setState(/* some new state */)
  // or
  setState(prevState => ({ ...prevState, /* some new state */ }))

It will be hard to add a logic when manipulated a state. Ie if you want to do currency conversion before calling setCurrency you should do it somewhere, or write custom hook. And this custom hook will be your implementation of Redux action.

It will be even harder to execute async code (fetch currency rates). Don't forget that fetching rates in useEffect is not enough, as you will have to handle server errors (5xx or 4xx) and show appropriate message to user. To store error you'll probably need additional state, or put it inside currency state.

Following this approach with complex state will lead you to writing Redux by yourself.

  1. useReducer (this is React reducer, not Redux) allows to manipulate complex state with actions. Ie you'll be able to dispatch SET_CURRENCY action and SET_RATES action separately, and useReducer will update state accordantly. But it does not have any logic for async code (ie fetching rates from server). You should write it yourself with custom hooks.

  2. Redux is most sophisticated approach to handle state. It allows to update parts of your state with actions and handle async actions. If you'll consider such library as Redux Toolkit , you'll be able to remove a lot of boilerplate code from your project and work with complex state update logic.

From my experience, useState is for simple state, like open dialog. All other state goes to Redux.

Additionally, I can mention form's state, which can be manipulated with such library as Reach Hook Forms . React hook form will hold form specific state internally and provide you with form specific state, like errors, touched, submit count, etc.

Second part in the provided example is state provider part. It is done with context. This is expected, as useState does not suggest anything to pass state to components. Redux is also using context, but it is created for you by React-Redux library. Additionally, React-Redux will provide you with useful tools like useSelector to select only parts of the state. React context does not have any selectors. It will give you full state, and you'll have to use useMemo to get part of the state and pass it to lower level components. Again, it is similar to writing React-Redux library yourself.

And final question. There is no name or pattern for approach, which is used in the provided code. Some developer just invented it for a project. Form my point of view, there is nothing interesting in such approach.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM