I recently came across a situation with redux that I had not considered before. I had created an autocomplete component that was using redux to store its internal state. This was all well and good when there was only one autocomplete component per page but when I have multiple autocomplete components on a given page then a change to one component was reflected in every component as you can see in the screen shot below where selecting a value in one component instance is reflected in all component instances:
The problem is that every component is reading and writing to the same redux store state element.
If you look at the code below that uses
combineReducers, there is only one key for the autocomplete state on line 7:
1 2 3 4 5 6 7 8 9 10
For the record, I am now firmly of the opinion that using redux to store a component’s internal state is actually a bad idea. If I was to code the autocomplete component again then I would store the state internally in the component. I still think there is a place though for an instance reducer.
My autocomplete reducer was working as I would expect with one component so I just needed to ensure that any code change would work with multiple components without changing the working reducer code. I also wanted to make this reusable for other situations. I just needed some layers of indirection in my mapStateToProps and any redux reducer that I wanted to ensure was segrating its global state by instance.
The steps I needed to complete this task would be:
- Ensure that any connected component subscribing to redux store state change events had its own unique id.
mapStateToPropssubscribes a component to redux store updates. I would create a
mapStateToPropsthat would retrieve the relevant state that would be tagged with the unique identifier mentioned in step 1.
mapDispatchToPropswraps action creators in a
dispatchfunction which sends an action to a reducer and updates the global store. I could hijack this functionality to send a component identifier with each dispatched action.
- I would need some capability to wrap any reducer and only mark the state in the store by the unique identifier mentioned in step 1.
For the above requirements, I would create a custom connect function that would call the real connect and add some code to ensure that state was retrived by unique identifier. I would also need a function that added some functionality around calling a reducer.
connect connects a component to the redux store. It does this by returning a higher order component which abstracts away the internals of subscribing to store updates. Higher order components are one of the many things I love about react.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
The above code is what is returned from my
instanceConnect function, I will supply the full listing later in the post or you can view it all here.
- Line 3 calls the react-redux connect function with a modified
mapDispatchToProps, I will discuss this later. The returned function accepts a component as an argument which will be the component that receives store update notifications. This connected component which is assigned to the
finalWrappedComponentvariable will be used in the
renderfunction of the higher order component.
- Lines 4 - 20 define a component class named
InstanceComponentthat will be the result of calling
- The main reason for being of the
InstanceComponentclass is to add a unique id to each instance and also to return the connected component that is constructed on line 3.
- An instance id is generated using lodash’s uniqueId utility.
instanceIdproperty is assigned to a member variable in the constructor on line 9 and this is added to the
propson line 18 of the
renderfuntion of the
InstanceComponentrenders the connected component with the
instanceIdappended to the props on line 18.
Now that I can identify each component instance with an
instanceId property that now exists in the props, I somehow need to communicate this unique id to the reducer so I can only pull back the state that is relevant to each component instance.
Below is my modified
mapDispatchToProps that will call the passed in user supplied
mapDispatchToProps that wraps each action creator with
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
- Line 2 pulls the
instanceIdfrom the props.
- Lines 6 - 23 iterates over each key in the supplied object and lines 14 - 22 create a modified version of the action creator.
- When the action creator is called by the client code, the
instanceIdis appended to the
metaproperty of the action on lines 17 and 19. The
metaproperty of an action is just a plain object where additional properties can be added without polluting the original
- The newly appended action is dispatched on line 21.
Now that I can identify a component instance from the action, I need to use this identifier to pull the relevant redux store state information when state changes are published via
mapStateToProps. Below is the modified
mapStateToProps that pulls out the instance state information:
1 2 3 4 5 6 7 8
reducerName argument should marry up to the value you give to the key that is supplied to
instanceIdis pulled from the props on line 2.
- The observant of you will will notice that I am using immutablejs to store the state.
- Lines 4 - 5 use the getIn function of the immutablejs Map which is excellent for dealing with nested values in a state hash. The state either returns the state for that instance if one exists or tries to retrieve data for a property named
initialStatefor this particular slice of the store state pie.
reducerName property on lines 4 -5 is passed in as an argument from the
instanceConnect function that returns the higher order component, you can see the full listing here:
1 2 3 4
mapDispatchToProps are passed to react-redux’s connect.
All that needs to be done is provide a function that can wrap a reducer with some indirection that ensures the state is modified with respect to the
Below is the reducer that accepts another reducer as an argument and ensures that the new version of the state is marked with the
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
instanceIdproperty is retrieved from the
metaproperty that was appended with the
instanceIdin the modified
- Lines 5 - 9 handles the case when redux invokes the
@@initaction. There will be no state at this stage and so we call the reducer with
undefinedon line 6, this will give the reducer the opportunity to supply some initial state. We then append this to a property we will name
initialState. This property can then be retrieved in our modified
mapStateToPropswhen there is no instance state.
- Lines 11 - 13 simply return the state when there is no
instanceIdin the props.
- Line 15 retrieves any instance state.
- Line 17 calls the reducer and passes in any instance state that might already exist. The fact that a reducer is a pure function is critical to this working correctly.
- Line 19 updates the state hash with respect to the
instanceIdby calling the immutablejs setIn function. This update only happens if the state has actually changed.
instanceConnect function ready to use, then it is simply a matter of calling it like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
You also need to wrap the appropriate reducers with the
1 2 3 4 5 6 7 8 9 10
I think what is missing would be to remove the component’s state when the component is unmounted and you could also create the slice when the component is mounted. I’ll possibly look at adding that.
If you disagree or agree with any of the above then please leave a comment below.
Here is a full listing of the code file that contains both the
Here is a test I wrote to drive out the functionality.