How to execute buisness logic in NgRx reducers - ngrx

Hi am using NgRx store for state management in my angular project.
My goal is to clear few state properties on action dispatch. The array of property names are passed to the action.
// Action
export const clearFields = createAction(
'[SC Base Data] Clear Fields',
props<{ fields: string[] }>()
);
// Reducers
on(SCActions.clearFields, (state: SCState,fields: string[]) => ({
...state,
SCData: {
...state.SCData
}
})),
/
How can iterate over fields array and set the state properties value as blank

If by "blank" you mean null, I believe what you're looking for is something along these lines:
on(SCActions.clearFields, (state: SCState, fields: string[]) => ({
// create an Object whose keys are all elements of fields and every value is null
const clearedState = {};
fields.forEach(field => {
clearedState[field] = null;
});
// return new copy of state nulling all fields from fields array
return {
...state,
...clearedState
};
}))

Related

How to update or change the entire propery of a particular redux object

Am having issues updating a redux store in NEXTJS. am building a CV platform with the feature to preview users' input almost immediately into a preview page. this cv platform has the experience, education etc that a normal cv platform should have and am using the react hook form package to manage forms and also to enhance dynamic forms.
so because the preview component will be another project on its own, I need the best way to pass data from my app into the preview app. Then I thought of passing every form input, cv styles, and data to a redux store so the preview component can just get the user's data from the store
as I said earlier, am using the react hooks form library to manage my form, so to update the store in real-time whenever the user inputs anything, I imported the useWatch hook from react hook form to watch my form in case of any data changes. so I set up a useEffect to listen for any useWatch change to dispatch the whole useWatch data to the store. NB: this data contains an array of objects
my challenge right now is that anytime I dispatch the data to store, redux toolkit or probably immer frowns at what am doing and will always break the app, returning back this error message
TypeError: Cannot assign to read only property 'jobTitle' of object '#<Object>'
at set (index.esm.mjs?b902:507:1)
at onChange (index.esm.mjs?b902:1749:1)
at HTMLUnknownElement.callCallback (react-dom.development.js?ac89:4164:1)
at Object.invokeGuardedCallbackDev (react-dom.development.js?ac89:4213:1)
at invokeGuardedCallback (react-dom.development.js?ac89:4277:1)
at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js?ac89:4291:1)
at executeDispatch (react-dom.development.js?ac89:9041:1)
at processDispatchQueueItemsInOrder (react-dom.development.js?ac89:9073:1)
at processDispatchQueue (react-dom.development.js?ac89:9086:1)
at dispatchEventsForPlugins (react-dom.development.js?ac89:9097:1)
at eval (react-dom.development.js?ac89:9288:1)
at batchedUpdates$1 (react-dom.development.js?ac89:26140:1)
at batchedUpdates (react-dom.development.js?ac89:3991:1)
at dispatchEventForPluginEventSystem (react-dom.development.js?ac89:9287:1)
at dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay (react-dom.development.js?ac89:6465:1)
at dispatchEvent (react-dom.development.js?ac89:6457:1)
at dispatchDiscreteEvent (react-dom.development.js?ac89:6430:1)
Below is the redux store and how am setting the experience
const initialState: IResume = {
templatePrimaryColor: "#335384",
top: 0,
showOverlay: false,
cv_object: {
templateId: 1,
personalInformation: {} as PersonalInformation,
experiences: [] as Experience[],
educations: [] as Education[],
skills: [] as Skill[],
awards: [] as Award[],
certificates: [] as Certificate[],
publications: [] as Publication[],
references: [] as Reference[],
},
};
export const resumeSlice = createSlice({
name: "resume",
initialState,
reducers: {
setExperience: (state, action: PayloadAction<Experience[]>) => {
// state.cv_object.experiences = [...action.payload];
state.cv_object.experiences = Object.assign(state.cv_object.experiences, action.payload);
},
},
});
Below is how am setting the forms and how am dispatching it
//React hooks form initialSetup
const { register, control, handleSubmit } = useForm<CvObject>({
defaultValues: {
experiences: [{ ...ExperienceDefaultValues }],
},
});
//usefieldArray for dynamic forms
const { append, fields, remove } = useFieldArray({ control, name: "experiences" });
//dispatch the entire form data to experience if any changes is being made
const formValues = useWatch({ control, name: "experiences" });
const [currentFormIndex, setCurrentFormIndex] = useState(0);
useEffect(() => {
if (!useAi) dispatch(hideOverlay());
else dispatch(showOverlay());
}, [useAi]);
useEffect(() => {
dispatch(setExperience(formValues));
}, [formValues]);
const handleAddAnotherExperience = () => {
setCurrentFormIndex((prev) => prev + 1);
append({ ...ExperienceDefaultValues });
};
const handleDelete = (index: number) => {
remove(index);
if (currentFormIndex > 0) setCurrentFormIndex((prev) => prev - 1);
};
const handleEdit = (index: number) => {
setCurrentFormIndex(index);
};
This is the sample object of the experience am passing but Array of Experience
export interface Experience {
companyName: string;
fromYear: string;
toYear: string;
fromMonth: string;
toMonth: string;
currentlyWorking: boolean;
achievements: string;
description: string;
city: string;
country: string;
index: number;
jobTitle: string;
}
So what am really expecting from this is how to change the store or how to replace the previous experience that is in the store with the incoming experience that is being dispatched.
React hook form is the guy handling new object, removing new object with their useFieldArray hooks.
First of all, you shouldn't directly mutate the data in the redux store, so you can use the object spread operator to create new objects and secondly you should always have a return statement in your slice. So your resumeslice should actually be like this
export const resumeSlice = createSlice({
name: "resume",
initialState,
reducers: {
setExperience: (state, action: PayloadAction<Experience[]>) => {
state = {
...state,
cv_object: {
...state.cv_object,
experiences: action.payload
}
}
return state
},
},
});
I believe this should work

useReducer and state managing

hey guys im learning the useReducer hook and for the most part it seems to be quite similar to redux (minus the action being sent to the store etc)
the thing i seem to ALWAYS have problems with when i get more complex state management situations is trying to alter my state in the ways i would like to. in my code i am essentially trying to have a user select a track and add it to a list of favorite songs. my code seems to be replacing the state and not adding to it
here is my initial state and my useReducer and then lastly my add function (which when a button is pressed down below it sends in a track to be added to the list
const initialState = {
name:'',
duration:''
};
const [favorites, dispatch]=useReducer(reducer, initialState)
const add=(song)=>{
dispatch({type:'ADD', payload:song})
console.log(favorites)
}
THIS is the part that is confusing me. in my reducer i have this
export const reducer=(song, action)=>{
switch(action.type){
case 'ADD':
return {...song, name:action.payload}
}
WHICH is essentially adding a new object everytime called name: trackname BUT i do not want to overwrite the last item. i feel like i am using spread wrong and also returning the incorrect payload maybe?
my final state keeps looking like this
{name: "deep end", duration: ""}
when i want it to look something like this
``
[{name: "deep end", duration: ""}
{name: "ok", duration: ""}
{name: "testtrack", duration: ""}
]`
i have tried setting the initial state to somethingm like this
const initialState = {
favorites:{
[ADD TRACKS HERE]
}
};
BUT CANT seem to overwrite the state correctly so that it ADDS to the array. it keeps overwritting the last one
Redux's guide to Immutable Update Patterns is a great resource on how to update nested data in a way that doesn't mutate your state.
With an array there are two main ways to immutably add an element.
With spread syntax:
const newArray = [...songs, newSong];
With .concat(), which returns a new array that contains the additional items (that is different from .push() which mutates the array and just returns the length).
const newArray = songs.concat(newSong);
You can decide what you want the shape of your state to be. Storing the array to a property favorites is fine, but adds another layer of complexity to your updates.
export const reducer = (state, action) => {
switch (action.type) {
case "ADD":
return {
...state,
favorites: [...state.favorites, action.payload]
};
default:
return state;
}
};
const initialState = {
favorites: [] // initial state has an empty array
};
const [state, dispatch] = useReducer(reducer, initialState);
// here favorites is just a property of state, not the whole state
const favorites = state.favorites;
I would recommend that that state should just be the array of favorites itself.
export const reducer = (favorites, action) => {
switch (action.type) {
case "ADD":
return [...favorites, action.payload]
default:
return favorites;
}
};
// favorites is the whole state
// initial state is an empty array
const [favorites, dispatch] = useReducer(reducer, []);
In either case, we are expecting the action.payload to be a complete song object, not just the name.
dispatch({ type: "ADD", payload: { name: "Deep End", duration: "3:22" } });
You could extract that into a helper function. In Redux terms we call this function an Action Creator.
const addFavorite = (name, duration) => ({
type: "ADD",
payload: { name, duration }
});
dispatch(addFavorite("Deep End", "3:22"));

Redux - Update store with same function from different files

being rather new to react.js + redux, I'm facing the following conundrum:
I have multiple files, which need to update the store in exactly the same way, based on the stores current state. Currently I simply copy-paste the same code (along with the needed mapStateToProps), which goes again DRY.
Similar to something like the below, where getData is an Ajax call living in the actions file and props.timeAttribute is coming from mapStateToProps:
props.getData(props.timeAttribute).then((newState) => {
console.log(newState)
})
Would a function like that go in the actions file? Can the current state be read from within that actions file? Or does one normally create some sort of helperFile.js in which a function like that lives and is being called from other files?
Thanks!
If your file is executing the same action, then yes, you would put the action creator in a separate file and export it. In theory, you can put state in an action by passing the state as a parameter, but the philosophy behind an action is that it announces to your application that SOMETHING HAPPENED (as denoted by the type property on the return value of the action function). The reducer function responsible for handling that type subsequently updates the state.
You can access the current state of the store inside of an action creator like this:
export const testAction = (someParam) => {
return (dispatch, getState) => {
const {
someState,
} = getState(); //getState gets the entire state of your application
//do something with someState and then run the dispatch function like this:
dispatch(() => {type: ACTION_TYPE, payload: updatedState})
}
I like this approach because it encapsulates all the logic for accessing state inside of the one function that will need to access it.
DO NOT modify the state inside of the action creator though! This should be read only. The state of your application should only be updated through your reducer functions.
Yes, it is recommended to maintain a separate file for your actions.
Below is an example of how i use an action to fetch information and dispatch an action.
export const fetchComments = () => (dispatch) => {
console.log("Fetch Comment invoked");
/*you can use your Ajax getData call instead of fetch.
Can also add parameters if you need */
return fetch(baseUrl + 'comments')
.then(response => {
if (response.ok){
return response;
}
else {
var error = new Error('Error ' + response.status + ': ' + response.statusText);
error.response = response;
throw error;
}
},
error => {
var errmess = new Error(error.message);
throw errmess;
})
.then(response => response.json())
.then(comments => dispatch(addComments(comments)))
.catch(error => dispatch(commentsFailed(error.message)));
}
/* Maintain a separate file called ActionTypes.js where you can store all the ActionTypes as Strings. */
export const addComments = (comments) => ({
type : ActionTypes.ADD_COMMENTS,
payload : comments
});
export const comments = (errMess) => ({
type : ActionTypes.COMMENTS_FAILED,
payload : errMess
});
Once, you receive dispatch an action, you need an reducer to capture the action and make changes to your store.
Note that this reducer must be a pure function.
export const comments = (state = { errMess: null, comments:[]}, action) => {
console.log("inside comments");
switch (action.type) {
case ActionTypes.ADD_COMMENTS:
return {...state, errMess: null, comments: action.payload};
case ActionTypes.COMMENTS_FAILED:
return {...state, errMess: action.payload};
default:
return state;
}
};
Don't forget to combine the reducers in the configureStore().
const store = createStore(
combineReducers({
comments
}),
applyMiddleware(thunk,logger)
);
In your components where you use the Actions, use
const mapDispatchToProps = dispatch => ({
fetchComments : () => dispatch(fetchComments()),
})
Note to export the component as
export default connect(mapStateToProps,mapDispatchToProps)(Component);

redux-thunk and in app architecture - want to render only views in views and dispatch GET actions in separate component

I am using react-redux and redux-thunk in my application and there are two things I am trying to do:
I want to be able to share the results of a GET request in two components. I know you can do this by connecting the two components to the store, but I want to make it so if the user lands on X page, then Y page cannot make the same GET request again (these two components are Thumbnail and Carousel). In other words, the GET request should be made once (not 100% sure what best practice is here for redux-thunk), and each component should be able to access the store and render the results in the component (this is easy and I can do)
currently the GET request is the parent of the two children view components, which (I think) doesn't make sense. I only want to render a child view component in the parent view, not a GET request. If unclear it will make more sense if you read my code below
This is parent view (Gallery), which has a child component which dispatches an action to redux (using redux-thunk) that makes an API (FetchImages):
import ...
export default function Gallery() {
return(
<>
<GalleryTabs />
<GalleryText />
<div className="gallery-images-container">
<FetchImages /> ----> this is making an API request and rendering two child view components
</div>
</>
)
}
This is FetchImages, which is dispatching the action (fetchImages) which makes the API call
import ...
function FetchImages({ fetchImages, imageData }) {
useEffect(() => {
fetchImages()
}, [])
return imageData.loading ? (
<h2>Loading</h2>
) : imageData.error ? (
<h2>Something went wrong {imageData.error}</h2>
) : (
<>
<Thumbnail /> -----> these two are views that are rendered if GET request is successful
<Carousel />
</>
)
}
const mapStateToProps = state => {
return {
imageData: state.images
}
}
const mapDispatchToProps = dispatch => {
return {
fetchImages: () => dispatch(fetchImages())
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(FetchImages)
I think it makes more sense to have something like this:
import ...
export default function Gallery() {
return(
<>
<GalleryTabs />
<GalleryText />
<div className="gallery-images-container">
<Thumbnail /> -----> Thumbnail should be rendered here but not Carousel ( FetchImages here adds unnecessary complexity )
</div>
</>
)
}
tldr
What are some best practices to follow if two components can dispatch an action which makes a GET request but the dispatch should only be made once per time the user is on the website?
Using redux-thunk, what are some best practices for separating concerns so that children view components are within parent view components and the smarter components which are shared between children view components (such as dispatching actions that make GET requests) are dispatched when the user lands on the page without the views and smarter components being directly together?
I'm a noob so thank you for any help
Your first question: your component container should just dispatch the action that it needs data. How you should store async result in state and later handle result from state is something not covered in this answer but the later example uses a component named List that just dispatches getting a data page, selects the data page and dumps the data page in UI. The tunk action does an early return if the data is already in state.
In production application you probably want to store async api result with loading, error, requested and a bunch of extra info instead of assuming it is there or not there.
Your second question is partly answered by the first answer. Component containers should just dispatch an action indicating they need data and not have to know about the data already being there, already being requested or any of that stuff.
You can group functions that return a promise with the following code:
//resolves a promise later
const later = (time, result) =>
new Promise((resolve) =>
setTimeout(() => resolve(result), time)
);
//group promise returning function
const createGroup = (cache) => (
fn,
getKey = (...x) => JSON.stringify(x)
) => (...args) => {
const key = getKey(args);
let result = cache.get(key);
if (result) {
return result;
}
//no cache
result = Promise.resolve(fn.apply(null, args)).then(
(r) => {
cache.resolved(key); //tell cache promise is done
return r;
},
(e) => {
cache.resolve(key); //tell cache promise is done
return Promise.reject(e);
}
);
cache.set(key, result);
return result;
};
//permanent memory cache store creator
const createPermanentMemoryCache = (cache = new Map()) => {
return {
get: (key) => cache.get(key),
set: (key, value) => cache.set(key, value),
resolved: (x) => x,//will not remove cache entry after promise resolves
};
};
//temporary memory cache store creator when the promise is done
// the cache key is removed
const createTmpMemCache = () => {
const map = new Map();
const cache = createPermanentMemoryCache(map);
cache.resolved = (key) => map.delete(key);
return cache;
};
//tesgting function that returns a promise
const testPromise = (m) => {
console.log(`test promise was called with ${m}`);
return later(500, m);
};
const permanentCache = createPermanentMemoryCache();
const groupTestPromise = createGroup(permanentCache)(
testPromise,
//note that this causes all calls to the grouped function to
// be stored under the key 'p' no matter what the arguments
// passed are. In the later List example I leave this out
// and calls with different arguments are saved differently
() => 'p'
);
Promise.all([
//this uses a permanent cache where all calls to the function
// are saved under the same key so the testPromise function
// is only called once
groupTestPromise('p1'),//this creates one promise that's used
// in all other calls
groupTestPromise('p2'),
])
.then((result) => {
console.log('first result:', result);
return Promise.all([
//testPromise function is not called again after first calls
// resolve because cache key is not removed after resolving
// these calls just return the same promises that
// groupTestPromise('p1') returned
groupTestPromise('p3'),
groupTestPromise('p4'),
]);
})
.then((result) => console.log('second result', result));
const tmpCache = createTmpMemCache();
const tmpGroupTestPromise = createGroup(tmpCache)(
testPromise,
//all calls to testPromise are saved with the same key
// no matter what arguments are passed
() => 'p'
);
Promise.all([
//this uses a temporary cache where all calls to the function
// are saved under the same key so the testPromise function
// is called twice, the t2 call returns the promise that was
// created with the t1 call because arguments are not used
// to save results
tmpGroupTestPromise('t1'),//called once here
tmpGroupTestPromise('t2'),//not called here using result of t1
])
.then((result) => {
console.log('tmp first result:', result);
return Promise.all([
//called once here with t3 becuase cache key is removed
// when promise resolves
tmpGroupTestPromise('t3'),
tmpGroupTestPromise('t4'),//result of t3 is returned
]);
})
.then((result) =>
console.log('tmp second result', result)
);
const tmpUniqueKeyForArg = createGroup(createTmpMemCache())(
testPromise
//no key function passed, this means cache key is created
// based on passed arguments
);
Promise.all([
//this uses a temporary cache where all calls to the function
// are saved under key based on arguments
tmpUniqueKeyForArg('u1'), //called here
tmpUniqueKeyForArg('u2'), //called here (u2 is different argument)
tmpUniqueKeyForArg('u1'), //not called here (already called with u1)
tmpUniqueKeyForArg('u2'), //not called here (already called with u2)
])
.then((result) => {
console.log('unique first result:', result);
return Promise.all([
tmpUniqueKeyForArg('u1'), //called with u1 tmp cache removes key
// after promise is done
tmpUniqueKeyForArg('u3'), //called with u3
tmpUniqueKeyForArg('u3'), //not called, same argument
]);
})
.then((result) =>
console.log('unique second result', result)
);
Now that we have code to group functions that return promises (function is not called when called again with same argument) we can try to apply this to thunk action creators.
Because a trunk action creator is not (...args)=>result but (...args)=>(dispatch,getState)=>result we can't pass the action creator directly to createGroup I created createGroupedThunkAction that adopts the function to group from (...args)=>(dispatch,getState)=>result to ([args],dispatch,getState)=>result while still returning a function with the right signature: (...args)=>(dispatch,getState)=>result.
Here is the example snippet:
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { createSelector } = Reselect;
//resolves a promise later
const later = (time, result) =>
new Promise((resolve) =>
setTimeout(() => resolve(result), time)
);
//group promise returning function
const createGroup = (cache) => (
fn,
getKey = (...x) => JSON.stringify(x)
) => (...args) => {
const key = getKey(args);
let result = cache.get(key);
if (result) {
return result;
}
//no cache
result = Promise.resolve(fn.apply(null, args)).then(
(r) => {
cache.resolved(key); //tell cache promise is done
return r;
},
(e) => {
cache.resolve(key); //tell cache promise is done
return Promise.reject(e);
}
);
cache.set(key, result);
return result;
};
//thunk action creators are not (...args)=>result but
// (...args)=>(dispatch,getState)=>result
// so here is how we group thunk actions
const createGroupedThunkAction = (thunkAction, cache) => {
const group = createGroup(
cache
)((args, dispatch, getState) =>
thunkAction.apply(null, args)(dispatch, getState)
);
return (...args) => (dispatch, getState) => {
return group(args, dispatch, getState);
};
};
//permanent memory cache store creator
const createPermanentMemoryCache = (cache = new Map()) => {
return {
get: (key) => cache.get(key),
set: (key, value) => cache.set(key, value),
resolved: (x) => x,//will not remove cache entry after promise is done
};
};
const initialState = {
data: {},
};
//action types
const MAKE_REQUEST = 'MAKE_REQUEST';
const SET_DATA = 'SET_DATA';
//action creators
const setData = (data, page) => ({
type: SET_DATA,
payload: { data, page },
});
const makeRequest = (page) => ({
type: MAKE_REQUEST,
payload: page,
});
//standard thunk action returning a promise
const getData = (page) => (dispatch, getState) => {
console.log('get data called with page:',page);
if (createSelectDataPage(page)(getState())) {
return; //do nothing if data is there
}
//return a promise before dispatching anything
return Promise.resolve()
.then(
() => dispatch(makeRequest(page)) //only once
)
.then(() =>
later(
500,
[1, 2, 3, 4, 5, 6].slice(
(page - 1) * 3,
(page - 1) * 3 + 3
)
)
)
.then((data) => dispatch(setData(data, page)));
};
//getData thunk action as a grouped function
const groupedGetData = createGroupedThunkAction(
getData,//no getKey function so arguments are used as cache key
createPermanentMemoryCache()
);
const reducer = (state, { type, payload }) => {
console.log('action:', JSON.stringify({ type, payload }));
if (type === SET_DATA) {
const { data, page } = payload;
return {
...state,
data: { ...state.data, [page]: data },
};
}
return state;
};
//selectors
const selectData = (state) => state.data;
const createSelectDataPage = (page) =>
createSelector([selectData], (data) => data[page]);
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(
//improvided thunk middlere
({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
}
)
)
);
//List is a pure component using React.memo
const List = React.memo(function ListComponent({ page }) {
const selectDataPage = React.useMemo(
() => createSelectDataPage(page),
[page]
);
const data = useSelector(selectDataPage);
const dispatch = useDispatch();
React.useEffect(() => {
if (!data) {
dispatch(groupedGetData(page));
}
}, [data, dispatch, page]);
return (
<div>
<pre>{data}</pre>
</div>
);
});
const App = () => (
<div>
<List page={1} />
<List page={1} />
<List page={2} />
<List page={2} />
</div>
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>
In that example there are 4 List components rendered, two for page 1 and two for page 2. All 4 will dispatch groupedGetData(page) but if you check the redux dev tools (or the console) you see MAKE_REQUEST and resulting SET_DATA is only dispatched twice (once for page 1 and once for page 2)
Relevant grouping functions with permanent memory cache is less than 50 lines and can be found here

redux - how to create a generic reducer?

In react-redux, I'm trying to create a generic reducer, meaning a reducer with common logic that writes (with that logic) each time to a different section in the store.
I read Reusing Reducer Logic over and over, I just can't wrap my head around it. Let's say I have this state:
{
a: { b: { c: {...} } } },
d: { c: {...} }
}
a and d are two reducers combined with combineReducers() to create the store. I want section c to be managed with common logic. I wrote the reducer logic for c, I wrapped it to create a higher-order reducer with a name.
How do I create the a reducer with the c reducer with reference to its location (and also d accordingly)? Maybe in other words, how do I create a reducer with a "store address", managing his slice of the state, agnostic to where it is?
I sure hope someone understands me, I'm new to redux and react.
Reducer are now simple function and can be reuse somewhere else
const getData = (state, action) => {
return {...state, data: state.data.concat(action.payload)};
};
const removeLast = (state) => {
return {...state, data: state.data.filter(x=>x !== state.data[state.data.length-1])};
}
Action type and reducer function are now declared in an array
const actions = [
{type: 'GET_DATA', reducer: getData},
{type: 'REMOVE_LAST', reducer: removeLast}
];
Initial state for the reducer
const initialState = {
data: []
}
actionGenerators creates an unique Id using Symbol and assign that Id to actions and reducer function.
const actionGenerators = (actions) => {
return actions.reduce((a,c)=>{
const id = Symbol(c.type);
a.actions = {...a.actions, [c.type]: id};
a.reducer = a.reducer ? a.reducer.concat({id, reducer: c.reducer}) : [{id, reducer: c.reducer}];
return a;
},{});
}
reducerGenerators is a generic reducer creator.
const reducerGenerators = (initialState, reducer) => {
return (state = initialState, action) => {
const found = reducer.find(x=>x.id === action.type);
return found ? found.reducer(state, action) : state;
}
}
Usage
const actionsReducerCreator = actionGenerators(actions);
const store = createStore(reducerGenerators(initialState, actionsReducerCreator.reducer));
const {GET_DATA} = actionsReducerCreator.actions;
store.dispatch({type: GET_DATA});
Checkout my github project where I have a working todo application utilizing this implementation.
Redux-Reducer-Generator

Resources