【问题标题】:Conflicts between useEffect in reactreact中useEffect之间的冲突
【发布时间】:2020-09-04 03:59:07
【问题描述】:

我必须创建使用分页和过滤器获取数据的组件。 过滤器由 props 传递,如果它们发生变化,组件应该重置数据并从第 0 页获取数据。

我有这个:

const PaginationComponent = ({minPrice, maxPrice}) => {

  const[page, setPage] = useState(null);
  const[items, setItems] = useState([]);

  const fetchMore = useCallback(() => {
    setPage(prevState => prevState + 1);
  }, []);

  useEffect(() => {
    if (page === null) {
      setPage(0);
      setItems([]);
    } else {
      get(page, minPrice, maxPrice)
        .then(response => setItems(response));
    }
  }, [page, minPrice, maxPrice]);

  useEffect(() => {
    setPage(null);
  },[minPrice, maxPrice]);
};

.. 还有一个问题,因为第一个 useEffect 依赖于 props,因为我用它们来过滤数据,第二个我想重置组件。因此,在更改道具后,两个 useEffects 都会运行。

我没有更多的想法如何正确地做到这一点。

【问题讨论】:

  • 我不确定我们是否需要第二个 useEffect。更改道具时重新渲染子元素。
  • minPricemaxPrice 发生变化时,你为什么要setPage(null),然后当这些相同的道具发生变化时,你又在另一个useEffect 钩子中执行获取请求?
  • 我必须将 setPage 设置为 null,因为如果有人更改了第 0 页上的过滤器怎么办? :) 0 等于 0 :)

标签: reactjs react-hooks


【解决方案1】:

一般来说,这里的关键是将page 状态向上移动到父组件,并在您更改过滤器时将页面更改为 0。您可以使用useStateuseReducer 进行操作。

它与useState 一起工作的原因(即只有一个重新渲染)是因为 React 在事件处理程序中批量状态更改,如果没有,您仍然会得到两个 API 调用。 CodeSandbox

const PaginationComponent = ({ page, minPrice, maxPrice, setPage }) => {
  const [items, setItems] = useState([]);

  useEffect(() => {
    get(page, minPrice, maxPrice).then(response => setItems(response));
  }, [page, minPrice, maxPrice]);

  return (
    <div>
      {items.map(item => (
        <div key={item.id}>
          {item.id}, {item.name}, ${item.price}
        </div>
      ))}
      <div>Page: {page}</div>
      <button onClick={() => setPage(v => v - 1)}>back</button>
      <button onClick={() => setPage(v => v + 1)}>next</button>
    </div>
  );
};

const App = () => {
  const [page, setPage] = useState(0);
  const [minPrice, setMinPrice] = useState(25);
  const [maxPrice, setMaxPrice] = useState(50);

  return (
    <div>
      <div>
        <label>Min price:</label>
        <input
          value={minPrice}
          onChange={event => {
            const { value } = event.target;
            setMinPrice(parseInt(value, 10));
            setPage(0);
          }}
        />
      </div>
      <div>
        <label>Max price:</label>
        <input
          value={maxPrice}
          onChange={event => {
            const { value } = event.target;
            setMaxPrice(parseInt(value, 10));
            setPage(0);
          }}
        />
      </div>

      <PaginationComponent minPrice={minPrice} maxPrice={maxPrice} page={page} setPage={setPage} />
    </div>
  );
};

export default App;


另一种解决方案是使用useReducer,它更透明,但也像通常使用reducer 一样,样板文件有点重。这个例子的行为有点不同,因为有一个“设置过滤器”按钮可以更改传递给分页组件的状态,更像是“现实生活”场景 IMO。 CodeSandbox

const PaginationComponent = ({ tableConfig, setPage }) => {
  const [items, setItems] = useState([]);

  useEffect(() => {
    const { page, minPrice, maxPrice } = tableConfig;
    get(page, minPrice, maxPrice).then(response => setItems(response));
  }, [tableConfig]);

  return (
    <div>
      {items.map(item => (
        <div key={item.id}>
          {item.id}, {item.name}, ${item.price}
        </div>
      ))}
      <div>Page: {tableConfig.page}</div>
      <button onClick={() => setPage(v => v - 1)}>back</button>
      <button onClick={() => setPage(v => v + 1)}>next</button>
    </div>
  );
};

const tableStateReducer = (state, action) => {
  if (action.type === "setPage") {
    return { ...state, page: action.page };
  }
  if (action.type === "setFilters") {
    return { page: 0, minPrice: action.minPrice, maxPrice: action.maxPrice };
  }
  return state;
};

const App = () => {
  const [tableState, dispatch] = useReducer(tableStateReducer, {
    page: 0,
    minPrice: 25,
    maxPrice: 50
  });
  const [minPrice, setMinPrice] = useState(25);
  const [maxPrice, setMaxPrice] = useState(50);

  const setPage = useCallback(
    page => {
      if (typeof page === "function") {
        dispatch({ type: "setPage", page: page(tableState.page) });
      } else {
        dispatch({ type: "setPage", page });
      }
    },
    [tableState]
  );

  return (
    <div>
      <div>
        <label>Min price:</label>
        <input
          value={minPrice}
          onChange={event => {
            const { value } = event.target;
            setMinPrice(parseInt(value, 10));
          }}
        />
      </div>
      <div>
        <label>Max price:</label>
        <input
          value={maxPrice}
          onChange={event => {
            const { value } = event.target;
            setMaxPrice(parseInt(value, 10));
          }}
        />
      </div>
      <button
        onClick={() => {
          dispatch({ type: "setFilters", minPrice, maxPrice });
        }}
      >
        Set filters
      </button>

      <PaginationComponent tableConfig={tableState} setPage={setPage} />
    </div>
  );
};

export default App;

【讨论】:

    【解决方案2】:

    你可以使用以下

    const fetchData = () => {
     get(page, minPrice, maxPrice)
        .then(response => setItems(response));
    }
    // Whenever page updated fetch new data
    useEffect(() => {
     fetchData();
    }, [page]);
    
    // Whenever filter updated reseting page
    useEffect(() => {
     const prevPage = page;
     setPage(0);
     if(prevPage === 0 ) {
       fetchData();
     }
    },[minPrice, maxPrice]);
    

    【讨论】:

    • 但第一个效果也取决于 minPrice 和 maxPrice。所以它应该听它。
    • 根据您的问题,当过滤器更改时,您只需重置页面,第二个效果更改页面。然后执行第一个效果(因为页面已更改),然后执行 get 函数以获取响应。 minPrice 和 maxPrice 的值仍将传递给get 函数
    • 好的,在这种情况下 linter 警告不重要吗?但正如我上面写的 - 我不能 setPage 为 0 ..
    • 它不能将页面设置为 0,因为如果我当前在我的分页中是第 0 页怎么办?没有区别,所以 useEffect 不会运行。
    • 我已经更新了帖子,这可能会解决你的问题
    猜你喜欢
    • 1970-01-01
    • 2022-07-20
    • 1970-01-01
    • 1970-01-01
    • 2021-03-07
    • 2023-02-09
    • 2011-07-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多