Заполнение Redux-Form начальными значениями из хранилища Redux - PullRequest
0 голосов
/ 27 сентября 2018

В жизни я не могу заставить мою Redux-форму заполняться начальными значениями.Мне кажется, что я посмотрел каждый 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(...) на буквальное «test», поле останется пустым.Если я переместу initialValues к вызову reduxForm(...), используя просто 'test' вместо state.getIn(...), поле firstName правильно отображает 'test'.

Я предполагаю, что оно как-то связано сЯ использую функции reduxForm, connect и compose.Именно так настроен проект реагировать на редукцию, поэтому я просто использую эту парадигму.

Я определенно не эксперт React / Redux, поэтому, пожалуйста, дайте мне знать, нужна ли какая-либо дополнительная информация,Спасибо!

Из package.json:

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

Ответы [ 2 ]

0 голосов
/ 07 октября 2018

Я закончил делать образец с 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);
0 голосов
/ 27 сентября 2018

ВНОВЬ ОБНОВЛЕНО!

Поскольку кажется, что mapStateToProps устанавливается асинхронно, вам необходимо использовать React state в сочетании с this.props.initialize() в методе componentDidUpdate.

this.props.formFields (или как вы его называете в состоянии Redux) должно следовать тому же соглашению об именах, указанному в Field 'name.Например: { firstName: "", lastName: "" } должно соответствовать <Field name="firstName" ... /> <Field name="lastName" .../>.

Вам также понадобится keepDirtyOnReinitialize: true, если вы планируете разрешить пользователю редактировать входные данные - в противном случае, несмотря на то, что отображается на входе, он отправит инициализированное значение.

Рабочий пример: https://codesandbox.io/s/zm3mqw2m4

В приведенном ниже примере запускается действие Redux (this.props.asyncFormFields) в методе componentDidMount класса, отображается счетчик, а затем проверяется, было ли this.props.formFieldsизменено в методе класса componentDidUpdate.Если this.props.formFields был изменен, он устанавливает this.state.isLoading в false, а затем предлагает поля Redux Form инициализироваться данными this.props.formFields.

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

Редукторы / 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
});
...