This question may be silly, but for some reason I cannot find what I want to. I have a component which has two inputs, if any one of the input changes, I have to dispatch an action to saga to find some info from backend, so I have send both these values. When values are changed in one of the components I dispatch with new value and set it in redux store, for sending request to backend I need the other input value which is already set in redux store, how should I do this?
Below is the component and usecase, I want both duration and date values in change functions.
export class Options extends React.Component { // eslint-disable-line react/prefer-stateless-function
render() {
return (
<div>
<SelectField
name={"duration"}
value={this.props.duration}
onChange={this.props.durationChanged}
>
<MenuItem key={1} value={1} primaryText={"option1"} />
<MenuItem key={2} value={2} primaryText={"option2"} />
<MenuItem key={3} value={3} primaryText={"option3"} />
<MenuItem key={4} value={4} primaryText={"option4"} />
</SelectField>
<DatePicker
value={this.props.date}
onChange={this.props.dateChanged}
/>
</div>
);
}
}
const mapStateToProps = createStructuredSelector({
date: makeSelectDate(),
duration: makeSelectDuration(),
});
function mapDispatchToProps(dispatch, a, b) {
return {
dispatch,
dateChanged: (any, date) => {
dispatch(changeDate(date));
//I want other duration value to dispatch an action to backend
},
durationChanged: (event, index, value) => {
dispatch(changeDuration(value));
//I want other date value to dispatch an action to backend
}
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Options);
I think, you need do:
1) Set only one prop "onChange" as function. And add props to define values on init. It like initValueDuration.
2) Set state with 3 fields
3) Add method onChange(state). This method will call: this.prop.onChange(this.state)
Say you have the two states duration, date in yourReducer. Then, you need two functions: durationChanged, dateChanged.
function durationChanged(newDuration) {
return (dispatch: Redux.Dispatch<any>, getState: () => RootState) => {
dispatch({
type: "DURATION_CHANGED",
payload: newDuration
});
const date = getState().yourReducer.date;
yourAsyncCallToTheBackEnd(newDuration, date);
...
}
}
function dateChanged(newDate) {
return (dispatch: Redux.Dispatch<any>, getState: () => RootState) => {
dispatch({
type: "DATE_CHANGED",
payload: newDate
});
const duration = getState().yourReducer.duration;
yourAsyncCallToTheBackEnd(duration, newDate);
...
}
}
In your mapDispatchToProps, you add
function mapDispatchToProps(dispatch: Redux.Dispatch<any>, ownProps: any): any{
return {
durationChanged: (newValue) => durationChanged(newValue),
dateChanged: (newValue) => dateChanged(newValue)
};
}
In yourReducer, you check the type of the action and change the corresponding state.
In your onChange handler of date input, you do this.props.dateChanged(event.target.value)
Checkout the redux-thunk
Related
Redux Form has FieldArray field:
https://redux-form.com/6.0.0-rc.3/docs/api/fieldarray.md/
I am trying to delete multiple of items from it but remove() method only works for a single removal perhaps because each time the fields get one item smaller and the index determined by me is bigger than the fields array:
<MultiSelect
placeholder="Delete project group"
onChange={(v) => {
const diff = difference(addedGroups, v)
if (!isEmpty(diff)) {
const groupToDelete = diff[0]
forEach(projectsByGroup[groupToDelete], p => removeElement(addedProjects.indexOf(p)))
deleteGroup(groupToDelete)
}}
options={projectGroupNames}
value={addedGroups}
inline
/>
Where removeElement is fields.remove FieldArray function. How to remove correctly multiple items from FieldArray selectively?
Update:
I have also tried to use change in my reducers like that:
import { change } from 'redux-form'
export const deleteVariantSearchProjectGroup = (projectGroupGuid) => {
return (dispatch, getState) => {
const state = getState()
const projectsInGroup = state.projectsByProjectGroup[projectGroupGuid]
const allProjectFields = getProjectsFamiliesFieldInput(state)
const remainingProjectFields = allProjectFields.filter(projectField => !projectsInGroup.includes(projectField.projectGuid))
change(SEARCH_FORM_NAME, 'projectFamilies', remainingProjectFields)
dispatch({ type: UPDATE_VARIANT_SEARCH_ADDED_GROUPS, newValue: without(getState().variantSearchAddedProjectGroups, projectGroupGuid) })
}
}
I get correctly an array remainingProjectFields but then change(SEARCH_FORM_NAME, 'projectFamilies', remainingProjectFields) does not do anything.
I was not able to actually find a way to remove fields one by one with fields.remove but ultimately I solved it by using a reducer and updating Redux Form state using change method:
import { change } from 'redux-form'
export const deleteVariantSearchProjectGroup = (projectGroupGuid) => {
return (dispatch, getState) => {
const state = getState()
const projectsInGroup = state.projectsByProjectGroup[projectGroupGuid]
const allProjectFields = getProjectsFamiliesFieldInput(state)
const remainingProjectFields = allProjectFields.filter(projectField => !projectsInGroup.includes(projectField.projectGuid))
dispatch(change(SEARCH_FORM_NAME, 'projectFamilies', remainingProjectFields))
dispatch({ type: UPDATE_VARIANT_SEARCH_ADDED_GROUPS, newValue: without(getState().variantSearchAddedProjectGroups, projectGroupGuid) })
}
}
and deleteVariantSearchProjectGroup = deleteGroup in the very first jsx code snippet in the question.
I'm trying to change the zoom level by passing a level of 13 into my action creator.
But get this error:
Error: Given action "ZOOM_SELECTED", reducer "zoom" returned undefined. To ignore an action, you must explicitly return the previous state. If you want this reducer to hold no value, you can return null instead of undefined.
Do you know how to change the current state passing a new zoom level into my action creator?
render() {
console.log(this.props.zoom)
return (
<div>
<Map className='map' center={this.props.mapCenter} zoom={this.props.zoom}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Marker position={this.props.mapCenter}>
<Popup>
A pretty CSS3 popup. <br /> Easily customizable.
</Popup>
</Marker>
</Map> <button onClick={() => this.props.selectZoom((13))} >Ă„ndra zoom</button>
</div >
)
}
};
const mapStateToProps = (state) => {
return {
zoom: state.zoom,
mapCenter: state.mapCenterPosition
}
};
const mapDispatchToProps = dispatch => {
return {
selectZoom: () => dispatch(selectZoom())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MapComponent); ```
Action Creator
export const selectZoom = (zoom) => {
console.log('action',zoom)
return {
type: 'ZOOM_SELECTED',
payload: zoom
};
};
Reducer
const zoomReducer = (state = 8, action) => {
if(action.type === 'ZOOM_SELECTED') {
return action.payload;
} else {
return state;
}
};
The problem is in the mapDispatchToProps - you are not passing the zoom to the action in the callback. You need to do this:
const mapDispatchToProps = dispatch => {
return {
selectZoom: (zoom) => dispatch(selectZoom(zoom))
}
}
Without it, no argument gets passed and the payload has value of undefined. Since you are returning payload directly in the reducer, it appears as if it is not returning anything.
I have a redux store with multiple teams.
const store = {
selectedTeamId: 'team1';
teams: {
team1: { ... },
team2: { ... },
team3: { ... },
},
};
At any given time a teamId is set.
Now given that I must select the team using the ID each time I call mapStateToProps(), I feel this is cumbersome.
Instead of doing this all the time:
mapStateToProps({ selectedTeamId, teams }) {
return {
team: teams[selectedTeamId],
}
}
Can I pre-process the store using some middleware instead of repeating this pattern in map state to props?
Approach suggested by Redux docs is to create a selector for currently active team and reuse it across all components
// selector itself is a pure function of state
// usually put in separate file, or in file with reducer
const activeTeamSelector = state => state.teams.teams[state.teams.selectedTeamId]
// in connect
const mapStateToProps = (state) => ({
activeTeam: activeTeamSelector(state),
})
That, of course, if you are using combineReducers and teams reducer is called teams in state. If you aren't, and selectedTeamId and teams are contained right in your store, following will work
const activeTeamSelector = state => state.teams[state.selectedTeamId]
Notice how I only had to change selector for this, and not every mapStateToProps in all the components
read more about Normalizing Store State and Computing Derived Data in Redux docs
Using a middleware for this scenario isn't performant (if I understood your question correctly :) ). I will outline 3 options you can use to achieve this:
Option 1
return both selectedTeamId and teams in mapStateToProps, this will allow you to find the team you need for each selected id:
mapStateToProps({ selectedTeamId, teams }) {
return {
selectedTeamId,
teams
}
}
That way you can access these props in render:
render() {
const { teams, selectedTeamId } = this.props;
return <Team team={teams.find(team => team.id === selectedTeamId)} />
}
Note: <Team /> is just a component I made for demonstration
Option 2
you can use reselect library to avoid recomputing this prop:
import { createSelector } from 'reselect'
const teams = state => state.teams;
const selectedTeamId = state => state.selectedTeamId;
const subtotalSelector = createSelector(
teams,
selectedTeamId,
(teams, selectedTeamId) => items.find(team => team.id === selectedTeamId)
)
Option 3
Create an action that will dispatch 'SELECT_TEAM' with the teamId
export function setSelectedTeam(id) {
return { type: types.SELECT_TEAM, payload: id };
}
Create a reducer for that type and return selectedTeam:
[types.SELECT_TEAM]: (state, payload)=> {
return {
...state,
selectedTeam: state.teams.find(team => team.id === payload.id)
};
},
That way you can have a selector for selectedTeam
export const getSelectedTeam = state => state.selectedTeam;
Hope it helps
I eventually used reselect, with thanks to the recommendation of #jank.
One of things I wanted to do was abstract away the need for selectors to appear in mapStateToProps. In order to do that, I wrapped redux connect. This allows insertion of a denormalizer function before mapStateToProps.
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
const getActiveTeamId = state => state.activeTeamId;
const getAllTeams = state => state.teams;
const teamSelector = createSelector(
getActiveTeamId,
getAllTeams,
(activeTeamId, teams) => teams[activeTeamId],
);
function denormalizer(mapStateToProps) {
return state => {
return mapStateToProps({ team: teamSelector(state) });
};
}
export default function reConnect(mapStateToProps = null, actions = null) {
const denormalizedMapStateToProps = denormalizer(mapStateToProps);
return function callConnect(Component) {
return connect(denormalizedMapStateToProps, actions)(Component);
};
}
I'm loading it like that:
const store = createStore(
AppReducer,
composeEnhancers(applyMiddleware(createDebounce(), thunkMiddleware,
websocketMiddleware)),
);
I've got the following action:
export const toggleCardLayersMenu = (index, value) => ({
type: "CARD:TOGGLE_LAYERS_MENU",
index: index,
value: value,
});
Which I dispatch something to open a menu and something to close it.
Now, the chrome extension display the action with a small delay if the action is used to open the menu, and won't display the action at all if it's used to close the menu.
It occurs only with that specific action. What are the possible causes, and what can be done?
Edit:
This is where I dispatch the action:
...
<IconButton
onClick={ e => dispatches.toggleCardLayersMenu(e.currentTarget) }
><LayersIcon />
</IconButton>
<Menu
anchorEl={ card.menuBtnElem }
open={ card.isMenuOpen }
onRequestClose={ () => dispatches.toggleCardLayersMenu(null) }
style={ styles.menu }
>
...
This is the dispatchToProps():
const mapDispatchToProps = (dispatch, ownProps) => ({ dispatches: {
updateCardLayers(value) {
dispatch(displayActions.updateCardLayers(ownProps.index, value));
},
toggleCardLayersMenu(value) {
dispatch(displayActions.toggleCardLayersMenu(ownProps.index, value));
},
...
In the example below, I am using mapDispatchToProps to bind the onSubmit event to the save() Action Creator. This works - the save() Action Creator logs the message to console.
The issue is that it does not subsequently dispatch the 'TEST_SAVE' action - i.e. the reducer never receives an action of type 'TEST_SAVE'.
I have a vanilla redux form working as below, but I am new to redux-form and I wonder what I might be doing wrong?
const reducers = {
// ... your other reducers here ...
form: formReducer.plugin({
contact: (state, action) => {
switch(action.type) {
case 'TEST_SAVE':
return state;
default:
return state;
}
}
})
};
const reducer = combineReducers(reducers);
const store = createStore(reducer);
function mapDispatchToProps(dispatch) {
return {
onSubmit: (val) => dispatch(actionCreators.save(val)),
}
}
var actionCreators = {
save: (value) => {
console.log('SAVE action done!');
return {type: 'TEST_SAVE', value};
},
};
const MyContactForm = connect(
mapDispatchToProps
)(ContactForm)
<Provider store={store}>
<MyContactForm />
</Provider>
An excerpt of the ContactForm class:
class ContactForm extends Component {
render() {
const { error, handleSubmit, pristine, reset, submitting } = this.props;
return (
<form
onSubmit={handleSubmit}
>
....
)
}
}
ContactForm = reduxForm({
form: 'contact'
})(ContactForm);
const MyContactForm = connect(
mapDispatchToProps
)(ContactForm)
The first parameter to connect() is mapStateToProps, not mapDispatchToProps. Should be:
const MyContactForm = connect(
undefined, // <------------- mapStateToProps
mapDispatchToProps
)(ContactForm)
Never use break inside a reducer. Always return the state object.