在useEffect中访问旧状态与新状态进行比较使用自定义钩子usePrevious

时间:2019-06-06 16:07:09

标签: javascript reactjs react-hooks

我正在尝试将基于类的react组件迁移到react-hook。该组件的目的是从HackerNews API中获取故事,并在每5000毫秒后再次点击API以获得新数据来进行轮询。

我面临的问题是使用 usePrevious() 下的自定义钩子将我以前的状态与当前状态进行比较,并且只有在进行比较之后才能在useEffect()中执行其他功能 我很可能在这里缺少自定义钩子或useEffect()的一些基本实现

我正在关注this official guide

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

Here's the code for my class based component,这很正常。

And below is my hooks based component

问题出在这行

const fromPrevStoriesIds = usePrevious(prevStoriesIds);

变量fromPrevStoriesIds在return()内给了我很好的价值,但在useEffect()内却没有定义。

import React, { Component, useState, useEffect, useRef } from "react";
import axios from "axios";
import MUIDataTable from "mui-datatables";
import "./Dashboard.css";
import NewItemAddedConfirmSnackbar from "./NewItemAddedConfirmSnackbar";
import TextField from "@material-ui/core/TextField";
import Button from "@material-ui/core/Button";
const isEqual = require("lodash.isequal");
const differenceWith = require("lodash.differencewith");
const omit = require("lodash.omit");

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

const getEachStoryGivenId = (id, index) => {
  return new Promise((resolve, reject) => {
    axios
      .get(`https://hacker-news.firebaseio.com/v0/item/${id}.json`)
      .then(res => {
        let story = res.data;        
        let result = omit(story, ["descendants", "time", "id", "type"]);        
        if (
          result &&
          Object.entries(result).length !== 0 &&
          result.constructor === Object
        ) {
          resolve(result);
        } else {
          reject(new Error("No data received"));
        }
      });
  });
};

const Dashboard = () => {
  const [prevStoriesIds, setPrevStoriesIds] = useState([]);
  const [fetchedData, setFetchedData] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [tableState, setTableState] = useState({});
  const [
    openNewItemAddedConfirmSnackbar,
    setOpenNewItemAddedConfirmSnackbar
  ] = useState(false);
  const [noOfNewStoryAfterPolling, setNoOfNewStoryAfterPolling] = useState(0);
  const [rowsPerPage, setRowsPerPage] = useState(10);

  const onChangeRowsPerPage = rowsPerPage => {
    setRowsPerPage(rowsPerPage);
  };

  const closeNewItemConfirmSnackbar = () => {
    setOpenNewItemAddedConfirmSnackbar(false);
    axios
      .get("https://hacker-news.firebaseio.com/v0/newstories.json")
      .then(storyIds => {
        setPrevStoriesIds(storyIds.data.slice(0, 2));
        getAllNewStory(storyIds);
      });
  };

  const getAllNewStory = storyIds => {
    setIsLoading(true);
    let topStories = storyIds.data.slice(0, 2).map(getEachStoryGivenId);
    let results = Promise.all(topStories);
    results
      .then(res => {
        setFetchedData(res);
        setIsLoading(false);
      })
      .catch(err => {
        console.log(err);
      });
  };


  const fromPrevStoriesIds = usePrevious(prevStoriesIds);

  useEffect(() => {
    const fetchData = () => {
      axios
        .get("https://hacker-news.firebaseio.com/v0/newstories.json")
        .then(storyIds => {
          //   console.log("STORY IDs FETCHED ", storyIds.data.slice(0, 2));

          setPrevStoriesIds(storyIds.data.slice(0, 2));
          getAllNewStory(storyIds);
        });
    };
    fetchData();

    const doPolling = () => {
      var timer = setInterval(() => {
        axios
          .get("https://hacker-news.firebaseio.com/v0/newstories.json")
          .then(storyIds => {            
            console.log(
              "fromPrevStoriesIds INSIDE doPolling() ",
              fromPrevStoriesIds
            );

            if (
              fromPrevStoriesIds !== undefined &&
              !isEqual(fromPrevStoriesIds.sort(), storyIds.data.slice(0, 2).sort())
            ) {
              setPrevStoriesIds(storyIds.data.slice(0, 2));
              setNoOfNewStoryAfterPolling(
                differenceWith(
                  prevStoriesIds.sort(),
                  storyIds.data.slice(0, 2).sort(),
                  isEqual
                ).length
              );
              getAllNewStory(storyIds);
              setOpenNewItemAddedConfirmSnackbar(true);              
            }
          });
      }, 5000);
    };

    doPolling();

    // return () => {
    //   console.log("cleaning up");
    //   clearInterval(timer);
    // };
  }, [rowsPerPage, noOfNewStoryAfterPolling]);


  let renderedStoriesOnPage = [];
  const getDataToRender = (() => {
    renderedStoriesOnPage = fetchedData.map(i => {
      return Object.values(i);
    });
    return renderedStoriesOnPage;
  })();


  const columnsOptions = [
    {
      name: "Author",
      sortDirection: tableState
        ? tableState.columns && tableState.columns[0].sortDirection
        : null
    },

    {
      name: "score",
      sortDirection: tableState
        ? tableState.columns && tableState.columns[1].sortDirection
        : null
    },

    {
      name: "title",
      sortDirection: tableState
        ? tableState.columns && tableState.columns[2].sortDirection
        : null
    },

    {
      name: "URL",
      options: {
        filter: false,
        customBodyRender: (value, tableMeta, updateValue) => {
          // console.log("TABLE META IS ", JSON.stringify(tableMeta));
          return (
            <a target="_blank" href={value}>
              {value}
            </a>
          );
        }
      }
    }
  ];

  const options = {
    filter: true,
    selectableRows: false,
    filterType: "dropdown",
    responsive: "stacked",
    selectableRows: "multiple",
    rowsPerPage: tableState ? tableState.rowsPerPage : 10,
    onChangeRowsPerPage: onChangeRowsPerPage,
    activeColumn: tableState ? tableState.activeColumn : 0,
    onTableChange: (action, tableState) => {
      // console.log("taBLE STATE IS ", JSON.stringify(tableState));
      setTableState(tableState);
    }
  };

  return (
    <React.Fragment>
      {console.log("fromPrevStoriesIds INSIDE RETURN --- ", fromPrevStoriesIds)}
      <div
        style={{
          marginLeft: "15px",
          marginTop: "80px",
          display: "flex",
          flexDirection: "row"
        }}
      >
        <h4 style={{ width: "400px", paddingRight: "15px" }}>
          Hacker News top 2
        </h4>
      </div>
      <div>
        {isLoading ? (
          <div className="interactions">
            <div className="lds-ring">
              <div />
              <div />
              <div />
              <div />
            </div>
          </div>
        ) : fetchedData.length !== 0 && renderedStoriesOnPage.length !== 0 ? (
          <MUIDataTable
            title={"Hacker News API top 2 result"}
            data={renderedStoriesOnPage}
            columns={columnsOptions}
            options={options}
          />
        ) : null}
        <NewItemAddedConfirmSnackbar
          openNewItemAddedConfirmSnackbar={openNewItemAddedConfirmSnackbar}
          closeNewItemConfirmSnackbar={closeNewItemConfirmSnackbar}
          noOfNewStoryAfterPolling={noOfNewStoryAfterPolling}
        />
      </div>
    </React.Fragment>
  );
};

export default Dashboard;

1 个答案:

答案 0 :(得分:3)

ref.current不会从usePrevious的返回中返回ref,因为ref.current将在其引用处发生突变,您将能够在useEffect内接收更新后的值,否则它将收到关闭带来的价值

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref;
}

代码:

const fromPrevStoriesIds = usePrevious(prevStoriesIds);

useEffect(() => {
    const fetchData = () => {
      axios
        .get("https://hacker-news.firebaseio.com/v0/newstories.json")
        .then(storyIds => {
          //   console.log("STORY IDs FETCHED ", storyIds.data.slice(0, 2));

          setPrevStoriesIds(storyIds.data.slice(0, 2));
          getAllNewStory(storyIds);
        });
    };
    fetchData();

    const doPolling = () => {
      var timer = setInterval(() => {
        axios
          .get("https://hacker-news.firebaseio.com/v0/newstories.json")
          .then(storyIds => {            
            console.log(
              "fromPrevStoriesIds INSIDE doPolling() ",
              fromPrevStoriesIds.current
            );

            if (
              fromPrevStoriesIds.current !== undefined &&
              !isEqual(fromPrevStoriesIds.current.sort(), storyIds.data.slice(0, 2).sort())
            ) {
              setPrevStoriesIds(storyIds.data.slice(0, 2));
              setNoOfNewStoryAfterPolling(
                differenceWith(
                  prevStoriesIds.sort(),
                  storyIds.data.slice(0, 2).sort(),
                  isEqual
                ).length
              );
              getAllNewStory(storyIds);
              setOpenNewItemAddedConfirmSnackbar(true);              
            }
          });
      }, 5000);
    };

    doPolling();

    // return () => {
    //   console.log("cleaning up");
    //   clearInterval(timer);
    // };
  }, [rowsPerPage, noOfNewStoryAfterPolling]);