Реактивный контекст useReducer не обновляется правильно - PullRequest
7 голосов
/ 10 июня 2019

У меня проблемы с настройкой редуктора для работы с контекстом React . В buttonbar.js есть две кнопки, которые должны обновлять состояние. Состояние будет обновляться путем фильтрации данных в текущем. Кнопки нажимаются, и я не получаю никаких ошибок, но он также ничего не делает. Я думаю, что проблема с редуктором.

context.js

import React, { useState, useEffect } from "react";
import * as moment from "moment";
import axios from "axios";

export const Context = React.createContext();

const url = "https://projects.fivethirtyeight.com/polls/polls.json";

export const filterReducer = (state, action) => {
  switch (action.type) {
    case "SHOW_ALL":
      return state.polls;
    case "SHOW_APPROVAL":
      return state.polls.filter(e => e.type === "trump-approval");
    default:
      return state.polls;
  }
};

export function Provider({ children }) {
  let intialState = {
    polls: [],
    dispatch: action => this.setState(state => filterReducer(state, action))
  };

  const [state, setState, dispatch] = useState(intialState);

  useEffect(() => {
    var dateRange = moment()
      .subtract(7, "days")
      .calendar();

    axios
      .get(url)
      .then(res => {
        setState({
          polls: res.data
            .filter(e => Date.parse(e.endDate) >= Date.parse(dateRange))
            .reverse()
        });
      }, [])
      .catch(error => console.log(error));
  }, []);

  return (
    <Context.Provider value={[state, setState, dispatch]}>
      {children}
    </Context.Provider>
  );
}

// export const Consumer = Context.Consumer;

buttonbar.js

import React, { useContext, useState, useEffect, useReducer } from "react";
import { Context, filterReducer } from "../context";

const ButtonBar = () => {
  const [state, setState] = useContext(Context);
  const [filter, dispatch] = useReducer(filterReducer, state);

  const showAll = () => {
    dispatch({ type: "SHOW_ALL" });
    console.log("showAll clicked");
  };
  const showApproval = () => {
    dispatch({ type: "SHOW_APPROVAL" });
    console.log("showApproval clicked");
  };

  return (
    <div class="mb-2">
      <button class="btn btn-primary btn-sm" name="all" onClick={showAll}>
        All
      </button>{" "}
      <button
        class="btn btn-primary btn-sm"
        name="trump approval"
        onClick={showApproval}
      >
        Trump Approval
      </button>
    </div>
  );
};

export default ButtonBar;

Ответы [ 2 ]

2 голосов
/ 20 июня 2019

Есть несколько вещей, которые вы делаете неправильно.

Сначала , вы используете initialState с методом отправки и вместо этого пытаетесь получить это значение отправки из useState, используя третий аргумент, который является неправильным

Секунда , так как вы используете схему редуктора, лучше использовать, если useReducer hook

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

Соответствующий код:

<code>import React, {
  useEffect,
  useContext,
  useReducer,
  useMemo,
  useState
} from "react";
import ReactDOM from "react-dom";

import "./styles.css";
import moment from "moment";
import axios from "axios";

export const Context = React.createContext();

const url = "https://projects.fivethirtyeight.com/polls/polls.json";

export const filterReducer = (state, action) => {
  switch (action.type) {
    case "ADD_POLLS":
      console.log(action.payload);
      return action.payload;
    default:
      return state.polls;
  }
};

export function Provider({ children }) {
  const [state, dispatch] = useReducer(filterReducer);

  useEffect(() => {
    var dateRange = moment()
      .subtract(7, "days")
      .calendar();

    axios
      .get(url)
      .then(res => {
        dispatch({
          type: "ADD_POLLS",
          payload: res.data
            .filter(e => Date.parse(e.endDate) >= Date.parse(dateRange))
            .reverse()
        });
      }, [])
      .catch(error => console.log(error));
  }, []);

  return (
    <Context.Provider value={[state, dispatch]}>{children}</Context.Provider>
  );
}
const ButtonBar = () => {
  const [polls] = useContext(Context);
  const [state, setState] = useState(polls);
  useEffect(() => {
    setState(polls);
  }, [polls]);
  const filterResult = useMemo(() => {
    return filter => {
      switch (filter) {
        case "SHOW_ALL":
          setState(polls);
          break;
        case "SHOW_APPROVAL":
          setState(polls.filter(e => e.type === "trump-approval"));
          break;
        default:
          return;
      }
    };
  }, [polls]);

  return (
    <div class="mb-2">
      <button
        class="btn btn-primary btn-sm"
        name="all"
        onClick={() => filterResult("SHOW_ALL")}
      >
        All
      </button>{" "}
      <button
        class="btn btn-primary btn-sm"
        name="trump approval"
        onClick={() => filterResult("SHOW_APPROVAL")}
      >
        Trump Approval
      </button>
      <div>{(state || []).length}</div>
      <pre>{JSON.stringify(state, null, 4)}
); }; const rootElement = document.getElementById ("root"); ReactDOM.render ( , RootElement );

Рабочая демоверсия

0 голосов
/ 14 июня 2019

Использование хука useReducer неправильно из-за того, что вы используете хук useReducer в своем компоненте, не означает, что вы обновляете состояние глобального контекста.

Так в вашем buttonbar.js

  const [filter, dispatch] = useReducer(filterReducer, state);

  const showAll = () => {
    dispatch({ type: "SHOW_ALL" });
    console.log("showAll clicked");
  };
  const showApproval = () => {
    dispatch({ type: "SHOW_APPROVAL" });
    console.log("showApproval clicked");
  };

Вы корректно обновляете свое состояние, используя редуктор, но оно будет обновлять только состояние локального компонента, а не состояние глобального контекста.

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

В контексте состояние содержится и изменяется в родительском компоненте, поэтому просто переместите вышеуказанный код в родительский компонент, а затем получите доступ к нему через контекст.

export function Provider({ children }) {
  let intialState = {
    polls: [],
    dispatch: action => this.setState(state => filterReducer(state, action))
  };

  // 2 args not 3
  const [state, setState] = useState(intialState);

  const [filter, dispatch] = useReducer(filterReducer, state);

  const showAll = () => {
    dispatch({ type: "SHOW_ALL" });
    console.log("showAll clicked");
  };
  const showApproval = () => {
    dispatch({ type: "SHOW_APPROVAL" });
    console.log("showApproval clicked");
  };

передать состояние и функции в значение prop

   <Context.Provider value={{
                          showAllProp: () => showAll(),
                          showApprovalProp: () => showApproval(),
                          filterProp: filter }}>
      {children}
    </Context.Provider>

Затем вы можете получить доступ к этим значениям и функциям в дочернем компоненте с помощью значения props.

   const context = useContext(Context);  

  <button class="btn btn-primary btn-sm" name="all" onClick={context.showAllProp}>
    All
  </button>{" "}
  <button
    class="btn btn-primary btn-sm"
    name="trump approval"
    onClick={context.showApprovalProp}
  >

По сути, это то, как вы связываете свой контекст с вашими компонентами.

...