Redux: dispatching an action according to cross-reducers state - javascript

This is my combined reducers:
export const element = (state, action) => {
...
}
export const elementSelection = (state, action) => {
...
}
const holoApp = combineReducers({
elements,
elementSelection
})
The state of elementSelection holds the current selected element. I want to be able to dispatch an action in the elementsReducer according to the current selected element.
For instance if store.getState().elementSelection.elementType equals "placeholder" this action will be disptached:
store.dispatch(replaceElement(...));
And otherwise this action will be dispatched:
store.dispatch(addElement(...));
My only guess is to put this logic in the app class which consists of the store and I wonder whether it is the best practice for dispatching actions according to cross-reducers state.

you can achive that in action creator with redux-thunk
const someAction = elem => (dispatch, getState)=>{
if(getState().elementSelection.elementType == "placeholder" ){
dispatch(replaceElement(...));
}else{
dispatch(addElement(...));
}
}

Related

Redux - action gets dispatched, but doesn't work with the reducer

I've got a weird problem where I dispatch an action correctly, but the reducer doesn't get called. I'm using redux-thunk and everything is set up exactly the same how it's set up in one of my other projects. The action type matches, but nothing happens.
Also, if I put a console.log on the default case of the reducer, it gets logged 4 times every time I refresh the page, so I suppose the reducer gets loaded correctly.
export const submitForm = names => {
console.log(names); //This logs what it's supposed to log every time
// I press the submit button
return dispatch => {
dispatch({
type: "SUBMIT_FORM",
names
});
};
};
export default function form(state = {}, action) {
switch (action.type) {
case "SUBMIT_FORM":
console.log(action.names);
return Object.assign({}, state, {
names: action.names
});
default:
return state;
}
}
//My component is connected like this:
const mapStateToProps = state => ({});
const mapDispatchToProps = dispatch => ({
formActions: bindActionCreators(formActions, dispatch)
});
export default connect(mapStateToProps, mapDispatchToProps)(Home);
It seems a little strange to me that an action creator returns a function. Action creatorsnormally return an action, that is, an object. Async action creators returns functions. So, you can try refactor your action creator that will return a objetct, and then you pass the action creator to the dispatch as an argument.
export const submitForm = names => {
console.log(names); //This logs what it's supposed to log every time
// I press the submit button
return {
type: "SUBMIT_FORM",
names
}
};
dispatch(submitForm(names));
Besides that, according to react-redux.js.org/mapdispatch to use bindActionCreators in mapDispatchToProps, mapDispatchToProps does not return an object, but does return bindActionCreators.
const mapDispatchToProps = dispatch =>
bindActionCreators(formActions, dispatch);
another point, if you need to pass an argument to your action creator I would do like this:
const mapDispatchToProps = dispatch => ({
submitForm: (names) => dispatch(submitForm(names))
});
Unfortunately, I can't see all of your code. I hope I helped you with something.

How to validate radio button inside reducer using the event object passed to the reducer?

I have the following reducer in my React app:
const initialState = {
genderRadio : false,
ageRadio : false
}
const reducer = ( state = initialState , action ) => {
switch(action.type) {
case "VALI_RADIO_INP":
console.log(action.payload.target);
return state
}
return state;
}
export default reducer;
action.payload is basically the event object that is passed to the reducer, like so from my component:
validateRadioInput : (e) => dispatch({ type: 'VALI_RADIO_INP' , payload : e })
What I would like to do in my reducer is check if the input element has been checked or not and update the state. How do I using the event object check if a element is checked or not checked?
NOTE::-
Before integrating redux I was checking if the checkbox is checked calling a method that resided right inside my component like so:
Array.from(document.getElementsByName('customer_gender')).some( (elem , idx) => { return elem.checked })
But of course I can't use this anymore; any suggestions on how I can validate the checkbox in my reducer using the event object?
First set attribute name to your checkbox element like so:
<input type="checkbox" name="genderRadio"/>
or:
<input type="checkbox" name="ageRadio"/>
And modify your code that set correct piece of state depending on the attribute name of checkbox element.
Example:
const reducer = ( state = initialState , action ) => {
switch(action.type) {
case "VALI_RADIO_INP":
console.log(action.payload.target);
return { ...state, [payload.target.name]: payload.target.checked };
}
return state;
}
export default reducer;
How do i using the event object check if a element is checked or not checked ?
You shouldn't do that. Your Redux reducers shouldn't be coupled to the DOM if you can help it. Though, it is possible that you can traverse the DOM from the event's target, if you're using React you shouldn't be depending on the DOM at all.
One way to do it is to get your component to have a data representation of the view. This could be your React component's state. Or, you could grab it from the DOM if you're not using React with something like this:
validateRadioInput: (e) => {
const checkedArr = Array.from(document.getElementsByName('customer_gender'))
.map(elem => elem.checked);
return dispatch({
type: 'VALI_RADIO_INP',
payload: checkedArr,
});
}
// reducer
const reducer = ( state = initialState , action ) => {
switch(action.type) {
case "VALI_RADIO_INP":
const valid = action.payload.some(checked => checked);
return { ...state, valid };
}
return state;
}
Ultimately, though, I don't agree with the concept of doing form validation sort of logic in Redux -- just do it in the component and then dispatch some action to Redux if it's valid. Redux shouldn't have to deal with every nitty-gritty state in your application; just state that affects multiple components in potentially complex ways.
Also, note that you may be trying to fix an HTML problem in JS since you can only check one radio button at a time, anyway, and you could just make the HTML have a required field. See HTML5: How to use the "required" attribute with a "radio" input field

Redux state not updating with javascript object

I have this container and component and the object yeast that Im trying to put in my store. However, its not working whenever I try and save it. The object looks like this
{ yeastType : value, temp: value}
Container.js
const mapDispatchToProps = (dispatch) => {
return {
handleYeastChange: (yeast) => {
dispatch(actions.updateYeast(yeast))
}
}
};
const RecipeYeastContainer = connect(
mapStateToProps,
mapDispatchToProps
)(RecipeYeast);
Component.js
updateYeastState = (updatedYeast) => {
this.props.handleYeastChange(updatedYeast)
};
I have no errors in the console or anything. When I open my redux dev tools its telling me the state has already been updated by the time the action is called. And thus only ever saving the first letter thats input into my field. It never persists it. Its really weird. Its also never showing up in the UI. I can type as much as I want and see the action firing and the state keeping the first letter but its not showing up in the input.
Whats weird is that when I change the code to pass in both yeastType and temp to the property function and construct the object in there it works. (See below)
This works: Container.js
const mapDispatchToProps = (dispatch) => {
return {
handleYeastChange: (yeastType, temp) => {
const yeast = {yeastType, temp}
dispatch(actions.updateYeast(yeast))
}
}
};
Component.js
updateYeastState = (updatedYeast) => {
this.props.handleYeastChange(updatedYeast.yeastType, updatedYeast.temp)
};
I cannot figure out why this is happening. I thought I could just pass the object all the way through and not have to reconstruct it.
Do you dispatch your action correctly? And in using redux, you are not updating the state of the component, you're updating the store and then the value in component is from your mapStateToProps function that get from the store. Say it you're updating your store with the object named yourReducer store. ex:
Container.js:
const mapStateToProps = (state) => ({
inputValue: state.yourReducer.value
})
const mapDispatchToProps = (dispatch) => ({
inputHandler: (e) => {
dispatch(yourAction({type: 'CHANGE_INPUT', value: e.target.value}))
// The store catches the action and pass to the reducer that can read the action's type
// in `yourReducer` will catch the 'CHANGE_INPUT' type and
// return a new value as same as the value that passed from the action
}
})
export default connect(mapStateToProps)(Component)
Component.js
export default class Component extends React.Component {
{ your custom function here ... }
render() {
const { inputValue, inputHandler } = this.props
return (
<div>
{/* The input will be the same as the inputValue changed */}
<input value={inputValue} onChange={inputHandler} />
</div>
)
}
}
For debugging redux you can try this redux-devtool.

Good pattern for redux action in callback

Supposing I have an update comment action. When a user updates comment after getting a successful result from Promise I should close comment editor. This is my sample code from my project:
export const updateComment = (comment,callBack/* ? */) => {
return (dispatch, getState){
api.updateComment({...comment}).then((result) => {
/* Do something */
callback() /* ? */
})
}
}
In react component I use action like the following code:
handleUpdateComment(){
dispatch(actions.updateComment(this.state.comment,this.closeCommentEitor)
}
It works well but I think is not a good pattern to close comment editor. I'm looking a correct pattern to close editor without passing callBack like I did if any.
When you are using redux-thunk, you can dispatch an action from another action.
What you can do is that, commentEditor have a state which you store in redux and based on that state open or close the commentEditor
export const updateComment = (comment, comment_id) => {
return (dispatch, getState){
api.updateComment({...comment}).then((result) => {
/* Do something */
dispatch({type: 'CLOSE_COMMENT_EDITOR', id: comment_id})
})
}
}
After this in a reducer on this action change the state of redux store, something like
import update from 'immutability-helper'
var initialState = [{commentId: '1', commentEditorOpenStatus: false}, {commentId: '2', commentEditorOpenStatus: false}]
const reducer = (state = initialState, action) => {
switch(action.type) {
'CLOSE_COMMENT_EDITOR':
const idx = state.findIndex(obj => obj.commentId == action.id);
return update(state, {
[idx]: {
commentEditorOpenStatus: {
$set: false
}
}
})
// Other action handlers here
default: return state
}
}
The only thing that updates your application's state is your reducers.
The reducer should be responsible to update the state of your application and not your action (you are now passing getState).
I suggest you to look at redux-promise-middleware
The middleware enables optimistic updates and dispatches pending, fulfilled and rejected actions, which can be intercepted by the reducer.

Add logic to the store?

I have a redux application with a "campaign" reducer/store.
Currently I have repeated code to check if a specific campaign is loaded or needs an API call to fetch details from the DB. Much simplified it looks like this:
// Reducer ----------
export default campaignReducer => (state, action) {
const campaignList = action.payload
return {
items: {... campaignList}
}
}
// Component ----------
const mapStateToProps = (state, ownProps) => {
const campaignId = ownProps.params.campaignId;
const campaign = state.campaign.items[campaignId] || {};
return {
needFetch: campaign.id
&& campaign.meta
&& (campaign.meta.loaded || campaign.meta.loading),
campaign,
};
}
export default connect(mapStateToProps)(TheComponent);
Now I don't like to repeat the complex condition for needFetch. I also don't like to have this complex code in the mapStateToProps function at all, I want to have a simple check. So I came up with this solution:
// Reducer NEW ----------
const needFetch = (items) => (id) => { // <-- Added this function.
if (!items[id]) return true;
if (!items[id].meta) return true;
if (!items[id].meta.loaded && !items[id].meta.loading) return true;
return false;
}
export default campaignReducer => (state, action) {
const campaignList = action.payload
return {
needFetch: needFetch(campaignList), // <-- Added public access to the new function.
items: {... campaignList}
}
}
// Component NEW ----------
const mapStateToProps = (state, ownProps) => {
const campaignId = ownProps.params.campaignId;
const campaign = state.campaign.items[campaignId] || {};
return {
needFetch: state.campaign.needFetch(campaignId), // <-- Much simpler!
campaign,
};
}
export default connect(mapStateToProps)(TheComponent);
Question: Is this a good solution, or does the redux-structure expect a different pattern to solve this?
Question 2: Should we add getter methods to the store, like store.campaign.getItem(myId) to add sanitation (make sure myId exists and is loaded, ..) or is there a different approach for this in redux?
Usually computational components should be responsible for doing this type of logic. Sure your function has a complex conditional check, it belongs exactly inside your computational component (just like the way you currently have it).
Also, redux is only for maintaining state. There's no reason to add methods to query values of the current state inside your reducers. A better way would be having a module specifically for parsing your state. You can then pass state to the module and it would extract the relevant info. Keep your redux/store code focused on computing a state only.
Your approach is somewhat against the idiomatic understanding of state in redux. You should keep only serializable data in the state, not functions. Otherwise you loose many of the benefits of redux, e.g. that you can very easily stash your application's state into the local storage or hydrate it from the server to resume previous sessions.
Instead, I would extract the condition into a separate library file and import it into the container component where necessary:
// needsFetch.js
export default function needsFetch(campaign) {
return campaign.id
&& campaign.meta
&& (campaign.meta.loaded || campaign.meta.loading);
}
// Component ----------
import needsFetch from './needsFetch';
const mapStateToProps = (state, ownProps) => {
const campaignId = ownProps.params.campaignId;
const campaign = state.campaign.items[campaignId] || {};
return {
needFetch: needsFetch(campaign),
campaign,
};
}
export default connect(mapStateToProps)(TheComponent);

Categories

Resources