score:2

Here is an example that uses context and useReducer hook to set an app state and a context provider for state and dispatch.

The container uses useContext to get the state and the dispatch function, useEffect to do side effects like you'd use thunk, saga or middleware if you were using redux, useMemo to map state to props and useCallback to map each auto dispatched action to props (I assume you are familiar with react redux connect.

import React, {
  useEffect,
  useContext,
  useReducer,
  useCallback,
  useMemo,
} from 'react';

//store provider
const Store = React.createContext();
const initStoreProvider = (rootReducer, initialState) => ({
  children,
}) => {
  const [state, dispatch] = useReducer(
    rootReducer,
    initialState
  );
  return (
    <Store.Provider value={{ state, dispatch }}>
      {children}
    </Store.Provider>
  );
};
//container for component
const ComponentContainer = ({ id }) => {
  const { state, dispatch } = useContext(Store);
  const num = state.find((n, index) => index === id);
  //side effects, asynchonously add another one if num%5===0
  //this is your redux thunk
  const addAsync = num % 5 === 0;
  useEffect(() => {
    if (addAsync)
      Promise.resolve().then(dispatch({ type: 'add', id }));
  }, [addAsync, dispatch, id]);
  //use callback so function does not needlessly change and would
  //trigger render in Component. This is mapDispatch but only for
  //one function, if you have more than one then use 
  //useCallback for each one
  const add = useCallback(
    () => dispatch({ type: 'add', id }),
    [dispatch, id]
  );
  //This is your memoized mapStateToProps
  const props = useMemo(() => ({ counter: num, id }), [
    num,
    id,
  ]);

  return (
    <Component add={add} doNothing={dispatch} {...props} />
  );
};
//use React.memo(Component) to avoid unnecessary renders
const Component = React.memo(
  ({ id, add, doNothing, counter }) =>
    console.log('render in component', id) || (
      <div>
        <button onClick={add}>{counter}</button>
        <button onClick={doNothing}>do nothing</button>
      </div>
    )
);
//initialize the store provider with root reducer and initial state
const StoreProvider = initStoreProvider(
  (state, action) =>
    action.type === 'add'
      ? state.map((n, index) =>
          index === action.id ? n + 1 : n
        )
      : state,
  [1, 8]
);

//using the store provider
export default () => (
  <StoreProvider>
    <ComponentContainer id={0} />
    <ComponentContainer id={1} />
  </StoreProvider>
);

Example is here

score:3

This is probably how I would implement it. I have a standard reducer. I will also create a helper functional component to help me set up the value for my context provider.

I also made some comments in the source code. I hope the following code snippet is simple enough to follow.

    import React, { useReducer, useEffect, createContext } from 'react';
    import FetchService from './util/FetchService'; // some helper functions


    const OrderInfoContext = createContext();

    const reducer = (state, action) => {
      switch (action.type) {
        case 'init':
          return {};
        case 'changeData':
          return action.payload;
        default:
          return state;
      }
    };

    const changeData = data => ({
      type: 'changeData',
      payload: data
    });

    /**
     * This is a helper component that generate the Provider wrapper
     */
    function OrderInfoProvider(props) {
      // We will persist API payload in the state so we can assign it to the Context
      const [orders, dispatch] = useReducer(reducer, {});

      // We use useEffect to make API calls.
      useEffect(() => {
        async function fetchData() {
          /**
           * This is just a helper to fetch data from endpoints. It can be done using
           * axios or similar libraries
           */
          const orders = await FetchService
            .get('/api/orders');
          dispatch(changeData(orders))
        }
        fetchData();
      }, []);

      /**
       * we create a global object that is available to every child components
       */
      return <OrderInfoContext.Provider value={[orders, dispatch]} {...props} />;
    } 

    // Helper function to get Context
    function useOrderInfo() {
      const context = useContext(OrderInfoContext);
      if (!context) {
        throw new Error('useOrderInfo must be used within a OrderInfoProvider');
      }
      return context;
    }

    export { OrderInfoProvider, useOrderInfo , changeData }; 



Related Query

More Query from same tag