【问题标题】:Populating Redux-Form with initial values from Redux store使用 Redux 存储中的初始值填充 Redux-Form
【发布时间】:2019-03-03 14:04:26
【问题描述】:

在我的一生中,我无法让我的 Redux-Form 填充初始值。我觉得我已经查看了那里的每个 SO 问题/答案,但到目前为止还没有任何帮助。

以下是我的代码的一些相关部分:

class Profile extends React.Component {

  render() {
    return (
      <form>
        <div>
          <Field type="text" name="firstName" label="First Name" component={rfField} />
        </div>
      </form>
    );
  }
}

const mapStateToProps = (state) => {
  return {
    initialValues: {
      firstName: state.getIn(['user', 'firstName'])
    }
  };
};

const profileForm = reduxForm({
  form: 'profile',
  enableReinitialize: true
})(Profile);

const withConnect = connect(mapStateToProps);
const withReducer = injectReducer({ key: 'profile', reducer });
const withSaga = injectSaga({ key: 'profile', saga });

export default compose(withReducer, withSaga, withConnect)(profileForm);

状态具有我在mapStateToProps 中从中获取的值,但是该字段不显示初始值;它仍然是空的。如果我将state.getIn(...) 更改为文字“测试”,则该字段保持为空。如果我将initialValues 移动到reduxForm(...) 调用,仅使用“测试”而不是state.getIn(...),firstName 字段会正确显示“测试”。

我认为这与我使用 reduxForm、connect 和 compose 函数的方式有关。这是 react-redux-boilerplate 项目的设置方式,所以我只是使用该范例。

我绝对不是 React/Redux 专家,所以如果需要任何其他信息,请告诉我。谢谢!

来自 package.json:

react: 16.4.0
redux: 4.0.0
react-redux: 5.0.7
redux-form: 7.4.2

【问题讨论】:

    标签: reactjs redux react-redux redux-form


    【解决方案1】:

    再次更新!

    由于 mapStateToProps 似乎是异步设置的,因此您需要在 componentDidUpdate 方法中结合使用 React state 和 this.props.initialize()。

    this.props.formFields(或您在Redux 状态下命名的任何名称)需要遵循Field 的name 中指定的相同命名约定。例如:{ firstName: "", lastName: "" } 应该匹配 &lt;Field name="firstName" ... /&gt; &lt;Field name="lastName" .../&gt;。

    如果您计划允许用户编辑输入,您还需要keepDirtyOnReinitialize: true - 否则,不管输入显示什么,它都会提交初始化值。

    工作示例:https://codesandbox.io/s/zm3mqw2m4

    下面的示例在类的 componentDidMount 方法中触发 Redux 操作 (this.props.asyncFormFields),显示一个微调器,然后检查类的 componentDidUpdate 方法中的 this.props.formFields 是否已更改。如果 this.props.formFields 被更改,它会将 this.state.isLoading 设置为 false,然后提示使用 this.props.formFields 数据初始化 Redux Form 字段。

    SimpleForm.js

    import React, { Component } from "react";
    import { reduxForm } from "redux-form";
    import { connect } from "react-redux";
    import { asyncFormFields } from "../actions";
    import ShowError from "../components/ShowError";
    import ShowForm from "../components/ShowForm";
    import Spinner from "../components/Spinner";
    
    class SimpleForm extends Component {
      state = {
        err: "",
        isLoading: true
      };
    
      componentDidUpdate = (prevProps, prevState) => {
        if (this.props.formFields !== prevProps.formFields) {
          this.setState({ isLoading: false }, () =>
            this.props.initialize({ ...this.props.formFields })
          );
        }
      };
    
      componentDidMount = () => this.props.asyncFormFields();
    
      reinitializeForm = () =>
        this.setState({ isLoading: true }, () => this.props.asyncFormFields());
    
      render = () =>
        this.props.err ? (
          <ShowError err={this.props.err} />
        ) : this.state.isLoading ? (
          <Spinner />
        ) : (
          <ShowForm {...this.props} reinitializeForm={this.reinitializeForm} />
        );
    }
    
    export default reduxForm({
      form: "SimpleForm",
      enableReinitialize: true,
      keepDirtyOnReinitialize: true
    })(
      connect(
        state => ({ err: state.server, formFields: state.fields }),
        { asyncFormFields }
      )(SimpleForm)
    );
    

    另一种初始化 Redux 表单的方法是使用 ReduxForm 的 formReducer 插件。这有点复杂,涉及的步骤更多,但结果是一样的:

    工作示例:https://codesandbox.io/s/xppnmklm7q

    SimpleForm.js

    import React, { Component } from "react";
    import { reduxForm } from "redux-form";
    import { connect } from "react-redux";
    import { asyncFormFields, initForm } from "../actions";
    import ShowError from "../components/ShowError";
    import ShowForm from "../components/ShowForm";
    import Spinner from "../components/Spinner";
    
    class SimpleForm extends Component {
      state = {
        err: "",
        isLoading: true
      };
    
      componentDidUpdate = (prevProps, prevState) => {
        if (this.props.formFields !== prevProps.formFields) {
          this.setState({ isLoading: false }, () => this.props.initForm(this.props.formFields));
        }
      };
    
      componentDidMount = () => this.props.asyncFormFields();
    
      reinitializeForm = () => this.setState({ isLoading: true }, () => this.props.asyncFormFields());
    
      render = () =>
        this.props.err 
          ? <ShowError err={this.props.err} />
          : this.state.isLoading 
            ? <Spinner />
            : <ShowForm {...this.props} reinitializeForm={this.reinitializeForm} />
      );
    }
    
    export default reduxForm({
      form: "SimpleForm",
      enableReinitialize: true,
      keepDirtyOnReinitialize: true
    })(
      connect(
        state => ({ err: state.server, formFields: state.fields }),
        { asyncFormFields, initForm }
      )(SimpleForm)
    );
    

    actions/index.js

    import axios from "axios";
    import { INIT_FORM, SET_FORMFIELDS, SERVER_ERROR } from "../types";
    
    export const asyncFormFields = () => dispatch =>
      axios
        .get("https://randomuser.me/api/?nat=us&results=1")
        .then(({ data: { results } }) =>
          dispatch({
            type: SET_FORMFIELDS,
            payload: {
              firstName: results[0].name.first,
              lastName: results[0].name.last
            }
          })
        )
        .catch(err => dispatch({ type: SERVER_ERROR, payload: err }));
    
    export const initForm = fields => ({ type: INIT_FORM, payload: fields });
    

    reducers/index.js

    import { createStore, combineReducers } from "redux";
    import { reducer as formReducer } from "redux-form";
    import { INIT_FORM, SET_FORMFIELDS, SERVER_ERROR } from "../types";
    
    const fieldsReducer = (state = {}, { payload, type }) => {
      switch (type) {
        case SET_FORMFIELDS:
          return { ...state, ...payload };
        default:
          return state;
      }
    };
    
    const serverResponseReducer = (state = "", { payload, type }) => {
      switch (type) {
        case SERVER_ERROR:
          return (state = payload);
        default:
          return state;
      }
    };
    
    const formReducers = {
      form: formReducer.plugin({
        SimpleForm: (state, { payload, type }) => { // <----- 'SimpleForm' - name given to reduxForm()
          switch (type) {
            case INIT_FORM: // <----- action type triggered by componentDidUpdate from 'SimpleForm'
              return {
                ...state, // <----- spreads out any previous form state (registered fields)
                values: {
                  ...payload // <----- initializes form fields values from supplied initForm action 'field' values
                }
              };
            default:
              return state;
          }
        }
      })
    };
    
    export default combineReducers({
      fields: fieldsReducer,
      server: serverResponseReducer,
      ...formReducers
    });
    

    【讨论】:

    • 感谢您的回答,马特!使用this.props.initialize(...) 让我更接近了,但还没有完全到那里。您能解释一下为什么需要使用该功能吗?我的表格看起来非常简单,所以很好奇为什么它似乎不像所有其他示例那样工作。此外,在生命周期中调用 componentDidMount() 时,看起来组件道具尚未通过 mapStateToProps 填充,因为该值未定义。但是,如果我做某事导致重新渲染,它们就会出现。
    • 更新了答案以包含异步 mapStateToProps 示例。
    • 再次更新了答案以包含另一种通过 ReduxForm 的 formReducer 插件初始化表单的方法。
    【解决方案2】:

    这周我最终用 Redux-form 做了一个示例,并有了一个想法。对于我的表单,我还使用了带有向导功能的 redux 表单,并且它没有保留状态(即使使用 destroyOnUnmount: false)。为了解决这个问题,我去掉了向导功能,只使用了一个表单(向导使用多个表单),并对问题本身进行了隐藏和显示。有些事情告诉我问题在于多种形式没有保持状态。代码如下:

    import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import { compose, bindActionCreators } from "redux";
    import injectReducer from '../../utils/injectReducer';
    import injectSaga from '../../utils/injectSaga';
    import { SubmissionError } from 'redux-form';
    import { Redirect } from "react-router-dom";
    import { fetchQuestions } from './actions';
    import reducer from './reducer';
    import saga from './sagas';
    import QuestionForm from './QuestionForm';
    import { addAnswer } from './actions';
    
    export class Quiz extends Component {
      constructor(props) {
        super(props)
        this.submitQuiz = this.submitQuiz.bind(this)
        // this.previousPage = this.previousPage.bind(this)
        this.state = {
          page: 1,
          sendToSummary: false,
        }
      }
      componentDidMount(){
        return new Promise((resolve, reject) => {
          this.props.fetchQuestions(resolve, reject);
          this.props.addAnswer();
        }).catch((error) => {
          throw new SubmissionError(error);
        })
      }
      submitQuiz() {
        console.log("sub");
        this.setState(() => ({
          sendToSummary: true
        }))
      }
    
      render(){
        console.log(this.props, "test");
        const { page } = this.state;
        const { questions } = this.props
        return (
          <div className="quiz-form">
           {questions ? 
              <QuestionForm 
                onSubmit={this.submitQuiz} 
                // previousPage={this.previousPage}
                questions={questions}
              />
             : 'loading'}
            {(this.state.sendToSummary === true) ? <Redirect to='/summary' /> : null}
    
          </div>
        );
      }
    }
    
    function mapSateToProps(state){
      console.log(state)
      return {
        form: state.get('form'),
        questions: state.getIn(['questionSet', 'questions'])
      }
    }
    const mapDispatchToProps = dispatch => ({
      fetchQuestions: () => dispatch(fetchQuestions()),
      addAnswer: () => dispatch(addAnswer()),
    })
    
    const withReducer = injectReducer({key: `questionSet`, reducer});
    const withSaga = injectSaga({key: `questionSet`, saga});
    const withConnect = connect(
      state => mapSateToProps, mapDispatchToProps
    );
    
    export default compose(
      withReducer, 
      withSaga, 
      withConnect,
    )(Quiz);
    

    这是表格:

    import React, { Component } from 'react';
    import RadioButton from '../../components/RadioButton';
    import { Field, reduxForm, SubmissionError } from 'redux-form';
    import { Button, Form } from 'reactstrap';
    import ReactHtmlParser from 'react-html-parser';
    import { addAnswer } from './actions';
    import { connect } from 'react-redux';
    import Progress from '../../components/Progress';
    
    class QuestionForm extends Component {
      constructor(props) {
        super(props);
        this.state = {
          selectedValues:[],
          isChecked: true,
          currentQuestion: 0
        };
        this.nextPage = this.nextPage.bind(this);
        this.previousPage = this.previousPage.bind(this);
    
      }
      nextPage(){
        if(this.state.currentQuestion !== this.props.questions-1){
          this.setState({ currentQuestion: this.state.currentQuestion + 1 })
        }
      }
      previousPage(){
        if(this.state.currentQuestion !== 0){
          this.setState({ currentQuestion: this.state.currentQuestion - 1 })
        }
      }
      render(){
        const { handleSubmit, questions } = this.props;
        console.log(this.props.questions)
        return (
          <form onSubmit={handleSubmit} className="quizForm">
            {questions.map((question,i) => {
              console.log(i, this.state.currentQuestion)
              return (
                <div key= {`question-${i}`} className={this.state.currentQuestion === i ? '':'d-none'}>
                  <h1>{ question.category }</h1>
                  <h2>{ ReactHtmlParser(question.question) }</h2>
                  <RadioButton name={`question-${i}`} label="true" radioButtonValue={true} />
                  <RadioButton name={`question-${i}`} label="false" radioButtonValue={false} />
                  <Button type="button" onClick={this.previousPage} className="next">
                    Previous
                  </Button>
                  {this.state.currentQuestion === questions.length-1 ? <Button type="submit" className="next" onClick={() => this.nextPage(i)}>
                    Submit
                  </Button>: <Button type="button" className="next" onClick={() => this.nextPage(i)}>
                    Next
                  </Button>}
                </div>
              )
            })}
          <Progress page={this.state.currentQuestion + 1} total={10}/>
          </form>
        )
      }
    }
    function mapStateToProps(state){
      console.log(state)
      return {
        form: state.get('form'),
        questions: state.getIn(['questionSet', 'questions'])
      }
    }
    const mapDispatchToProps = dispatch => ({
      fetchQuestions: () => dispatch(fetchQuestions()),
      addAnswer: () => dispatch(addAnswer()),
    })
    
    QuestionForm = connect(state => (mapStateToProps, mapDispatchToProps))(QuestionForm)
    
    export default reduxForm({
      form: 'quiz_form',
      destroyOnUnmount: false,
      // forceUnregisterOnUnmount: true,
      // keepDirtyOnReinitialize: true
    })(QuestionForm);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-10-24
      • 2017-05-07
      • 2018-05-17
      • 1970-01-01
      • 2018-10-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多