如何基于异步源设置initialValues,例如使用redux-form的ajax调用

时间:2015-12-14 16:25:57

标签: ajax asynchronous redux redux-form

redux-form的官方网页和GitHub问题上,有多个如何使用 initialValues 的示例但是我找不到一个专注于解释如何<可以设置em> initialValues 以响应异步源。

我想到的主要情况就像一个简单的CRUD应用程序,用户将编辑一些已经存在的实体。首次打开视图并安装redux-form组件但在呈现组件之前,必须设置initialValues。假设在此示例中,首次装入和呈现组件时,按需加载数据。这些示例显示了基于硬编码值或redux存储状态设置initialValues,但我没有找到关注如何基于异步调用设置initialValues,如调用XHR或fetch。

我确定我错过了一些基本的东西,所以请指出我正确的方向。

参考文献:

6 个答案:

答案 0 :(得分:12)

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

现在最新版本的ReduxForm中的documented,比我以前的答案简单得多。

使用ReduxForm进行装饰后,关键是connect表单组件。然后,您就可以像组件上的任何其他道具一样访问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 plugin方法完成了这项工作。

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

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

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

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结合使用。

  

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

答案 1 :(得分:3)

默认情况下,您只能通过initialValues初始化一次表单组件。使用new&#34; pristine&#34;有两种方法可以重新初始化表单组件。值:

将enableReinitialize prop或reduxForm()配置参数设置为true,以允许使用new&#34; pristine&#34;重新初始化表单。每次initialValues prop改变时的值。要在重新初始化时保留脏表单值,可以将keepDirtyOnReinitialize设置为true。默认情况下,重新初始化表单会使用&#34; pristine&#34;替换所有脏值。值。

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

参考:http://redux-form.com/6.1.1/examples/initializeFromState/

答案 2 :(得分:2)

你能解雇componentWillMount()上的调度,并将状态设置为loading。

在加载时,渲染一个微调器,仅当请求返回值时,更新状态,然后使用值重新呈现表单

答案 3 :(得分:1)

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

不应不定义initialValues中的所有值,否则您will get an infinite loop

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

答案 4 :(得分:0)

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

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

<强> Form.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 };
   });
}    

<强> RenderAlert.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;

<强> Reducers.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;

答案 5 :(得分:0)

使用这个:

<div class="img-magnifier-container">
  <img id="export-a-product-to-china" src="https://static1.squarespace.com/static/5a27e60318b27d7d0e86bd68/t/5fda32c7b424be58e1607b44/1608135367867/anipro_bw_final.svg" width="960" height="540">
</div>