React JS Функциональный компонент дважды визуализирует событие onChange и всегда получает текущее значение массива, а затем новое значение - PullRequest
0 голосов
/ 09 февраля 2020

У меня есть функциональный компонент React, который рендерит JSX, содержащий абзац, раскрывающийся список из библиотеки Select React JS и линейный график из библиотеки Nivo.

При первом отображении компонента У меня есть useEffect ловушка, которая помогает мне получать данные, список, из бэкэнда. Данные возвращаются из бэкэнда на основе значения mode .

Этот mode может быть:

  • неделя
  • month
  • year

, и у меня для него есть useState const [mode, setMode] = useState("week").

В раскрывающемся списке Select есть прослушиватель событий onChange, который помогает мне установить новый режим

setMode(e.value) - может быть неделя , месяц или год

Когда я устанавливаю новый режим , он возвращается к бэкэнду и получает новый список с данными.

Моя проблема заключается в следующем:

  1. Я выбираю новое значение из раскрывающегося списка
  2. Мой компонент повторно отображает и получает текущее значение моего массива
  3. Затем он меняет режим на новое значение
  4. Затем useEffect получает новые данные на основе нового режима и повторного рендеринга с новым списком.

Например:

Мое текущее значение в режиме week . Данные поступают из бэкэнда и корректно отображаются на графике. Я выбираю новый режим месяц и звоню setMode("month"). Первые вещи, которые происходят:

  1. Компонент перерисовывает
  2. Получает текущие данные для диаграммы, которые составляют неделя
  3. useEffect запрос GET для нового режима month data и устанавливает новые данные на графике
  4. Если текущий режим month и я хочу показать диаграмму с год . Снова в раскрывающемся списке onChange Я установил режим на год , но первый компонент повторно отображает текущее значение массива, равное месяц , а затем возвращает данные для год .

enter image description here

Я не хочу получать текущее значение массива первым при каждом выборе нового значения из падать. Я только хочу установить Mode с новым значением и получить данные с сервера с новым списком.

Вот мой код:

React Component:

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 custom hook

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];
}; 

Спасибо!

1 Ответ

0 голосов
/ 09 февраля 2020
  • проблема в том, что вы меняете значение useState непосредственно из onChange, которое вызывает повторный рендеринг и запускает ловушку, вам нужно отправить вызов fetch непосредственно в onChange. Удачи! :)
...