【问题标题】:React context useReducer is not updating correctly反应上下文 useReducer 没有正确更新
【发布时间】:2019-06-10 19:28:00
【问题描述】:

我无法让 reducer 与 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;

【问题讨论】:

  • useState 挂钩不返回 3 个值。它只返回 state 和 setter 函数。

标签: javascript reactjs react-hooks


【解决方案1】:

有几件事,你做得不对。

首先,您正在使用带有调度方法的 initialState,而是尝试使用不正确的第三个参数从 useState 获取此调度值

第二,由于你使用reducer模式,最好使用useReducer hook

第三,千万不要对reducer中的数据进行过滤,否则下次要显示所有数据时,会丢失完整的数据,只保留过滤后的数据。相反,你必须有它的选择器。

相关代码:

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)}</pre>
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(
  <Provider>
    <ButtonBar />
  </Provider>,
  rootElement
);

Working demo

【讨论】:

    【解决方案2】:

    您错误地使用了 useReducer Hook,仅仅因为您在组件中使用了 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");
      };
    

    您正在使用 reducer 正确更新您的状态,但它只会更新本地组件状态而不是全局上下文状态。

    如果您来自 redux,这似乎违反直觉。

    在上下文中,状态在父组件中包含和更改,因此只需将上述代码移动到父组件,然后通过上下文访问它。

    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");
      };
    

    将状态和函数传递给 value prop

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

    然后你可以在子组件中通过 value 属性访问这些值和函数。

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

    本质上,这就是您将上下文与组件连接起来的方式。

    【讨论】:

    • 我不明白这一行。 “您正在使用减速器正确更新您的状态,但它只会更新本地组件状态而不是全局上下文状态。”我在哪里可以阅读更多关于它的信息?谢谢
    猜你喜欢
    • 1970-01-01
    • 2019-06-07
    • 2021-10-15
    • 2023-01-17
    • 1970-01-01
    • 2020-07-03
    • 1970-01-01
    • 2021-08-05
    • 1970-01-01
    相关资源
    最近更新 更多