I'm still very much a react/redux noob. On one page I have a ton of text inputs. After a while I started noticing my action file has functions doing the same thing but for different inputs:
export function setInputName(string) {
return {
type: 'SET_CURRENT_NAME',
payload: {value: string}
};
}
export function setInputCity(string) {
return {
type: 'SET_CURRENT_CITY',
payload: {value: string}
};
}
With my reducer looking like:
export function currentName(state='', {type, payload}) {
switch (type) {
case 'SET_CURRENT_NAME':
return payload.value;
default:
return state;
}
}
export function currentCity(state='', {type, payload}) {
switch (type) {
case 'SET_CURRENT_CITY':
return payload.value;
default:
return state;
}
}
And my component had these multiple inputs:
import {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {setInputName, setInputCity} from 'actions/form';
export default class Form extends Component {
static propTypes = {
setInputName: PropTypes.func.isRequired,
setInputCity: PropTypes.func.isRequired,
currentName: PropTypes.string.isRequired,
currentCity: PropTypes.string.isRequired
}
render() {
let {setInputName, setInputCity, currentName, currentCity} = this.props;
return (
<div>
<input
type="text"
placeholder="Name"
onChange={(e) => setInputName(e.currentTarget.value)}
value={currentName}
/>
<input
type="text"
placeholder="City"
onChange={(e) => setInputCity(e.currentTarget.value)}
value={currentCity}
/>
</div>
);
}
}
function select(state) {
return {
currentName: state.form.currentName,
currentCity: state.form.currentCity
};
}
function actions(dispatch) {
return bindActionCreators({
setInputName: setInputName,
setInputCity: setInputCity,
}, dispatch);
}
export default connect(select, actions)(Form);
Which isn't very DRY and I immediately thought I was doing something wrong. Is there a good way to have a common setInputValue
action for all text inputs on every page and component of my app? I would also want to use a common reducer for every input. Thank you.
UPDATE
Here's my gross example that works but I feel like this is still a bit convoluted and there has to be a better way. Basically in the reducer I check to see if that input has been used and added to the state yet. If not I add it. If so I just update its value. EDIT I lied this doesn't actually work.
// actions
export function updateTextInput(name, value) {
return {
type: 'SET_CURRENT_TEXT_INPUT',
payload: {name: name, value: value}
};
}
// reducer
export function currentTextInput(state=[], {type, payload}) {
switch (type) {
case 'SET_CURRENT_TEXT_INPUT':
let newState = state;
let curInput = state.findIndex(function(elem) {
return elem.name === payload.name;
});
if (curInput === -1) {
newState.push({name: payload.name, value: payload.value});
} else {
newState[curInput].value = payload.value;
}
return newState;
default:
return state;
}
}
// component
...
render() {
let {updateTextInput, currentTextInput} = this.props;
return (
<div>
<input
type="text"
placeholder="Name"
onChange={(e) => updateTextInput('name', e.currentTarget.value)}
value={currentTextInput.name}
/>
<input
type="text"
placeholder="City"
onChange={(e) => updateTextInput('city', e.currentTarget.value)}
value={currentTextInput.city}
/>
</div>
);
}
...
A small first step to simplify the code is to use higher order reducers/functions.
function makePropReducer(actionType, prop) {
return (state = '', {type, payload}) => {
if (type === actionType) {
return payload[prop];
}
return state;
};
}
export const currentName = makePropReducer('SET_CURRENT_NAME', 'value');
export const currentCity = makePropReducer('SET_CURRENT_CITY', 'value');
Ok here's an example as requested. Let's say I have a User data model, it looks something like this:
{
id: 1,
name: 'Some Name',
email: '[email protected]',
age: 21
}
The way you currently have it set up you'd have to have SET_USER_NAME, SET_USER_EMAIL, SET_USER_AGE actions. No need for all those when you can have an UPDATE_USER action that receives an object of the updated values as an argument.
To set a new name for the first user you would call the UPDATE_USER action like:
updateUser(1, { name: 'New Name' })
In the Users reducer you'd update the users' state (an array of users) like:
function updateUser(usersState, id, attrs) {
return usersState.map(user =>
user.id === id ?
Object.assign({}, user, attrs) :
user
);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With