React useReducer async data fetch

前端 未结 6 676
臣服心动
臣服心动 2020-11-30 22:36

I\'am trying to fetch some data with new react useReducer API and stuck on stage where i need to fetch it async. I just don\'t know how :/

How to place data fetching

6条回答
  •  借酒劲吻你
    2020-11-30 23:38

    It is a good practice to keep reducers pure. It will make useReducer more predictable and ease up testability.

    Fetch data before dispatch (simple approach)

    You can wrap the original dispatch with asyncDispatch and pass this function down via context:

    const AppContextProvider = ({ children }) => {
      const [state, dispatch] = React.useReducer(reducer, initState);
      const asyncDispatch = () => {
        dispatch({ type: "loading" });
        fetchData().then(data => {
          dispatch({ type: "finished", payload: data });
        });
      };
    
      return (
        
          {children}
        
      );
    };
    

    const reducer = (state, { type, payload }) => {
      if (type === "loading") return { status: "loading" };
      if (type === "finished") return { status: "finished", data: payload };
      return state;
    };
    
    const initState = {
      status: "idle"
    };
    
    const AppContext = React.createContext();
    
    const AppContextProvider = ({ children }) => {
      const [state, dispatch] = React.useReducer(reducer, initState);
      const asyncDispatch = () => {
        dispatch({ type: "loading" });
        fetchData().then(data => {
          dispatch({ type: "finished", payload: data });
        });
      };
    
      return (
        
          {children}
        
      );
    };
    
    function App() {
      return (
        
          
        
      );
    }
    
    const Child = () => {
      const val = React.useContext(AppContext);
      const {
        state: { status, data },
        dispatch
      } = val;
      return (
        

    Status: {status}

    Data: {data || "-"}

    ); }; function fetchData() { return new Promise(resolve => { setTimeout(() => { resolve(42); }, 2000); }); } ReactDOM.render(, document.getElementById("root"));
    
    
    

    Use middleware for dispatch

    For more flexibility and reusability, you can enhance dispatch with middlewares. Either write your own or use the ones from Redux ecosystem like redux-thunk.

    Let's say, you want to fetch async data with thunk and do some logging, before dispatch is invoked:

    import thunk from "redux-thunk";
    const middlewares = [thunk, logger]; // logger is our own one
    

    We can then write a composer similar to applyMiddleware, which creates a chain fetch data → log state → dispatch. In fact I looked up in the Redux repository, how this is done.

    useMiddlewareReducer Hook

    const [state, dispatch] = useMiddlewareReducer(middlewares, reducer, initState);
    

    The API is the same as useReducer and you pass middlewares as first argument. Basic idea is, that we store intermediate state in mutable refs, so each middleware always can access the most recent state with getState.

    const middlewares = [ReduxThunk, logger];
    
    const reducer = (state, { type, payload }) => {
      if (type === "loading") return { ...state, status: "loading" };
      if (type === "finished") return { status: "finished", data: payload };
      return state;
    };
    
    const initState = {
      status: "idle"
    };
    
    const AppContext = React.createContext();
    
    const AppContextProvider = ({ children }) => {
      const [state, dispatch] = useMiddlewareReducer(
        middlewares,
        reducer,
        initState
      );
      return (
        
          {children}
        
      );
    };
    
    function App() {
      return (
        
          
        
      );
    }
    
    const Child = () => {
      const val = React.useContext(AppContext);
      const {
        state: { status, data },
        dispatch
      } = val;
      return (
        

    Status: {status}

    Data: {data || "-"}

    ); }; function fetchData() { return (dispatch, getState) => { dispatch({ type: "loading" }); setTimeout(() => { // fake async loading dispatch({ type: "finished", payload: (getState().data || 0) + 42 }); }, 2000); }; } function logger({ getState }) { return next => action => { console.log("state:", JSON.stringify(getState()), "action:", JSON.stringify(action)); return next(action); }; } // same API as useReducer, with middlewares as first argument function useMiddlewareReducer( middlewares, reducer, initState, initializer = s => s ) { const [state, setState] = React.useState(initializer(initState)); const stateRef = React.useRef(state); // stores most recent state const dispatch = React.useMemo( () => enhanceDispatch({ getState: () => stateRef.current, // access most recent state stateDispatch: action => { stateRef.current = reducer(stateRef.current, action); // makes getState() possible setState(stateRef.current); // trigger re-render return action; } })(...middlewares), [middlewares, reducer] ); return [state, dispatch]; } // | dispatch fn | // A middleware has type (dispatch, getState) => nextMw => action => action function enhanceDispatch({ getState, stateDispatch }) { return (...middlewares) => { let dispatch; const middlewareAPI = { getState, dispatch: action => dispatch(action) }; dispatch = middlewares .map(m => m(middlewareAPI)) .reduceRight((next, mw) => mw(next), stateDispatch); return dispatch; }; } ReactDOM.render(, document.getElementById("root"));
    
    
    

    External library links found (ordered by star count): react-use, react-hooks-global-state, react-enhanced-reducer-hook

提交回复
热议问题