从提交处理函数内部更新 react/redux 状态

Posted

技术标签:

【中文标题】从提交处理函数内部更新 react/redux 状态【英文标题】:Updating react/redux state from inside a submit handler function 【发布时间】:2021-07-24 19:26:49 【问题描述】:

我目前正在开发一个投票应用程序,该应用程序应该按顺序呈现问题列表并将答案发布到服务器。我在处理答案方面没有问题,但循环问题给我带来了一些麻烦。

这是我的代码流程:

PollContainer.js - 组件

import React,  useState, useEffect  from 'react'
import  useSelector, useDispatch  from 'react-redux'
import Question from './Questions/Question';
import  pushAnswers  from '../../actions/answers';
import  incrementCounter  from '../../actions/utils';
import Thanks from './Thanks'


const PollContainer = () => 

    const dispatch = useDispatch();

    const questions = useSelector(state => state.questions); // an array of questions


// a counter redux state which should increment at every click of 'submit' inside a question

    const utils = useSelector(state => state.utils); 
    let activeQuestion = questions[utils.counter]; 


// function passed as a prop to a singular Question component to handle submit of an answer

    const pushSingleAnswer = (answer) => 
        let answersUpdate = state.answers;
        answersUpdate.push(answer);
        console.log(`counter:$utils.counter`) // logs 0 everytime I click submit, while redux dev tools show that utils.counter increments at every click

        if (utils.counter < questions.length ) 
            setState(...state, answers: answersUpdate, activeQuestion: activeQuestion);
            dispatch(incrementCounter());
         else
            dispatch(pushAnswers(state.answers));
            setState(...state, isFinished: true);
        
    ;
         
    const [state, setState] = useState( 
        isFinished: false,
        activeQuestion: questions[0],
        answers: [],
        pushSingleAnswer
         )

    return (
        (utils.isLoading) ? (
            <h1>Loading questions...</h1>
        ) : (
        <div>
            
            (!state.isFinished && <Question  ...state />)

            (state.isFinished && <Thanks/>) 

        </div>
    ))


export default PollContainer;

incrementCounter 动作:

import * as types from "./types";


export const incrementCounter = () => 
    return 
       type: types.INCREMENT_COUNTER,
    

utils.js - 减速器

// reducer handles what to do on the news report (action)

import * as types from '../actions/types';

const initialState = 
    isLoading: false,
    error: null,
    counter: 0


export default (utils = initialState, action) => 


    switch(action.type) 

        case types.LOADING_DATA:
            return ...utils, isLoading: true;

        case types.DATA_LOADED:
            return ...utils, isLoading: false;

        case types.ACTION_FAILED:
            return ...utils, error: action.error;

        case types.INCREMENT_COUNTER:
            return ...utils, counter: utils.counter + 1 // here is the incrementing part

        default:
            return utils;
         
    

传递给pushSingleAnswer 函数的utils.counter 不会增加,但是redux 开发工具告诉我,每次在Question 组件中单击submit 时它都会增加。因此,它不会呈现下一个问题。 Question 组件中的提交处理程序就是这样的:

    const handleSubmit = (e) => 
        e.preventDefault();
        props.pushSingleAnswer(state);
    ;

我也尝试过:

         useEffect(() => 
            dispatch(incrementCounter()),
            [state.answers]
        );

预计每次更新 state.answers 时它都会增加,但它也不起作用。更何况 redux-dev-tools 中的 counter 也不会增加。

如果有任何建议,我将不胜感激,这是我第一个认真的 react-redux 项目,我真的很喜欢使用这些技术。但是我不太明白 react 如何决定在状态变化时渲染内容。

【问题讨论】:

【参考方案1】:

问题

    您正在关闭存储在 state 中并传递给 Question 组件的 pushSingleAnswer 回调中的初始 counter 状态。 您正在改变处理程序中的状态对象。

代码:

const pushSingleAnswer = (answer) => 
  let answersUpdate = state.answers; // <-- save state reference
  answersUpdate.push(answer); // <-- state mutation
  console.log(`counter:$utils.counter`) // <-- initial value closed in scope

  if (utils.counter < questions.length ) 
    setState(
      ...state, // <-- persists closed over callback/counter value
      answers: answersUpdate,
      activeQuestion: activeQuestion,
    );
    dispatch(incrementCounter());
   else
    dispatch(pushAnswers(state.answers));
    setState( ...state, isFinished: true );
  
;

const [state, setState] = useState( 
  isFinished: false,
  activeQuestion: questions[0],
  answers: [],
  pushSingleAnswer // <-- closed over in initial state
);

(!state.isFinished && <Question  ...state />) // <-- stale state passed

解决方案

不要将回调存储在状态中并使用功能状态更新。

const pushSingleAnswer = (answer) => 
  console.log(`counter:$utils.counter`) // <-- value from current render cycle

  if (utils.counter < questions.length ) 
    setState(prevState => (
      ...prevState, // <-- copy previous state
      answers: [
        ...prevState.answers, // <-- copy previous answers array
        answer // <-- add new answer
      ],
      activeQuestion,
    ));
    dispatch(incrementCounter());
   else
    dispatch(pushAnswers(state.answers));
    setState( ...state, isFinished: true );
  
;

const [state, setState] = useState( 
  isFinished: false,
  activeQuestion: questions[0],
  answers: [],
);

!state.isFinished && (
  <Question  ...state  pushSingleAnswer=pushSingleAnswer />
)

【讨论】:

谢谢,我不确定是否可以同时将 state 和 props 传递给组件,但现在看起来很简单!

以上是关于从提交处理函数内部更新 react/redux 状态的主要内容,如果未能解决你的问题,请参考以下文章

如何在react-native / redux中处理外部数据库更新?

从 Redux-Saga 函数更新另一个 Redux 存储的最佳方法

无效的挂钩调用。钩子只能在反应函数组件内部使用...... useEffect,redux

React+redux-form - 提交后重定向

React/Redux - 你啥时候应该从 API 获取新数据来更新你的商店?

如何在 mapDispatchToProps 或更新道具后运行回调函数,使用 react redux?