【问题标题】:React hooks pagination with useEffect - how to revert page number if request fails?React 使用 useEffect 挂钩分页 - 如果请求失败,如何恢复页码?
【发布时间】:2020-07-13 14:12:41
【问题描述】:

我正在使用 react 钩子测试简单的分页,并想知道如何处理失败以使其保持有效状态?

伪代码:

function reducer(state, action) {
  switch (action.type) {
    case 'next':
      return {page: state.page + 1};
    case 'prev':
      return {page: state.page - 1};
    default:
      throw new Error();
  }
}

function Page() {
  const [state, dispatch] = useReducer(reducer, { page: 1});

  useEffect(() => {
    loadMore(state.page)
    .then(r => console.log(r))
    .catch(e => {
      // how to revert page without triggering this effect?
      // cause if I do dispatch({type: 'prev'}) page will change, and this will re-run
      console.log(e)
    })
  }, state.page)

  return (
    <>
      Page: {state.page}
      <button onClick={() => dispatch({type: 'prev'})}>-</button>
      <button onClick={() => dispatch({type: 'next'})}>+</button>
    </>
  );
}

让我们来看看这个场景:

  • 我们从第 1 页开始
  • 用户点击下一步
  • 页面增加状态
  • useEffect 触发,网络请求失败。
  • 状态页面为2,但实际数据为页面1

我无法在 catch 时调度页面更改事件,因为它会触发重新获取,我不想这样做

我正在考虑的另一个选项是,我应该只在获得数据后才增加页面,但是我会在 useEffect 中创建无限循环,因为页面会在获取数据后发生变化?

【问题讨论】:

    标签: reactjs react-hooks


    【解决方案1】:

    您可以添加另一个状态来表示我们希望看到的页面,假设它已加载。

    function reducer(state, action) {
      switch (action.type) {
        case 'failed':
          return { ...state, requestedPage: null };
        case 'loaded':
          return { page: action.page, requestedPage: null };
        case 'next':
          return { ...state, requestedPage: state.page + 1 };
        case 'prev':
          return { ...state, requestedPage: state.page - 1 };
        default:
          throw new Error();
      }
    }
    
    const Page = () => {
      const [{ page, requestedPage }, dispatch] = useReducer(reducer, {
        page: 1,
        requestedPage: null,
      });
    
      useEffect(() => {
        loadMore(requestedPage)
          .then((r) => {
            dispatch({ type: 'loaded', page: requestedPage });
            console.log(r);
          })
          .catch((e) => {
            dispatch({ type: 'failed' });
            console.error(e);
          });
        // Make sure the dependencies are an array
      }, [requestedPage]);
    
      // Optimistically render the requestedPage (with a spinner?) if present,
      // otherwise current page.
      return (
        <>
          Page: {requestedPage || page}
          <button onClick={() => dispatch({ type: 'prev' })}>-</button>
          <button onClick={() => dispatch({ type: 'next' })}>+</button>
        </>
      );
    };
    

    【讨论】:

      【解决方案2】:

      您也可以简单地删除useEffect并使用这样的功能:

      let fetch = (page, isNext)=>{
      
       loadMore(page)
          .then(r => {
            // ..
            isNext ? dispatch({type: 'next'}):dispatch({type: 'prev'});
          })
          .catch(e => {
            // Don't dispatch anything, you will remain on same page
          })
      
      }
      

      现在,当用户单击下一步时,只需调用 fetch(state.page + 1, true)。如果她点击prev btn,使用fetch(state.page - 1, false);

      【讨论】:

      • 上一个/下一个分页的简单解决方案。但是,如果我需要从另一个 useEffect(例如无限滚动)调用 fetch 怎么办 - 那么同样的问题仍然存在
      • @gerasalus 你不能在无限滚动场景中省略 useEffect 吗?
      【解决方案3】:

      我会使用useRef() 来保存成功的上一页。如果当前调用失败,通过设置返回上一页。输入useEffect()时,检查当前页面和ref中存储的页面是否相同,如果相同则什么都不做。

      function reducer(state, { type, page }) {
        switch (type) {
          case 'set':
            return { page };
          case 'next':
            return { page: state.page + 1 };
          case 'prev':
            return { page: state.page - 1 };
          default:
            throw new Error();
        }
      }
      
      function Page() {
        const [state, dispatch] = useReducer(reducer, { page: 1 });
      
        const currentPage = useRef();
      
        useEffect(() => {
          if (currentPage.current === state.page) return;
      
          loadMore(state.page)
          .then(r => {
            currentPage.current = state.page;
          })
          .catch(e => {
            dispatch({ type: 'set', page: currentPage.current || 1 }); // if currentPage.current doesn't exist use page 1 as default
      
            console.log(e)
          })
        }, state.page)
      
        return (
          <>
            Page: {state.page}
            <button onClick={() => dispatch({type: 'prev'})}>-</button>
            <button onClick={() => dispatch({type: 'next'})}>+</button>
          </>
        );
      }
      

      【讨论】:

        【解决方案4】:

        通常最好避免让 useEffect 处理分页,这样您就可以完全控制正在发生的事情。

        你可以

          const [currentPage, setCurrentPage] = useState(0);
          const [lastPage, setLastPage] = useState(true);
        
          const fetchData = useCallback((page: number) => {
            yourFetchFunction(page).then((response) => {
              // By setting page here, you will update the page just if it works
              setCurrentPage(page);
        
              // If your endpoint returns last, you can also update it here
              setLastPage(response.last);
        
              // ... Do whatever you need with the response
            });
          }, []);
        
          // First run, that's why we have the useCallback
          useEffect(() => fetchData(0), [fetchData]);
        
          // To load the next page, we can simply fetch data passing the next page
          // State is going to be update if it succeeds. If it fails, "page is going to be reverted" (actually, not updated)
          const loadMore = () => {
            if (!lastPage) {
              fetchData(currentPage + 1);
            }
          };
        
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-02-13
          • 2021-10-03
          • 2020-09-25
          • 1970-01-01
          • 2021-02-02
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多