在redux-form的官方网页和GitHub问题上,有多个如何使用 initialValues 的示例但是我找不到一个专注于解释如何<可以设置em> initialValues 以响应异步源。
我想到的主要情况就像一个简单的CRUD应用程序,用户将编辑一些已经存在的实体。首次打开视图并安装redux-form组件但在呈现组件之前,必须设置initialValues。假设在此示例中,首次装入和呈现组件时,按需加载数据。这些示例显示了基于硬编码值或redux存储状态设置initialValues,但我没有找到关注如何基于异步调用设置initialValues,如调用XHR或fetch。
我确定我错过了一些基本的东西,所以请指出我正确的方向。
参考文献:
答案 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)
虽然这种方法可能不是最佳解决方案,但它足以满足我的需求:
<强> 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>