Here's what you can do to make migrating your forms to Redux easier in the future
01 Oct 2017
You know you don't need Redux for your form state... yet. Requirements change and apps evolve. You can't always know in advance. What was perfectly fine to store in the local state might need to be moved to Redux.
Oh, and it can be painful to migrate to Redux.
But it can also be smooth... if you do certain things.
Actually, just one
Make small 'action reducer' functions. They would take current state and some details about the action, and return the new state.
function changeEmail(state, newEmail) {
return { ...state, email: newEmail };
}
changeEmail(state, "new@email.com"); // => returns state with the new value of email
They can be literally used with the functional form of setState
:
handleEmailChange = (evt) => {
this.setState(state => changeEmail(state, event.target.value);
};
If you've never passed the function to setState
, don't worry.
There's a section about it in React docs.
Basically, instead of passing the new state to setState
, you can pass it a function which would accept the current state and return a new state.
Logically, this.setState(fn)
is similar to doing this:
const newState = fn(this.state);
this.setState(newState);
Then, to migrate this to Redux, you'd only need to do a few things:
- Instead of calling
setState(state => changeEmail(state, ...))
, you would dispatch an action:this.props.onChangeEmail(...)
; - Instead of getting the current values of fields from
this.state
, you'd usethis.props
. - The
changeEmail
function trivially becomes a branch in the reducer:
function loginFormReducer(state, action) {
switch (action.type) {
case "changeEmail": {
return { ...state, email: action.newEmail };
}
}
}
Going a bit futher
You can go further and create a local "reducer" in your component.
Here's a general idea:
React.ReducerComponent... 🤔 pic.twitter.com/nMde7ykkc8
— Jared Palmer (@jaredpalmer)
Since actions are often accompanied by some relevant data (like the new field value), we need something more than string actions ('increment'
).
We need action objects ({ type: 'something', otherRelevantData: 42 }
).
function changeEmail(newEmail) {
return { type: 'changeEmail', email: newEmail };
}
class Form extends Component {
// ...
reduce = action => this.setState(state => this.reducer(state, action));
reducer = (state, action) => {
switch (action.type) { // note we're switching by action.type
case 'changeEmail': return { ...state, email: action.email };
}
};
render() {
// ...
<input
...
onChange={evt => this.reduce(changeEmail(evt.target.value))}
/>
}
}
If it seems really close to Redux, that's because it is.
It would be trivial to switch from local state to Redux when a form manages its state this way:
changeEmail
would be left without changes, and would be connected byreact-redux
;- Instead of having
reducer
in the class, it would become a Redux reducer; - Instead of doing
this.reduce(changeEmail(...))
, you'd dothis.props.onChangeEmail(...)
.