You can use the Context Module Functions Pattern to encapsulate a complex set of state changes in a utility function that can be tree-shaken and loaded lazily.
Let's take a look at an example of a simple context and a reducer combo:
I want to focus in on the user of our reducer (the Counter component). Notice that they have to create their own increment and decrement functions which call dispatch. I don't think that's a super great API. It becomes even more of an annoyance when you have a sequence of dispatch functions that need to be called (like you'll see in our exercise).x
The first inclination is to create "helper" functions and include them in the context. Let's do that. You'll notice that we have to put it in React.useCallback so we can list our "helper" functions in dependency lists):
constincrement=React.useCallback( () =>dispatch({type:'increment'}), [dispatch],)constdecrement=React.useCallback( () =>dispatch({type:'decrement'}), [dispatch],)constvalue= {state, increment, decrement}return <CounterContext.Providervalue={value} {...props} />// now users can consume it like this:const {state,increment,decrement} =useCounter()
This isn't a bad solution necessarily. But DanAbramov said:
Helper methods are object junk that we need to recreate and compare for no purpose other than superficially nicer looking syntax.
Dan suggests (and what Facebook does) the use of importable "helpers" that accept dispatch. Let's take a look at how that might work:
This may look like overkill, and it is. However, in some situations this pattern can not only help you reduce duplication, but it also helps improve performance and helps you avoid mistakes in dependency lists.