Im trying to build a reduxform wherein I have a parent page , and step1 , step2 components like the redux form example on the site.
Step1 has a dropdown with 2 values -
lets say 'A' and 'B'
Step2 has a couple of form input details
If I have value 'A' in the step1 dropdown - I continue step2 with a post request to nodejs/mongo and redirect to a success page
(Main Question now: - How do I do the below)
If i have value 'B' in step 1 - The Step2 should continue to a Step3 -- and then at somepoint post to DB etc
How do i split the wizard flow ?
Any leads much appreciated - Im v v new to react/redux and reduxforms and on a deadline .. apologies for the 'noob'ness
The onSubmit func receives the values of the form that is being submitted:
onSubmit(values, dispatch, props)
In the example each step uses the same form 'wizard' so values contains the state from all wizard steps. You can use this to determine the next step:
class WizardForm extends Component {
constructor(props) {
super(props)
this.firstPageSubmit = this.firstPageSubmit.bind(this);
this.secondPageSubmit = this.secondPageSubmit.bind(this);
this.thirdPageSubmit= this.thirdPageSubmit.bind(this);
this.state = { page: 1 };
}
firstPageSubmit(values) {
this.setState({ page: 2 });
}
secondPageSubmit(values) {
if(values.dropdown == 'A') {
// post request to nodejs/mongo and redirect to a success page
} else {
this.setState({ page: this.state.page - 1 });
}
}
thirdPageSubmit(values) {
// at somepoint post to DB etc
}
render() {
const { onSubmit } = this.props
const { page } = this.state
return (
<div>
{page === 1 && <WizardFormFirstPage onSubmit={this.firstPageSubmit} />}
{page === 2 && <WizardFormSecondPage onSubmit={this.secondPageSubmit} />}
{page === 3 && <WizardFormThirdPage onSubmit={this.thirdPageSubmit} />)}
</div>
)
}
}
Related
I have two actions TEST and TEST_DONE which both increment an id property in my redux state. I am using redux-saga to dispatch the second action TEST_DONE automatically whenever I dispatch the first action TEST from my component.
I expect the order of execution to go like this:
component renders with initial value of testState.id = 0
component dispatches TEST action
component re-renders with testState.id = 1
saga dispatches the TEST_DONE action
component re-renders with testState.id = 2
Instead my component only re-renders when testState.id is updated to 2. I can't see the 1 value in the getSnapshotBeforeUpdate function. It shows 0 as the previous prop.
Why does the prop jump from 0 to 2 without receiving 1 in between?
saga.js:
export function* TestSagaFunc() {
yield put({
type: actions.TEST_DONE
});
};
export default function* rootSaga() {
yield all([
yield takeEvery(actions.TEST, TestSagaFunc),
]);
};
action.js:
const actions = {
TEST: 'TEST',
TEST_DONE: 'TEST_DONE',
callTest: (id) => ({
type: actions.TEST,
payload: {
id
}
}),
};
export default actions;
reducer.js:
const initState = {
testState: {
id: 0
}
};
export default function TestReducers ( state=initState, { type, ...action}) {
switch(type) {
default:
return state;
case actions.TEST: {
const { id } = state.testState;
const nextId = id + 1;
return {
...state,
testState: {
...state.testState,
id: nextId
}
};
};
case actions.TEST_DONE: {
const { id } = state.testState;
const nextId = id + 1;
return {
...state,
testState: {
...state.testState,
id: nextId
}
};
}
};
};
console output from component getSnapshotBeforeUpdate
Summarizing my comments from the question:
The redux state is indeed being updated as you've seen, but a component is not guaranteed to render every intermediate state change based on the way react batches state changes. To test this you can try importing delay from redux-saga/effects and adding yield delay(1000); before calling yield put in TestSagaFunc so the two state updates don't get batched together.
This is just a trick to illustrate the effects of batching and almost certainly not what you want to do. If you need the intermediate state to be rendered you could dispatch TEST_DONE from the component being rendered with a useEffect (or componentDidUpdate) to ensure that the component went through one render cycle with the intermediate state. But there is no way to force your component to render intermediate reducer states that are batched together.
I have two reducer actions that I want to dispatch one after the other. The first one modifies the state, then the second one uses a portion of the modified state to make another modification. The difficulty is that when the second dispatch is called, it still has the old outdated state and thus doesn't update the state properly.
An example is the following (also found here - https://codesandbox.io/s/react-usereducer-hqtc2) where there is a list of conversations along with a note of which one is considered the "active" conversation:
import React, { useReducer } from "react";
const reducer = (state, action) => {
switch (action.type) {
case "removeConversation":
return {
...state,
conversations: state.conversations.filter(
c => c.title !== action.payload
)
};
case "setActive":
return {
...state,
activeConversation: action.payload
};
default:
return state;
}
};
export default function Conversations() {
const [{ conversations, activeConversation }, dispatch] = useReducer(
reducer,
{
conversations: [
{ title: "James" },
{ title: "John" },
{ title: "Mindy" }
],
activeConversation: { title: "James" }
}
);
function removeConversation() {
dispatch({ type: "removeConversation", payload: activeConversation.title });
dispatch({ type: "setActive", payload: conversations[0] });
}
return (
<div>
Active conversation: {activeConversation.title}
<button onClick={removeConversation}>Remove</button>
<ul>
{conversations.map(conversation => (
<li key={conversation.title}>{conversation.title}</li>
))}
</ul>
</div>
);
}
In here, when I click the "remove conversation" button, I want to remove the active conversation, then set the active conversation to be the one at the top of the list. However, here when the first dispatch removes the conversation from the list, the second dispatch sets active to conversations[0], which still contains the removed value (since the state hasn't updated yet). As a result, it keeps the active conversation as the one it was before, even though it's been removed from the list.
I could probably combine the logic into just one action and do it all there (remove the conversation and set active all in one), but I would ideally like to keep my reducer actions to have one responsibility each if possible.
Is there any way to make the second dispatch call have the most recent version of the state so that this kind of problem doesn't occur?
It may help if you think of useEffect() like setState's second parameter (from class based components).
If you want to do an operation with the most recent state, use useEffect() which will be hit when the state changes:
const {
useState,
useEffect
} = React;
function App() {
const [count, setCount] = useState(0);
const decrement = () => setCount(count-1);
const increment = () => setCount(count+1);
useEffect(() => {
console.log("useEffect", count);
}, [count]);
console.log("render", count);
return (
<div className="App">
<p>{count}</p>
<button onClick={decrement}>-</button>
<button onClick={increment}>+</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render( < App / > , rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Some further info on useEffect()
Answering this for anyone who may come across similar issues in the future. The key to finding the solution to this is understanding that state in React is a snapshot.
You can see that in the dispatched setActive action, the value of conversations[0] of state is being passed:
dispatch({ type: "setActive", payload: conversations[0] });
Thus when the action is called before the next render, it uses the snapshotted state at the time of re-render:
// snapshot of state when action is called
{
conversations: [
{ title: "James" },
{ title: "John" },
{ title: "Mindy" }
],
activeConversation: { title: "James" }
}
Thus conversations[0] evaluates to {title: "James"}. This is why in the reducer, activeConversation: action.payload returns {title: "James"} and the active conversation doesn't change. In technical terms, "you're calculating the new state from the value in your closure, instead of calculating it from the most recent value."
So how do we fix this? Well useReducer actually in fact always has access to the most recent state value. It is a sister pattern to the state updater function, which also gives you access to the latest state variable even before the next render.
This means that after the first dispatch action:
dispatch({ type: "removeConversation", payload: activeConversation.title }); // first dispatch action
dispatch({ type: "setActive", payload: conversations[0] }); // second dispatch action
the next dispatch action actually has access to the latest state already. You just need to access it:
case "setActive":
return {
...state,
activeConversation: state.conversations[0]
};
You can verify this by logging it to the console:
const reducer = (state, action) => {
console.log(state);
switch (action.type) {
case "removeConversation":
return {
...state,
conversations: state.conversations.filter(
c => c.title !== action.payload
)
};
case "setActive":
return {
...state,
activeConversation: state.conversations[0]
};
default:
return state;
}
};
Also important to note that the 2 dispatch calls are batched as explained in the state updater function link mentioned above. More info on batching here too.
I am a beginner in Vue and I am wondering if I can get an insight from experienced developers here about my Vue codes. I just want to ask for help to know if my Vue approach is efficient and proper. (Project is running on Laravel)
The Case:
Let us say I have 2 tables in DB
(1) stores
(2) ad_accounts
Then we have 2 web pages to present these tables' data and execute CRUD functions with it
(1) store.blade.php
(2) adaccount.blade.php
Each page is running a Vue component
(1) Stores.vue
(2) AdAccounts.vue
I am using Vuex for store management.
Within store.js, I would have set of actions for CRUD for each vue component.
Now I realized that I have series of actions that actually does the same thing. For example, I have an action to add stores, and another action to add Ad accounts. Their only difference is that they are calling a different Laravel route.
So it seemed to me that my code was unnecessarily long and a bit expensive. To resolve, I decided to write my actions in a form of template. So this is what I did:
In store.js, I created an action for each CRUD function to be used as template
In Stores.vue and AdAccounts.vue, if I need to execute a CRUD function, I would use a method to call the corresponding action from store.js and provide the Laravel route as part of the action's payload
I have states and corresponding getters for returning these states in Stores.vue and AdAccounts.vue
Each action has a dedicated mutation that alters the approriate state
states and getters are mapped in each Vue component in order to access and use them
Is this approach efficient and proper? I have sample methods and actions below for reference.
Stores.vue
<template>
<div>
<form #submit.prevent="addData('stores/add')">
<button type="submit" class="btn btn-primary">Save</button>
</form>
</div>
</template>
<script>
export default {
methods: {
addData: function(url) {
this.payload.url = url
if(
this.payload.requestData.store_name == "" &&
this.payload.requestData.store_token == ""
) {
this.payload.isErr = true;
this.payload.errMsg = "ERROR: Could not continue due to some invalid or missing data. \nPlease check your entries and try again or contact your administrator.";
this.$store.dispatch('addData', this.payload)
}
else {
this.payload.isErr = false;
this.$store.dispatch('addData', this.payload)
this.readDataAll('stores/all', 'store');
}
this.cleanOnModalDismiss(this.$refs.addModal, this.refreshRequestData)
}
}
}
</script>
AdAccounts.vue
<template>
<div>
<form #submit.prevent="addData('ad_accounts/add')">
<button type="submit" class="btn btn-primary">Save</button>
</form>
</div>
</template>
<script>
export default {
methods: {
addData: function(url) {
this.payload.url = url
if(
this.payload.requestData.ad_id == "" &&
this.payload.requestData.ad_name == ""
) {
this.payload.isErr = true;
this.payload.errMsg = "ERROR: Could not continue due to some invalid or missing data. \nPlease check your entries and try again or contact your administrator.";
this.$store.dispatch('addData', this.payload)
}
else {
this.payload.isErr = false;
this.$store.dispatch('addData', this.payload)
this.readDataAll('ad_accounts/all', 'adaccounts');
}
this.cleanOnModalDismiss(this.$refs.addModal, this.refreshRequestData)
}
}
}
</script>
store.js
export default new Vuex.Store({
actions: {
addData (commit, payload) { // insert a record to DB
try {
if(payload.isErr == true) {
commit('SHOW_ERRORS', {messageType: "alert-danger", errorMessage: payload.errMsg});
} else {
axios.post(payload.url, payload.requestData)
.then(response=>{
if(response.status == 200) {
var err_msg = "";
if(response.data.success !== null) {
response.data.messageType = "alert-info"
response.data.actionMessage = response.data.success
commit('ADD_DATA', response.data);
} else {
response.data.messageType = "alert-danger"
for(var i=0; i<response.data.error.length; i++) {
err_msg += response.data.error[i] + "\n"
}
response.data.actionMessage = err_msg
commit('ADD_DATA', response.data);
}
}
else {
commit('SHOW_ERRORS', {messageType: "alert-danger", errorMessage: "ERROR: Connection status set to '" + response.headers.connection + "' due to error " + response.status + " " + response.statusText + ". \nPlease contact your administrator."});
}
})
}
} catch (error) {
commit('SHOW_ERRORS', {messageType: "alert-danger", errorMessage: error})
}
}
}
}
I am having a hard time trying to joing a filterBy with orderBy, on vuejs 2.0, with all research I have found about this subject, as of link on the bottom of my question.
This is my filter, which is working:
// computed() {...
filteredResults() {
var self = this
return self.results
.filter(result => result.name.indexOf(self.filterName) !== -1)
}
A method called in the component:
// methods() {...
customFilter(ev, property, value) {
ev.preventDefault()
this.filterBook = value
}
In the component:
// Inside my component
Name..
And another filter, which works as well:
// computed() {...
orderByResults: function() {
return _.orderBy(this.results, this.sortProperty, this.sortDirection)
}
To comply with my orderBy I have this method:
// methods() {...
sort(ev, property) {
ev.preventDefault()
if (this.sortDirection == 'asc' && this.sortProperty == property ) {
this.sortDirection = 'desc'
} else {
this.sortDirection = 'asc'
}
this.sortProperty = property
}
And to call it I have the following:
// Inside my component
Name..
I have found in the docs how we use this OrderBy, and in this very long conversation how to use filter joint with sort, but I could really not implement it...
Which should be some like this:
filteredThings () {
return this.things
.filter(item => item.title.indexOf('foo') > -1)
.sort((a, b) => a.bar > b.bar ? 1 : -1)
.slice(0, 5)
}
I could not make this work...
I tried in many forms as of:
.sort((self.sortProperty, self.sortDirection) => this.sortDirection == 'asc' && this.sortProperty == property ? this.sortDirection = 'desc' : this.sortDirection = 'asc' )
But still, or it does not compile or it comes with errors, such as:
property not defined (which is defines such as I am using it in the other method)
method of funcion not found (is happens when comment my method sort.. maybe here is what I am missing something)
Thanks for any help!
The ideas of your approach seem valid, but without a full example it's hard to tell what might actually be wrong.
Here's a simple example of sorting and filtering combined. The code can easily be extended e.g. to work with arbitrary fields in the test data. The filtering and sorting is done in the same computed property, based on the parameters set from the outside. Here's a working JSFiddle.
<div id="app">
<div>{{filteredAndSortedData}}</div>
<div>
<input type="text" v-model="filterValue" placeholder="Filter">
<button #click="invertSort()">Sort asc/desc</button>
</div>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
testData: [{name:'foo'}, {name:'bar'}, {name:'foobar'}, {name:'test'}],
filterValue: '',
sortAsc: true
};
},
computed: {
filteredAndSortedData() {
// Apply filter first
let result = this.testData;
if (this.filterValue) {
result = result.filter(item => item.name.includes(this.filterValue));
}
// Sort the remaining values
let ascDesc = this.sortAsc ? 1 : -1;
return result.sort((a, b) => ascDesc * a.name.localeCompare(b.name));
}
},
methods: {
invertSort() {
this.sortAsc = !this.sortAsc;
}
}
});
</script>
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:
Initializing Form State
Handling form defaults
What is the correct way to populate a dynamic form with initial data?
EDIT: Updated Solution from ReduxForm docs
This is now documented in the latest version of ReduxForm, and is much simpler than my previous answer.
The key is to connect your form component after decorating it with ReduxForm. Then you will be able to access the initialValues prop just like any other prop on your component.
// 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)
I accomplished this by using the redux-form reducer plugin method.
The following demos fetching async data and pre-populating a user form with response.
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));
}
Then in your root reducer where you include your redux-form reducer you would include your reducer plugin that overrides the forms values with the returned fetched data.
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
});
Finally you include you combine your new formReducer with the other reducers in your app.
Note The following assumes that the fetched user object's keys match the names of the fields in the user form. If this is not the case you will need to perform an additional step on the data to map fields.
By default, you may only initialize a form component once via initialValues. There are two methods to reinitialize the form component with new "pristine" values:
Pass a enableReinitialize prop or reduxForm() config parameter set to true to allow the form the reinitialize with new "pristine" values every time the initialValues prop changes. To keep dirty form values when it reinitializes, you can set keepDirtyOnReinitialize to true. By default, reinitializing the form replaces all dirty values with "pristine" values.
Dispatch the INITIALIZE action (using the action creator provided by redux-form).
Referenced from : http://redux-form.com/6.1.1/examples/initializeFromState/
Could you fire the dispatch on componentWillMount(), and set the state to loading.
While it is loading, render a spinner for example and only when the request returns with the values, update the state, and then re-render the form with the values??
Here is minimal working example on how to set initialValues based on async source.
It uses initialize action creator.
All values from initialValues shouldn't be undefined, or you 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,
));
}
While this method may not be the best solution, it works well enough for my needs:
AJAX request to API on entry
Initializes form with data when request has been fulfilled or displays a server error
Resetting form will still reset to initial seed data
Allows the form to be reused for other purposes (for example, a simple if statement could bypass setting initial values): Add Post and Edit Post or Add Comment and Edit Comment...etc.
Data is removed from Redux form on exit (no reason to store new data in Redux since it's being re-rendered by a Blog component)
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;
use this :
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;