【问题标题】:React JS Functional component renders twice on onChange event and gets always current value of array and then new valueReact JS 功能组件在 onChange 事件上呈现两次,并始终获取数组的当前值,然后获取新值
【发布时间】:2020-05-25 01:14:39
【问题描述】:

我有一个 React 功能组件,它呈现一个 JSX,其中包含一个段落、一个来自 Select React JS 库的下拉列表和一个来自 Nivo 库的折线图。

当我第一次渲染组件时,我有一个 useEffect 钩子,它可以帮助我从后端检索数据,一个列表。数据基于 mode 值从后端返回。

这个模式可以是:

我有一个 useState const [mode, setMode] = useState("week")

“选择”下拉菜单有 onChange 事件侦听器,可帮助我设置新模式

setMode(e.value) - 可以是

当我设置新模式时,它会返回后端并检索包含数据的新列表。

我的问题如下:

  1. 我从下拉列表中选择一个新值
  2. 我的组件重新渲染并获取我的数组的当前值
  3. 然后它将 mode 更改为新值
  4. 然后useEffect根据新模式获取新数据,并用新列表重新渲染。

例如:

我当前的模式值是week。数据来自后端并在图表中正确呈现。我选择了一个新模式,并致电setMode("month")。 首先发生的事情:

  1. 组件重新渲染
  2. 获取图表的当前数据
  3. useEffect 对新模式月份数据发出 GET 请求,并在图表上设置新数据
  4. 如果当前模式是 month 并且我想显示一个带有 year 的图表。再次在下拉 onChange 上,我将模式设置为 year,但第一个组件重新渲染获取当前数组值,即 month,然后返回 year。

我不想每次从下拉列表中选择一个新值时都先获取数组的当前值。我只想使用新值设置模式,并使用新列表从服务器检索数据。

这是我的代码:

反应组件:

import { useFeedbackDistribution } from "../../api/analytics.firebase";
import { DISTRIBUTION_MODE } from "../../utils/query_filters";
// UI
import Loading from "../App/loadingIndicator";
import { ErrorSolid } from "../Errors/error.solid";
import { ResponsiveLine } from "@nivo/line";
import Select from "react-select";

import {
    dayTimeScaleProperties,
    monthTimeScaleProperties,
    yearTimeScaleProperties
} from "../../utils/charts/utils";

const options = [
    { value: DISTRIBUTION_MODE.WEEK, label: DISTRIBUTION_MODE.WEEK },
    { value: DISTRIBUTION_MODE.MONTH, label: DISTRIBUTION_MODE.MONTH },
    { value: DISTRIBUTION_MODE.YEAR, label: DISTRIBUTION_MODE.YEAR }
];

const FeedbackDistributionCard = user => {
    const [mode, setMode] = useState({
        value: DISTRIBUTION_MODE.WEEK,
        label: DISTRIBUTION_MODE.WEEK
    });
    const [{ isLoading, isError, data }] = useFeedbackDistribution(
        mode.value,
        []
    );

    function load(data) {
        switch (mode.value) {
            case DISTRIBUTION_MODE.WEEK:
                return dayTimeScaleProperties(data);
            case DISTRIBUTION_MODE.MONTH:
                return monthTimeScaleProperties(data);
            default:
                return yearTimeScaleProperties(data);
        }
    }

    return (
        <div className="w-full h-64 sm:w-full md:w-full lg:w-3/4 xl:w-3/4 mb-4 bg-white rounded-lg shadow">
            <div className="w-full h-full ">
                {isError && <ErrorSolid />}
                {isLoading ? (
                    <Loading />
                ) : (
                    <div className="h-full w-full flex-col shadow p-6">
                        <div>
                            <p className="font-bold float-left inline-block">
                                Feedback by {mode.value}
                            </p>
                            <Select
                                className="w-40 z-50 float-right"
                                options={options}
                                onChange={e => {
                                    setMode({ label: e.label, value: e.value });
                                }}
                                value={mode}
                            />
                        </div>
                        <div className="h-full w-full">
                            <FeedbackDistributionLineChart
                                properties={load(data)}
                            /> 
                        </div>
                    </div>
                )}
            </div>
        </div>
    );
};

export default FeedbackDistributionCard;

const FeedbackDistributionLineChart = ({ properties }) => (
    <ResponsiveLine
        {...properties}
        enablePoints={false}
        enableGridX={false}
        enableGridY={false}
        colors={{ scheme: "category10" }}
        margin={{ top: 20, right: 30, bottom: 40, left: 40 }}
        animate={true}
        enableSlices={"x"}
        yScale={{
            type: "linear",
            stacked: false
        }}
        axisLeft={{
            legend: "total",
            legendPosition: "middle",
            legendOffset: -30,
            tickValues: 5
        }}
        curve={"basis"}
        enablePointLabel={true}
        useMesh={true}
        enableSlices={false}
    />
);

useEffect 自定义钩子

const feedbackDistributionReducer = (state, action) => {
    switch (action.type) {
        case "FETCH_INIT":
            return { ...state, isLoading: true, isError: false };
        case "FETCH_SUCCESS":
            return {
                ...state,
                isLoading: false,
                isError: false,
                data: action.payload
            };
        case "FETCH_ERROR":
            return {
                ...state,
                isLoading: false,
                isError: true,
                data: action.payload
            };
        default:
            throw new Error("Could not fetch feedback distribution");
    }
};

const useFeedbackDistribution = (mode, initialData) => {
    const [state, dispatch] = useReducer(feedbackDistributionReducer, {
        isLoading: false,
        isError: false,
        data: initialData
    });

    useEffect(() => {
        let didCancel = false;
        async function load(mode) {
            console.log(mode);
            dispatch({ type: "FETCH_INIT", payload: null });
            try {
                var feedbackDistribution = firebase
                    .functions()
                    .httpsCallable("feedbackDistribution");
                let result = await feedbackDistribution({ mode: mode });
                var mappedResult = result.data.map(function(item) {
                    var info = { y: item.totalCount, x: item.time };
                    return info;
                });
                const distribution = [
                    {
                        id: "id",
                        data: mappedResult
                    }
                ];

                if (!didCancel) {
                    dispatch({ type: "FETCH_SUCCESS", payload: distribution });
                }
            } catch (err) {
                if (!didCancel) {
                    dispatch({ type: "FETCH_ERROR", payload: error });
                }
            }
        }
        load(mode);
        return () => {
            didCancel = true;
        };
    }, [mode]);

    return [state];
}; 

谢谢!

【问题讨论】:

    标签: reactjs react-hooks jsx


    【解决方案1】:
    • 问题是您直接从 onChange 更改 useState 值,这会导致重新渲染并触发挂钩,您需要直接在 onChange 中调度 fetch 调用。祝你好运! :)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-06-20
      • 2022-01-18
      • 2020-08-04
      • 2022-10-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多