modular-redux-thunk
modular-redux-thunk copied to clipboard
Suggestion: allow creating selectors and actions at the `combineModules` level
This idea probably needs a few revisions before it's workable in the library.
I've noticed that it is convenient to add actions and selectors in a combined module... for example:
// Sample submodules
const loading = {
reducer: (state = false, action) => {
switch (action.type) {
case START_FETCH: return true;
case FINISH_FETCH: return false;
default: return state;
}
},
selectors: { isLoading: (state) => state },
actions: {},
}
const data = {
reducer: (state = null, action) => {
switch (action.type) {
case FINISH_FETCH: return action.data;
default: return state;
}
},
selectors: { getData: (state) => state },
actions: {},
};
const metadata = {
reducer: (state = null, action) => {
switch (action.type) {
case FINISH_FETCH: return action.metadata;
default: return state;
}
},
selectors: { getMetadata: (state) => state },
actions: {},
}
// Here comes the interesting stuff
const moduleActions = {
// These actions are used by multiple submodules within this
// module, but not outside of it
startFetch: () => ({ type: START_FETCH }),
finishFetch: (result) => ({
type: FINISH_FETCH,
data: result.data,
metadata: result.metadata
}),
// This action dispatches actions from within this module.
// It may also use actions from submodules, although this isn't demonstrated here
fetch: () => async (dispatch) => {
dispatch(moduleActions.startFetch());
const result = await fetchDataFromApi();
dispatch(moduleActions.finishFetch(result));
dispatch(moduleActions.)
},
};
const moduleSelectors = {
getEnhancedData = (state) => {
// This line mentions "data" three times, not to mention having to name a
// variable something similar!
const d = data.selectors.getData(state.data);
const m = metadata.selectors.getMetadata(state.metadata);
return d.map(item => enhanceItemWithMetadata(item, m));
},
};
const module = combineModules({
loading, data, metadata,
});
// Adding these high-level actions and selectors is hacky, but workable
Object.assign(module.actions, moduleActions);
Object.assign(module.selectors, moduleSelectors);
Maybe something like this would be better:
// Actually pretty happy with the syntax here
const moduleActions = {
startFetch: () => ({ type: START_FETCH }),
finishFetch: (result) => ({
type: FINISH_FETCH,
data: result.data,
metadata: result.metadata
}),
fetch: () => async (dispatch) => {
dispatch(moduleActions.startFetch());
const result = await fetchDataFromApi();
dispatch(moduleActions.finishFetch(result));
dispatch(moduleActions.)
},
};
// Like global selectors, a list of selectors is passed as the first argument
const moduleSelectors = {
getEnhancedData = (selectors, state) => {
// ahhh much better
const data = selectors.getData(state);
const metadata = selectors.getMetadata(state);
return d.map(item => enhanceItemWithMetadata(item, m));
},
};
const module = combineModules({
loading, data, metadata,
}), {
// Ability to pass actions and selectors in an options field
actions: moduleActions,
selectors: moduleSelectors
};
This is pretty similar to the concept of global actions/selectors. Maybe I should have moved that logic to the level of combineModules in the first place.