Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at $19.99/month. Cancel anytime
From there, each modal component class can be connected to the store, retrieve any other needed data, and dispatch specific actions for both internal behavior as well as ultimately dispatching a 'HIDE_MODAL' action when it's ready to close itself. This way, the handling of modal display is centralized, and nested components don't have to "own" the details of showing a modal.
Unfortunately, this pattern runs into a problem when we want to create and use a very generic component, such as a ColorPicker. We would probably want to use the ColorPicker in a variety of places and features within the UI, each needing to use the "result" color value in a different way, so having it dispatch a generic 'COLOR_SELECTED' action won't really suffice. We could include some kind of a callback function within the action, but that's an anti-pattern with Redux, because using non-serializable values in actions or state can break features like time-travel debugging. What we really need is a way to specify behavior specific to a feature, and use that from within the generic component.
The answer that I came up with is to have the modal component accept a plain Redux action object as a prop. The component that requested the dialog be shown should specify that action as one of the props to be passed to the modal. When the modal is closed successfully, it should copy the action object, attach its "return value" to the action, and dispatch it. This way, different parts of the UI can use the "return value" of the generic modal in whatever specific functionality they need.
Here's how the different pieces look:
/ In some arbitrary component:
const onColorSelected = {
type : 'FEATURE_SPECIFIC_ACTION',
payload : {
someFeatureSpecificData : 42,
}
};
this.props.dispatch({
type : 'SHOW_MODAL",
payload : {
modalType : "ColorPicker",
modalProps : {
initialColor : "red",
/ Include the pre-configured action object as a prop for the modal
onColorSelected
}
}
});
/ In the ColorPicker component:
handleOkClicked() {
if(this.props.onColorSelected) {
/ If the code that requested this modal included an action object,
/ clone the action, attach our "return value", and dispatch it
const clonedAction = _.clone(this.props.onColorSelected);
clonedAction.payload.color = this.state.currentColor;
this.props.dispatch(clonedAction);
}
this.props.hideModal();
}
/ In some reducer:
function handleFeatureSpecificAction(state, action) {
const {payload} = action;
/ Use the data provided by the original requesting code, as well as the
/ "return value" given to us by the generic modal component
const {color, someFeatureSpecificData} = payload;
return {
...state,
[someFeatureSpecificData] : {
...state[someFeatureSpecificData],
color
}
};
}
This technique satisfies all the constraints for our problem. Any part of our application can request that a specific modal component be shown, without needing a nested component to "own" the modal. The display of the modal is driven by our Redux state. And most importantly, we can specify per-feature behavior and use "return values" from generic modals while keeping both our actions and our Redux state plain and serializable, ensuring that features like time-travel debugging still work correctly.
About the author
Mark Erikson is a software engineer living in southwest Ohio, USA, where he patiently awaits the annual heartbreak from the Reds and the Bengals. Mark is author of the @acemarke. He can be usually found in the Reactiflux chat channels, answering questions about React and Redux. He is also slightly disturbed by the number of third-person references he has written in this bio!