简体   繁体   English

React & Redux:即使使用 mapStateToProps,组件状态也不会更新

[英]React & Redux: State of component is not updating even with mapStateToProps

I posted an answer below, but if someone can explain why this is necessary you'll get the bounty, I went through a redux tutorial and feel like I didn't learn about mapDispatchToProps , only mapStateToProps .我在下面发布了一个答案,但如果有人能解释为什么这是必要的,你会得到赏金,我浏览了一个 redux 教程,感觉我没有了解mapDispatchToProps ,只了解mapStateToProps If you can explain at a deeper level what exactly mapStateToProps and mapDispatchToProps are doing and how they are different I'll give you the bounty.如果您能更深入地解释mapStateToPropsmapDispatchToProps在做什么以及它们有何不同,我会给您奖励。


Minimum Reproducible Example最小可重现示例


I have a mapStateToProps function that looks like我有一个 mapStateToProps 函数,看起来像

const mapStateToProps = state => {
  return {
    firstName: state.firstName,
    middleName: state.middleName,
    lastName: state.lastName,
  }
}

const ReduxTabForm = connect(mapStateToProps)(MyTab)

In my MyTab component I have a button that is supposed to be inactive if these 2 field do not have anything entered in, but the state of whether or not the button is disabled does not change在我的MyTab组件中,如果这两个字段没有输入任何内容,我有一个按钮应该处于非活动状态,但是按钮是否被禁用的状态不会改变

function App() {
  const {firstName, lastName} = store.getState().formData

  const isDisabled = () => {
    const {firstName, lastName} = store.getState().form
    const requiredFields = [firstName, lastName]
    alert(requiredFields)
    for(let i = 0; i < requiredFields.length; i=i+1){
      if (!requiredFields[i]){
        return true
      }
    }
    return false
  }

  return (

    <div className="App">
        <div className='bg-light rounded'>
          <div className='px-sm-5 pt-0 px-4 flexCenterCol mx-5'>

            <div>
                <input 
                    type='text'
                    className="form-control"
                    value={store.getState().formData['firstName']}
                    placeholder="First Name"
                    onChange={(e) => {
                        store.dispatch(setFormData({'firstName': e.target.value}))
                    }}
               ></input>
               <input 
                    type='text'
                    className="form-control"
                    value={store.getState().formData['lastName']}
                    placeholder="Last Name"
                    onChange={(e) => {
                        store.dispatch(setFormData({'lastName': e.target.value}))
                    }}
               ></input>

            </div>
            <button
                type="submit"
                disabled={isDisabled()}
            >
                Button
            </button>
        </div>
    </div>
  </div>
 )
}

That alert statement executes on page refresh, but does not execute any time after that when I enter data in. I have checked that the redux state updating and it is.该警报语句在页面刷新时执行,但在我输入数据之后的任何时间都不会执行。我已经检查过 redux 状态是否正在更新。 The button will not update though, and the isDisabled function will not run more than once该按钮不会更新,并且 isDisabled 函数不会多次运行

I don`t know if this will solve your problems, but maybe it is one of the things below: 1 - As Mohammad Faisal said the correct form of calling props should be我不知道这是否会解决您的问题,但也许是以下问题之一: 1 - 正如 Mohammad Faisal 所说,调用 props 的正确形式应该是

const { firstName, lastName } = props;

2 - Instead of reduxTabForm, maybe you could use this instead: 2 - 而不是 reduxTabForm,也许你可以用它来代替:

export default connect(mapStateToProps)(MyTab);

3 - And finally, maybe it is an error in the "isDisabled": 3 - 最后,也许是“isDisabled”中的错误:

for(let i = 0; i < requiredFields.length; i=i+1){
  if (!requiredFields){
    return false
  }
}

If you look carefully, you can see that you are not checking if there is an error inside requeiredFields, your are looking if that doesnt exist if (!requiredFields) , maybe changing the condition to if(!requiredFields[i]) so it check each variable and not if the array doesn`t exists.如果仔细查看,您会发现您没有检查 requeiredFields 中是否存在错误,您正在查看if (!requiredFields)是否不存在,可能将条件更改为if(!requiredFields[i])以便检查每个变量,而不是数组不存在。

Edit: the return condition is correct?编辑:退货条件是否正确? Returning False when something doesn`t exists?当某些东西不存在时返回 False ?

const ReduxTabForm = connect(mapStateToProps,null)(MyTab)

Try this code snippet, as you are not passing dispatch function to your component.试试这个代码片段,因为你没有将 dispatch 函数传递给你的组件。 It is better to pass null value.最好传递空值。 mapdispatchtoProps is the same basic theory of mapStateToProps.You are storing the function inside the store(which usually in actions) and when rendering the component you attach those function in store to your props. mapdispatchtoProps 与 mapStateToProps 的基本理论相同。您将函数存储在 store 中(通常在 action 中),并且在渲染组件时,您将 store 中的这些函数附加到 props。 After rendering the component you will be able to run the functions which are in store.渲染组件后,您将能够运行存储中的功能。

I looked at your reducer code and it looks like this:我查看了您的减速器代码,它看起来像这样:

...
const reducer = combineReducers({
  formData: formReducer,
})

export default reducer

Which means your redux state structure is like this:这意味着你的 redux 状态结构是这样的:

state = {
  formData: {
    firstName: <value>,
    middleName: <value>,
    lastName: <value>,
  }
}

Solution解决方案

So, to make your component re-render when the redux state is changed, you need to subscribe to the correct state variables in your mapStateToProps function.因此,要在 redux 状态更改时重新渲染组件,您需要在 mapStateToProps 函数中订阅正确的状态变量。 Change it to this and will work:将其更改为此并将工作:

const mapStateToProps = state => {
  return {
    firstName: state.formData.firstName, // Note that I added "formData"
    middleName: state.formData.middleName,
    lastName: state.formData.lastName
  }
}

Couple of side notes:几个边注:

  1. It's a better to use the props instead of directly accessing the redux store.最好使用 props 而不是直接访问 redux store。
  2. For debugging, console.log is preferred over alert .对于调试, console.log优于alert React DevTools and Redux DevTools are even better. React DevTools 和 Redux DevTools 甚至更好。

your state values are passed to your component as props.您的状态值作为道具传递给您的组件。 so if you want to access them inside component you should do something like this所以如果你想在组件内部访问它们,你应该做这样的事情

 const {firstName, lastName} = props

What you could do is something like:你可以做的是:

<button 
      type="submit"
      disabled={!fistname && !lastname}
    />

this way if any of your fields be falsy, button is disabled.这样,如果您的任何字段为假,则按钮将被禁用。 (empty string is falsy) (空字符串为假)

The React redux documentation also explains mapDispatchToProps React redux 文档也解释了 mapDispatchToProps

If you call your action called setFormData from your redux/actions.js then you will be mapping the action setFormData (which is an object) to your component.如果您从您的redux/actions.js调用名为setFormData ,那么您将把操作setFormData (它是一个对象)映射到您的组件。 The action setFromData replace mapDispatchToProps .操作setFromData替换mapDispatchToProps This is what the documentation says about mapping actions to your components这是文档关于将操作映射到组件的内容

If it's an object full of action creators, each action creator will be turned into a prop function that automatically dispatches its action when called.如果它是一个充满动作创建者的对象,那么每个动作创建者都会变成一个 prop 函数,在调用时自动调度它的动作。


To fix your problem, change your connect function call to this.要解决您的问题,请将连接函数调用更改为此。

const ReduxApp = connect(
  setFormData,
  mapStateToProps
)(App)

Your issues is with this code isDisabled()您的问题与此代码isDisabled()

     const isDisabled = () => {
    const {firstName, lastName} = store.getState().form
    const requiredFields = [firstName, lastName]
    alert(requiredFields)
    for(let i = 0; i < requiredFields.length; i=i+1){
      if (!requiredFields){
        return false
      }
    }
    return true
  }

You are trying to test in loop !requiredFields , an array you created which always return false, even if it doesn't have values.您正在尝试在循环!requiredFields进行测试,这是您创建的一个数组,它始终返回 false,即使它没有值。 It's a reference type.它是一个引用类型。 What you can do now is你现在能做的是

const isDisabled = () => {
    const {firstName, lastName} = store.getState().form
    const requiredFields = [firstName, lastName]
    alert(requiredFields)
    for(let i = 0; i < requiredFields.length; i=i+1){
      if (!requiredFields[i]){
        return false
      }
    }
    return true
  }

Your loop will check firstNAme and LastName values if they are undefined or not and test should respond with this.您的循环将检查firstNAmeLastName值是否未定义,并且测试应该对此做出响应。

The problem is in the mapStateToProps method.问题出在mapStateToProps方法中。 Your code is setting firstNAme and lastName as formData within state object and you are trying to access it from state object directly.您的代码将firstNAmelastName设置为state 对象中的formData 并且您试图直接从 state 对象访问它。

This codesandbox has your project updated with the fix.代码沙盒已使用修复程序更新您的项目。 I didn't fix any other thing in your code so everything is as it is only mapStateToProps should be:我没有在您的代码中修复任何其他内容,所以一切都是因为它只是mapStateToProps应该是:

const mapStateToProps = (state) => {
  return {
    firstName: state.formData["firstName"],
    middleName: state.middleName,
    lastName: state.formData["lastName"]
  };
};

and this will fix your issue.这将解决您的问题。 mapStateToProps is kind of a projection function which will project entire store to some object which will only have properties based on requirements of your component. mapStateToProps是一种投影函数,它将整个商店投影到某个对象,该对象仅具有基于组件要求的属性。

Redux re-render components only when store state changes. Redux 仅在存储状态更改时重新渲染组件。 Check if you are only updating store's state property not whole state because redux compare state by comparing their references, so if you are doing something like this inside your reducer:检查您是否只更新商店的状态属性而不是整个状态,因为 redux 通过比较它们的引用来比较状态,因此,如果您在减速器中执行以下操作:

State.firstName = action.payload.firstName;

return State;

Then change this to this:然后把它改成这样:

return {...State, firstName: action.payload.firstName}

Note: If you unable to grasp that then kindly provide your reducer code too so that I can see how you are updating your store state.注意:如果您无法理解,请也提供您的减速器代码,以便我了解您如何更新商店状态。

  1. Looking at your MRE on GitHub, the first thing I want to say is that your button will only have one state and it is the one that the isDisabled() method returns when you refresh the page.在 GitHub 上查看您的 MRE,我想说的第一件事是您的按钮将只有一个状态,并且是刷新页面时isDisabled()方法返回的状态。 This is because the App component it's not getting refresh every time you write on the input fields, so you will never be able to make it change.这是因为 App 组件每次在输入字段上写入时都不会刷新,因此您将永远无法对其进行更改。

  2. you need to subscribe to the correct state variables in your mapStateToProps function.您需要在 mapStateToProps 函数中订阅正确的状态变量。 Like this:像这样:

const mapStateToProps = state => {
    return {
        firstName: state.formData.firstName,
        middleName: state.formData.middleName,
        lastName: state.formData.lastName
    }
}
  1. So now that you have this right, you have to introduce this props into your component, like this:所以现在你有了这个权利,你必须将这个 props 引入你的组件,像这样:
     function  App({firstName, lastName}) { ...
  1. Another thing to add, is that your are not initializing the states when you create your reducer in reducer.js .要补充的另一件事是,当您在reducer.js创建减速器时,您没有初始化状态。 If you don't do this, your initial states for firstName and lastName will be null.如果您不这样做,则firstNamelastName初始状态将为空。 Here is how you should do it:以下是您应该如何操作:
import {combineReducers} from 'redux'
import {SET_FORM_DATA} from './actions'

const initialState = {
  firstName: "",
  lastName: ""
}

const formReducer = (state = initialState, action) => {
  if (action.type === SET_FORM_DATA){
    return {
      ...state,
      ...action.payload
    }
  }else{
    return state
  }
}

const reducer = combineReducers({
  formData: formReducer,
})

export default reducer
  1. Finally you have to update App:最后你必须更新应用程序:
function  App({firstName, lastName}) {
  return (
      <div className="App">
        <div className='bg-light rounded'>
          <div className='px-sm-5 pt-0 px-4 flexCenterCol mx-5'>

            <div>
            <input 
              type='text'
              className="form-control"
              placeholder="First Name"
              onChange={(e) => {
                store.dispatch(setFormData({'firstName': e.target.value}));
                console.log(firstName === "h");
              }}
            ></input>
            <input 
              type='text'
              className="form-control"
              placeholder="Last Name"
              onChange={(e) => {
                store.dispatch(setFormData({'lastName': e.target.value}))
                alert(JSON.stringify(store.getState()))

              }}
            ></input>

            </div>
            <button
              type="submit"
              disabled={firstName == "" || lastName == ""}
            >
              Button
            </button>
          </div>
        </div>
      </div>
  );
}

So in this way you will be able to update the states from your store and have the dynamic behavior that you were looking for.因此,通过这种方式,您将能够从您的商店更新状态并拥有您正在寻找的动态行为。

In isDisabled function you read data from form : const {firstName, lastName} = store.getState().form but I guess they are saved to formData .isDisabled函数中,您从form读取数据: const {firstName, lastName} = store.getState().form但我猜它们被保存到formData

Changing const {firstName, lastName} = store.getState().form to const {firstName, lastName} = store.getState().formData should help.const {firstName, lastName} = store.getState().form更改为const {firstName, lastName} = store.getState().formData应该会有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM