26

On the official pages and in the GitHub issues for redux-form there are more than one example of how to work with initialValues however I cannot find a single one that focuses on explaining how initialValues can be set in response to an asynchronous source.

The main case that I have in mind is something like a simple CRUD application where a user is going to edit some entity that already exists. When the view is first opened and the redux-form component is mounted but before the component is rendered the initialValues must be set. Lets say that in this example that the data is loaded on demand when the component is first mounted and rendered for the first time. The examples show setting initialValues based on hard coded values or the redux store state but none that I can find focus on how to set the initialValues based on something async like a call to XHR or fetch.

I'm sure I'm just missing something fundamental so please point me in the right direction.

References:

4

6 回答 6

12

编辑:来自 ReduxForm 文档的更新解决方案

这现在记录在最新版本的 ReduxForm 中,并且比我之前的答案简单得多。

关键是connect在用 ReduxForm 装饰后的表单组件。然后,您将能够像访问initialValues组件上的任何其他道具一样访问该道具。

// Decorate with reduxForm(). It will read the initialValues prop provided by connect()
InitializeFromStateForm = reduxForm({
  form: 'initializeFromState'
})(InitializeFromStateForm)

// now set initialValues using data from your store state
InitializeFromStateForm = connect(
  state => ({
    initialValues: state.account.data 
  })
)(InitializeFromStateForm)

我通过使用 redux-form reducer 插件方法实现了这一点。

以下演示获取异步数据并使用响应预填充用户表单。

const RECEIVE_USER = 'RECEIVE_USER';

// once you've received data from api dispatch action
const receiveUser = (user) => {
    return {
       type: RECEIVE_USER,
       payload: { user }
    }
}

// here is your async request to retrieve user data
const fetchUser = (id) => dispatch => {
   return fetch('http://getuser.api')
            .then(response => response.json())
            .then(json => receiveUser(json));
}

然后在包含减速器的根减速器中,您redux-form将包含减速器插件,该插件将使用返回的获取数据覆盖表单值。

const formPluginReducer = {
   form: formReducer.plugin({
      // this would be the name of the form you're trying to populate
      user: (state, action) => {
         switch (action.type) {
             case RECEIVE_USER:
                return {
                  ...state,
                  values: {
                      ...state.values,
                      ...action.payload.user
                  }
               }
            default:
               return state;
         }
      }
   })
};

const rootReducer = combineReducers({
   ...formPluginReducer,
   ...yourOtherReducers
});

最后,您将新的 formReducer 与应用程序中的其他 reducer 结合起来。

注意以下假设获取的用户对象的键与用户表单中的字段名称匹配。如果不是这种情况,您将需要对数据执行额外的步骤以映射字段。

于 2016-10-13T16:37:26.220 回答
3

默认情况下,您只能通过 initialValues 初始化表单组件一次。有两种方法可以使用新的“原始”值重新初始化表单组件:

将 enableReinitialize 属性或 reduxForm() 配置参数设置为 true 以允许表单在每次 initialValues 属性更改时使用新的“原始”值重新初始化。要在重新初始化时保留脏表单值,可以将 keepDirtyOnReinitialize 设置为 true。默认情况下,重新初始化表单会将所有脏值替换为“原始”值。

调度 INITIALIZE 动作(使用 redux-form 提供的动作创建者)。

引用自:http ://redux-form.com/6.1.1/examples/initializeFromState/

于 2016-10-24T08:48:05.413 回答
2

这是关于如何基于异步源设置 initialValues 的最小工作示例。
它使用初始化动作创建者。

initialValues 中的所有值都不应未定义,否则您将获得无限循环

// import { Field, reduxForm, change, initialize } from 'redux-form';

async someAsyncMethod() {
  // fetch data from server
  await this.props.getProducts(),

  // this allows to get current values of props after promises and benefits code readability
  const { products } = this.props;

  const initialValues = { productsField: products };

  // set values as pristine to be able to detect changes
  this.props.dispatch(initialize(
    'myForm',
    initialValues,
  ));
}
于 2018-12-28T01:49:14.223 回答
2

您能否在 componentWillMount() 上触发调度,并将状态设置为正在加载。

在加载时,例如,仅在请求返回值时呈现微调器,更新状态,然后使用值重新呈现表单?

于 2015-12-14T20:27:46.423 回答
0

虽然这种方法可能不是最好的解决方案,但它足以满足我的需求:

  • 进入时对 API 的 AJAX 请求
  • 当请求完成或显示服务器错误时,用数据初始化表单
  • 重置表格仍将重置为初始种子数据
  • 允许将表单重用于其他目的(例如,简单的 if 语句可以绕过设置初始值):添加帖子和编辑帖子或添加评论和编辑评论...等。
  • 退出时从 Redux 表单中删除数据(没有理由在 Redux 中存储新数据,因为它正在由博客组件重新呈现)

表单.jsx:

import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
import { browserHistory, Link } from 'react-router';

import { editPost, fetchPost } from '../../actions/BlogActions.jsx';
import NotFound from '../../components/presentational/notfound/NotFound.jsx';
import RenderAlert from '../../components/presentational/app/RenderAlert.jsx';   
import Spinner from '../../components/presentational/loaders/Spinner.jsx'; 

// form validation checks
const validate = (values) => {
  const errors = {}
  if (!values.title) {
    errors.title = 'Required';
  }

  if (!values.image) {
    errors.image = 'Required';
  }

  if (!values.description) {
    errors.description = 'Required';
  } else if  (values.description.length > 10000) {
    errors.description = 'Error! Must be 10,000 characters or less!';
  }

  return errors;
}

// renders input fields
const renderInputField = ({ input, label, type, meta: { touched, error } }) => (
  <div>
    <label>{label}</label>
    <div>
      <input {...input} className="form-details complete-expand" placeholder={label} type={type}/>
      {touched && error && <div className="error-handlers "><i className="fa fa-exclamation-triangle" aria-hidden="true"></i> {error}</div>}
    </div>
  </div>
)

// renders a text area field
const renderAreaField = ({ textarea, input, label, type, meta: { touched, error } }) => (
  <div>
    <label>{label}</label>
    <div>
      <textarea {...input} className="form-details complete-expand" placeholder={label} type={type}/>
      {touched && error && <div className="error-handlers"><i className="fa fa-exclamation-triangle" aria-hidden="true"></i> {error}</div>}
    </div>
  </div>
)

class BlogPostForm extends Component {   
  constructor() {
    super();

    this.state = {
      isLoaded: false,
      requestTimeout: false,
    };
  }

  componentDidMount() {
    if (this.props.location.query.postId) {
      // sets a 5 second server timeout
      this.timeout = setInterval(this.timer.bind(this), 5000);
      // AJAX request to API 
      fetchPost(this.props.location.query.postId).then((res) => {
        // if data returned, seed Redux form
        if (res.foundPost) this.initializeForm(res.foundPost);
        // if data present, set isLoaded to true, otherwise set a server error
        this.setState({
          isLoaded: (res.foundPost) ? true : false,
          serverError: (res.err) ? res.err : ''
        });
      });
    }
  }

  componentWillUnmount() {
    this.clearTimeout();
  }

  timer() {
    this.setState({ requestTimeout: true });
    this.clearTimeout();
  }

  clearTimeout() {
    clearInterval(this.timeout);
  }

  // initialize Redux form from API supplied data
  initializeForm(foundPost) {

    const initData = {
      id: foundPost._id,
      title: foundPost.title,
      image: foundPost.image,
      imgtitle: foundPost.imgtitle,
      description: foundPost.description
    }

    this.props.initialize(initData);
  }

  // onSubmit => take Redux form props and send back to server
  handleFormSubmit(formProps) {
    editPost(formProps).then((res) => {
      if (res.err) {
        this.setState({
          serverError: res.err
        });
      } else {
        browserHistory.push(/blog);
      }
    });
  }

  renderServerError() {
    const { serverError } = this.state;
    // if form submission returns a server error, display the error
    if (serverError) return <RenderAlert errorMessage={serverError} />
  }

  render() {
    const { handleSubmit, pristine, reset, submitting, fields: { title, image, imgtitle, description } } = this.props;
    const { isLoaded, requestTimeout, serverError } = this.state;

    // if data hasn't returned from AJAX request, then render a spinner 
    if (this.props.location.query.postId && !isLoaded) {
      // if AJAX request returns an error or request has timed out, show NotFound component
      if (serverError || requestTimeout) return <NotFound />

      return <Spinner />
     }

    // if above conditions are met, clear the timeout, otherwise it'll cause the component to re-render on timer's setState function
    this.clearTimeout();

    return (
      <div className="col-sm-12">
        <div className="form-container">
          <h1>Edit Form</h1>
          <hr />
          <form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
            <Field name="title" type="text" component={renderInputField} label="Post Title" />
            <Field name="image" type="text" component={renderInputField} label="Image URL" />
            <Field name="imgtitle" component={renderInputField} label="Image Description" />
            <Field name="description" component={renderAreaField} label="Description" />
            <div>
              <button type="submit" className="btn btn-primary partial-expand rounded" disabled={submitting}>Submit</button>
              <button type="button" className="btn btn-danger partial-expand rounded f-r" disabled={ pristine || submitting } onClick={ reset }>Clear Values</button>
            </div>
          </form>
         { this.renderServerError() }
        </div>
      </div>
    )
  }
}

BlogPostForm = reduxForm({
  form: 'BlogPostForm',
  validate,
  fields: ['name', 'image', 'imgtitle', 'description']
})(BlogPostForm);


export default BlogPostForm = connect(BlogPostForm);

BlogActions.jsx:

import * as app from 'axios';

const ROOT_URL = 'http://localhost:3001';

// submits Redux form data to server
export const editPost = ({ id, title, image, imgtitle, description, navTitle }) => {
 return app.put(`${ROOT_URL}/post/edit/${id}?userId=${config.user}`, { id, title, image, imgtitle, description, navTitle }, config)
 .then(response => {
   return { success: response.data.message }
  })
  .catch(({ response }) => {
    if(response.data.deniedAccess) {
      return { err: response.data.deniedAccess }
    } else {
      return { err: response.data.err }
    }
  });
}

// fetches a single post from the server for front-end editing     
export const fetchPost = (id) => {
  return app.get(`${ROOT_URL}/posts/${id}`)
  .then(response => {
     return { foundPost: response.data.post}
   })
   .catch(({ response }) => {
     return { err: response.data.err };
   });
}    

渲染警报.jsx:

import React, { Component } from 'react';

const RenderAlert = (props) => {   
    const displayMessage = () => {
      const { errorMessage } = props;

      if (errorMessage) {
        return (
          <div className="callout-alert">
            <p>
              <i className="fa fa-exclamation-triangle" aria-hidden="true"/>
              <strong>Error! </strong> { errorMessage }
            </p>
          </div>
        );
      }
    }

    return (
      <div>
        { displayMessage() }
      </div>
    );
  }


export default RenderAlert;

减速器.jsx

import { routerReducer as routing } from 'react-router-redux';
import { reducer as formReducer } from 'redux-form';
import { combineReducers } from 'redux';  

const rootReducer = combineReducers({
  form: formReducer,
  routing
});

export default rootReducer;
于 2017-06-13T19:01:14.993 回答
0

用这个 :

UpdateUserForm = reduxForm({
  enableReinitialize: true,
  destroyOnUnmount: false,
  form: 'update_user_form' // a unique identifier for this form
})(UpdateUserForm);

UpdateUserForm = connect(
  (state) => ({
    initialValues: state.userManagment.userSingle
  })
)(UpdateUserForm);
export default UpdateUserForm;
于 2020-12-23T16:26:55.630 回答