【问题标题】:Edit data when using a custom data fetching React Hook?使用自定义数据获取 React Hook 时编辑数据?
【发布时间】:2020-07-19 15:17:17
【问题描述】:

我一直在尝试使用从返回数据的 API 获取的数据制作图表,如下所示:

{
  "totalAmount": 230,
  "reportDate": "2020-03-05"
},
{
  "totalAmount": 310,
  "reportDate": "2020-03-06"
}
...

当显示为图表时,日期字符串太长,所以我想通过删除年份部分来缩短它。

2020-03-06 变为 03/06

a great tutorial by Robin Wieruch 之后,我现在有一个自定义 Hook 来获取数据:

const useDataApi = (initialUrl, initialData) => {
  const [data, setData] = useState(initialData);
  const [url, setUrl] = useState(initialUrl);
  const [isLoading, setIsLoading] = useState(true);
  const [isError, setIsError] = useState(false);
  useEffect(() => {
    const fetchData = async () => {
      setIsError(false);
      setIsLoading(true);
      try {
        const response = await fetch(url);
        const data = await response.json();
        setData(data);
      } catch (error) {
        setIsError(true);
      }
      setIsLoading(false);
    };
    fetchData();
  }, [url]);
  return [{ data, isLoading, isError }];
}; 

连同我用 React Hooks 编写的图表组件:

const MyChart = () => {
  const [{ data, isLoading, isError }] = useDataApi(
    "https://some/api/domain",
    []
  );
  useEffect(() => {
    // I'm using useEffect to replace every date strings before rendering
    if (data) {
      data.forEach(
        d =>
          (d.reportDate = d.reportDate
            .replace(/-/g, "/")
            .replace(/^\d{4}\//g, ""))
      );
    }
  }, [data]);
  return (
    <>
      <h1>My Chart</h1>
      {isError && <div>Something went wrong</div>}
      {isLoading ? (
        . . .
      ) : (
        <>
        . . .
        <div className="line-chart">
          <MyLineChart data={data} x="reportDate" y="totalAmount" />
        </div>
        </>
      )}
    </>
  );
};                                   

以上工作。但我感觉这可能不是最佳实践,因为useEffect 在渲染过程中会被调用两次。当我尝试在我的自定义 Hook 中采用 useReducer 时,代码不再工作了。

所以我想知道在这种情况下编辑数据的最佳方法是什么?

【问题讨论】:

    标签: javascript reactjs react-hooks use-effect use-reducer


    【解决方案1】:

    您可以为您的数据创建一个映射函数,然后由钩子使用。这可以在你的钩子函数之外完成。

    const mapChartDataItem = (dataItem) => ({
      ...dataItem,
      reportDate: dataItem.reportDate.replace(/-/g, "/").replace(/^\d{4}\//g, ""))
    });
    

    reportDate 映射与您在 useEffect 中使用的代码相同。

    然后在你的钩子函数中:

    const data = await response.json();
    
    // this is the new code
    const mappedData = data.map(mapChartDataItem);
    
    // change setData to use the new mapped data
    setData(mappedData);
    

    在此处执行此操作意味着您只映射一次对象(在获取对象时),而不是每次数据值更改时。


    更新 - 将函数注入钩子

    你的钩子现在看起来像这样:

    const useDataApi = (initialUrl, initialData, transformFn) => {
      const [data, setData] = useState(initialData);
      const [url, setUrl] = useState(initialUrl);
      const [isLoading, setIsLoading] = useState(true);
      const [isError, setIsError] = useState(false);
      useEffect(() => {
        const fetchData = async () => {
          setIsError(false);
          setIsLoading(true);
          try {
            const response = await fetch(url);
            const data = await response.json();
            // if transformFn isn't provided, then just set the data as-is
            setData((transformFn && transformFn(data)) || data);
          } catch (error) {
            setIsError(true);
          }
          setIsLoading(false);
        };
        fetchData();
      }, [url, transformFn]);
      return [{ data, isLoading, isError }];
    }; 
    

    然后调用它,你可以使用如下:

    const mapChartDataItem = (dataItem) => ({
      ...dataItem,
      reportDate: dataItem.reportDate.replace(/-/g, "/").replace(/^\d{4}\//g, ""))
    });
    
    const transformFn = useCallback(data => data.map(mapChartDataItem), []);
    
    const [{ data, isLoading, isError }] = useDataApi(
      "https://some/api/domain",
      [],
      transformFn
    );
    

    为简单起见,因为 transformFn 参数是函数的最后一个参数,所以您可以选择不使用它来调用您的钩子,它只会返回从 fetch 调用返回的数据。

    const [{ data, isLoading, isError }] = useDataApi(
      "https://some/api/domain",
      []
    );
    

    会像以前一样工作。

    另外,如果你不想在你的代码中使用(transformFn &amp;&amp; transformFn(data)) || data,你可以在你的钩子中给 transformFn 一个默认值,更多的是:

    const useDataApi = (
      initialUrl, 
      initialData, 
      transformFn = data => data) => {
    
      // then the rest of the hook in here
    
      // and call setData with the transformFn
      setData(transformFn(data));
    }
    

    【讨论】:

    • useDataApi 钩子函数应该保持通用,因为它还需要从其他具有不同日期字符串格式的 API 获取数据。所以这里的问题实际上是如何在获取数据之后进行替换。感谢映射功能。
    • 在这种情况下,你可以添加一个可选的转换函数作为你的钩子的参数
    • 应该可以。你能在你的回答中举个例子吗?
    • 要在useEffect 中使用transformFn,必须将其添加到依赖项中。但是因为是函数,所以会触发无限重取。我认为简单的解决方案是将transformFn 包装成useCallback Hook。
    • 我已将 transformFn 添加到依赖项中。希望你不要介意。
    猜你喜欢
    • 2020-06-20
    • 2022-01-07
    • 2019-07-27
    • 2020-01-01
    • 2021-09-22
    • 2019-11-24
    • 2022-07-12
    • 2013-10-03
    • 1970-01-01
    相关资源
    最近更新 更多