import React from 'react';
import {useReducer, createContext } from 'react';
let initialState = {
associatedOrgs:[],
};
const reducer = (state, action) => {
switch (action.type) {
case "logout":
return initialState;
case "set-associated-org":
return {...state, associatedOrgs: action.payload };
default:
return state;
}
};
export const UserContext = createContext(initialState);
const UserContextProvider = ({children}) => {
const [state, dispatch] = useReducer(reducer, initialState);
const logout = () => {
dispatch({
type: "logout",
});
};
const setAssociatedOrg = (org) => {
dispatch({
type: "set-associated-org",
payload: org,
});
};
return(
<UserContext.Provider
value={{
associatedOrgs: state.associatedOrgs,
logout,
setAssociatedOrg
}}
>
{children}
</UserContext.Provider>
)
};
export default UserContextProvider;

- 8,509
- 7
- 49
- 78

- 1
- 1
-
I hope you don't mind, but I fixed the formatting of your post, and while doing so I noticed you've got some nesting errors in the render section, within the props passed to the context provider. You don't actually state what problem you're having, so I could be off-base, but it's possible that was part of your problem. – Tom Apr 11 '21 at 03:18
1 Answers
Here's how I would do it:
case "add-associated-org":
return {
...state,
associatedOrgs: state.associatedOrgs.concat(action.orgToAdd)
}
case "remove-associated-org":
return {
...state,
associatedOrgs: state.associatedOrgs.filter(org => org !== action.orgToRemove)
}
Then you just need to define a couple action-creators that dispatch those events to your reducer; the pattern you have for set-associated-orgs
should work (although it would be better to define them outside your component since they can be pure functions and there's no need to make the engine re-construct them on every render).
EDIT
Q: How would you pass the dispatch to the action creator if created outside?
A: (Excellent question.) I'd make each action-creator a pure function, and then wrap them all up in a function that binds them to a particular dispatch
. Unfortunately, this results in reversing the performance gains. More on that in a bit.
Here's a quick-and-dirty first draft:
function getActions( dispatch ) {
const logout = () => {
dispatch({
type: "logout",
});
};
const setAssociatedOrg = (org) => {
dispatch({
type: "set-associated-org",
payload: org,
});
};
return {
logout,
setAssociatedOrg
}
}
And then inside the component I'd have:
const [state, dispatch] = useReducer(reducer, initialState);
const actions = getActions(dispatch)
That won't scale very well, and for testing purposes each action-creator should be exported, so a better pattern would look like this:
// src/actions/index.js
export function logout( dispatch ) { /* impl */ }
export function setAssociatedOrgs( dispatch, orgs ) { /* impl */ }
// create a collection of all action creators so we can process them all
const ACTIONS = { logout, setAssociatedOrgs }
// app code should only ever import this function
// the other functions would be private if not for unit-testing
export default function bindActionsToDispatch( dispatch ) {
return Object.keys(ACTIONS).reduce(( hash, actionCreatorName ) => ({
...hash,
[actionCreatorName]: ACTIONS[actionCreatorName].bind(undefined, dispatch)
}), {})
}
This has benefits and drawbacks.
One quirk of this approach is that each action creator is defined with an extra dispatch
arg. The aggregator function makes that invisible to calling code (so, you'd still do actions.setAssociatedOrgs(orgs)
), but I don't know what it'll look like to your IDE's syntax suggestions.
Like I said, I'm assuming this will re-introduce the performance penalty of re-defining all the action-creators on every component render, which is bad. And now I'm doing it with a bunch of extra code, which is worse.
So, if I were doing this, I would create a custom React hook built on top of useReducer
; it would create the state container, define the root reducer, and bind all the action creators. (It makes sense to bind the actions creators at that point, because the set of actions that a store supports is determined by what reducer(s) it houses, and that is set when you provide the root reducer to useReducer
. ) I'm pretty sure you can set all of that up in a way that will only execute once, with subsequent renders returning identical store & store-bound action-creators.
Using the new hook might look like this:
let [ orgState, orgActions ] = useDucksStore(initialOrgState, orgReducers, orgActionCreators)
// and then:
orgActions.setAssociatedOrgs(newOrgs)
Stepping back, I think the "action creator" pattern is not a good fit for the bare useReducer
hook. useReducer
is nice because it's lightweight, and you're giving that up if you decide that every dispatch
call has to be wrapped in a "Ducks"-style action creator. If Ducks is your thing, you really should build a custom hook to facilitate and enforce those access patterns. Otherwise you'll have to do all this work inside each component that wants to manage state similarly, not to mention whatever is the performance cost of reconstructing a miniature Ducks ecosystem on every blit.

- 8,509
- 7
- 49
- 78
-
1How would you pass the dispatch to the action creator if created outsite? – iagowp Apr 11 '21 at 03:25