In the vuex actions we have a context object which contains dispatch, commit, state, getters
Can we modify this context object? maybe when we re creating vuex store? I want to replace the dispatch in context with my own version of dispatch
Is something like this not a variant?
actions: {
myCustomDispatch(context, myAction) {
if (something) {
context.store.dispatch(myAction);
}
}
}
Related
I have a mapStateToProps function that fires off an async request to fetch user data from the server. The successful result of that, fires another action which actually sets the user to the state. All of this works fine. What I need to do now is, after the user data is set on the state, I need to fire another action (setEditUserModel) using the user value that was set from fetchUser.
Since state is not accessible in mapStateToProps, I've added the mergeProps connect option. It's my understanding that this combines ("merges") the mapStateToProps and mapDispatchToProps together. When I do this with the code below, stateProps.user in mergeProps is null even though the actual value when I inspect my AppState is not null.
function mapStateToProps(state: AppState): EditUserStateProps {
return {
user: state.user.data
};
}
function mapDispatchToProps(dispatch: Dispatch<ActionTypes>): EditUserDispatchProps {
return {
fetchUser: (id: number) => {
return fetchUser(id)(dispatch);
},
setEditUserModel: (user: User) => {
dispatch(actionCreators.setEditUserModel(user));
}
}
};
function mergeProps(stateProps: EditUserStateProps, dispatchProps: EditUserDispatchProps, ownProps: EditUserContainerProps) {
return {
...ownProps,
...stateProps,
...dispatchProps,
fetchUser: (id: number) => {
return dispatchProps.fetchUser(id).then(() => {
dispatchProps.setEditUserModel(stateProps.user);
});
}
}
}
Why is my stateProps.user value null?
stateProps.user is null because of how javascript handles a closure's scope chain. When your closure that calls dispatchProps.setEditUserModel() gets executed after the stateProps.fetchUser() Promise is resolved, it is actually referencing the stateProps object that was within scope during its declaration, which happens to still contain a null user prop.
If you were to mutate the Redux store's state instead of copying it and then somehow pass that reference all the way down to your mergeProps(), you might see this bug disappear because now the stateProps in the mergeProps() closure references the same object that is in the redux store. However, this is not advisable because Redux reducers should be pure functions.
Instead, you might consider resolving the Promise returned in stateProps.fetchUser() with the user object and pass that to dispatchProps.setEditUserModel().
I'm using react-redux & redux-thunk for my project.
I have to inject my actions to a component by using connect.
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
My task is one level up. I don't want just to inject multiple actions in this form:
{
doThis(),
doThat()
}
But in this form:
{
this: {
doThis1(),
doThis2()
}
that: {
doThat()
}
}
So basically my problem is that I want to dispatch multiple action-creator files because I want them organized as such.
I tried this version which obviously doesn't work because dispatch is not injected in each Thunk Action Creator:
import * as actions from './actions'
const mapDispatchToProps = (dispatch) => {
return {
dataActions: {
...actions.dataActions
}
};
}
export default connect(null, mapDispatchToProps)(Component);
So my final question is:
Am I even supposed to use Redux this way? Can I organize my files this way, if so how?
If instead of having one property per action creator, you want to structure your bound action creators in a couple of properties that each contain a group of action creators, you can do something like this:
import { bindActionCreators, .. } from 'redux';
..
const mapDispatchToProps = (dispatch) => {
return {
dataActions: bindActionCreators(actions.dataActions, dispatch),
otherActions: bindActionCreators(actions.otherActions, dispatch),
..
};
};
The first argument to bindActionCreators is an object containing action-creator functions (e.g. an imported module that exports only action creators). In your actual component, you should then be able to use this.props.dataActions.someDataAction(..).
If the question is just about whether you can keep different action creators in different files, you might not even want to group the action creators and just do this:
return {
...bindActionCreators(actionCreatorsFromOneModule, dispatch),
...bindActionCreators(actionCreatorsFromAnotherModule, dispatch),
..
};
All:
I am pretty new to Redux, when I follow its Reddit API example, there is one code snippet confuse me so much:
In AsyncApp.js, there is:
componentDidMount() {
const { dispatch, selectedSubreddit } = this.props
dispatch(fetchPostsIfNeeded(selectedSubreddit))
}
I wonder where the dispatch and selectedSubreddit get bind to this.props?
Thanks
That example is using the connect() function from react-redux to inject certain parts of the Redux state and the store's dispatch() function as props in that component. See the 'Usage With React' part of the Redux docs for more information.
For example:
App.js:
export class App extends Component {
//...
}
function mapStateToProps(state) {
const { selectedReddit, postsByReddit } = state
const {
isFetching,
lastUpdated,
items: posts
} = postsByReddit[selectedReddit] || {
isFetching: true,
items: []
}
return {
selectedReddit,
posts,
isFetching,
lastUpdated
}
}
export default connect(mapStateToProps)(App)
The connect() function here is taking the mapStateToProps() function above to inject the appropriate parts of the Redux state as props in the <App /> component. The keys of the object returned by mapStateToProps() correspond to the names of the props injected, and the corresponding values are the values of those injected props.
connect() can also take a second argument, matchDispatchToProps(), which can be used to inject specific action dispatch functions as props in your component. Whether or not you supply any arguments to connect(), it will inject your Redux store's dispatch() function as a prop called dispatch.
These connected container components receive state updates from the store, so when your Redux state changes, the connected container components will receive new props accordingly.
I've seen the argument for separating actions and reducers because they have a many-to-many relationship.
I don't think that actually applies in Redux though. Because there's only 1 datastore, actions to reducers should be 1-to-many.
Typically reducers apply to a specific change for a specific datastore.
MY_ACTION = "MY_ACTION"
function reducer(state, action) {
switch(action.type) {
case MY_ACTION: // stuff with my action to create new state
default: return state
}
}
We can combine multiple reducers with combineReducers so why not define the handler for an action with the action itself.
For instance
class Action {
constructor(type) {
this.type = type
this.handlers = []
}
add_handler(handler) {
this.handlers += handler
}
get_reducer() {
reducer = combineReducers(this.handlers)
return (state, action) => {
if(action.type == this.type) {
return reducer(state, action)
}
return state
}
}
}
With the "ducks" pattern, we end up putting the main reducers in the same module as the action declaration.
Is there any reason to keep reducers + actions separate with redux?
The main reason for separating the action creators from the reducer function is that the reducer function must be a pure function. If you wanted to do something in an action creator, like an asynchronous API call for instance, then you could not put this in the reducer. There's a great explanation on this here.
I'm trying to build keyboard shortcut support into my React/Redux app in an idiomatic React/Redux way. The way I am planning to do this is to have the following action creator and associated action:
registerShortcut(keyCode, actionCreatorFuncReference)
The reducer would then update a registeredShortcuts object in the redux store with a mapping of keyCodes to actionCreatorFuncReferences. Then my root component would listen for keyup and see if there is an associated keyCode registered and if so, then dispatch the mapped action via the action creator function reference.
However, this would be the first time I am storing function references in my Redux store. To date, I've only had objects with keys with vanilla values (strings, ints, etc).
The Redux docs says:
You should do your best to keep the state serializable. Don’t put anything inside it that you can’t easily turn into JSON.
Does this suggest it's a bad idea to store such function references in my Redux store? If so, what is a better way to accomplish what I'm trying to do in React/Redux?
An alternative approach is just to store the mapping of keyCodes and function references in the root react component itself, but that didn't feel very Redux-like since now the application state is not in the Redux store.
No, you should not store function references in the redux store. They are not serializable, and as you mentioned state should be serializable at all times. The most redux-friendly approach I can think of is just to keep the map of hotkeys to their actionCreatorFuncNames.
TL;DR: You don't. The store state must be serializable at all times (as Nathan answered).
The Redux way is via enhancers, or the Redux-Observable way via dependencies.
NL;PR: Based on the Redux docs example, what you want is to pass the reference in your action(1), ignore it your reducer(2) and use it in your enhancer(3):
//... in your action:
const data={val:1}, ref=()=>{};
const action = {type:'ACTION_WITH_REF', data, ref}; //(1)
//... in your reducer:
case 'ACTION_WITH_REF':
return {...state, data: action.data}; //(2)
//... and in your enhancer:
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';
export const myRefStore= {};
function refHandler({ getState }) {
return next => action => {
switch(action.type){
// this can be done more elegantly with a redux-observable
case 'ACTION_WITH_REF':
myRefStore.aRef = action.ref; // (3)
break;
}
// be sure to maintain the chain of the store
const returnValue = next(action);
// otherwise, your midddeware will break the store
return returnValue;
};
}
const store = createStore(
reducers,
initialState,
applyMiddleware(refHandler)
);
Note: As far as there are no side-effects in your enhancers, you are good to go. Be aware that you could have obtained the refs directly in the reducers, but such an approach keeps the reference at the reducer-level and misses the point of combineReducers(). With an enhancer, you keep them all in one place(myRefStore).
One final observation is that a redux store is not an any-data store but a state store, thus why we need to handle functions and other non-state related stuff in enhancers. You can leverage the enhancer backbone to Redux-Observable and inject myRefStore via dependencies.
I'm new to redux, but the way I see it, you could pass the key code and an action type.
Then a reducer could be listening for that action type and make changes accordingly.
Here is an example using the library Mousetrap:
// On your Container
function registerShortcut(element, dispatch, keyCode, actionType) {
Mousetrap(element).bind(keyCode, function(e) {
dispatch({
type: actionType,
payload: {
keyCode: keyCode,
event: e
}
});
});
});
mapDispatchToProps = function(dispatch) {
return {
onMount: function(element) {
registerShortcut(element, dispatch, ['command+f', 'ctrl+f'], 'OPEN_SEARCH');
},
onUnmount: function(element) {
Mousetrap(element).unbind(['command+f', 'ctrl+f']);
}
};
};
// On your Component
componentDidMount() {
onMount(ReactDOM.findDOMNode(this));
};
componentWillUnmount() {
onUnmount(ReactDOM.findDOMNode(this));
};
// On your reducer
function reducer(oldState, action) {
if (action.type == 'OPEN_SEARCH') {
//... make changes ...//
return newState;
}
return oldState;
};
This way, keyboard shortcuts will dispatch an action. The reducer will make the changes necessary to the state. And finally, the application can re-render.