Reputation: 2813
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>
);
}
...
Upvotes: 5
Views: 5433
Reputation: 370
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
);
}
Upvotes: 5
Reputation: 373
For saving input to state, you can use redux-form
It seems your question is not really related to sharing reducers/actions. For sharing actions/reducers, I wrote one and you can have a look redux-conditional
Upvotes: 0
Reputation: 19821
I recently came up with such action,
export function controlStateChange(controlName, propertyName, value) {
return { type: 'CONTROL_STATE_CHANGE', payload: { controlName, propertyName, value } };
}
And correspoding reducer,
const actionHandlers = {
'CONTROL_STATE_CHANGE': (state, payload) => {
const controls = {
[payload.controlName]: {
[payload.propertyName]: payload.value
}
};
return {...state, controls };
}
};
It is quite general and I hope could serve you needs too.
Upvotes: 1
Reputation: 24368
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');
Upvotes: 6