DEV Community

Cover image for For Myself: Explaining Reducers
Adriana DiPietro
Adriana DiPietro

Posted on

For Myself: Explaining Reducers

Reducers for My Bookmark Resource

Today I will be explaining my bookmarkReducer.js file of my React-Redux frontend application.

I utilize reducers to portray CRUD functionality, updates to state, and the return of a new state.

🌱☁️This is really for myself, but if you want to read along then come join me!☁️🌱

Reducers

export default (state = {bookmarks: [], loading: false}, action) => {
    switch (action.type) {
        case LOADING_BOOKMARKS:
            return {
                ...state, 
                bookmarks: [...state.bookmarks],
                loading: true
            }
        case GET_BOOKMARKS:
            return {bookmarks: action.payload, loading: false}
        case CREATE_BOOKMARK:
            return {
                ...state, 
                bookmarks: [...state.bookmarks, action.payload],
                loading: false 
            }
        case FAVORITE_BOOKMARK:
            return state.bookmarks.map(bookmark => {
                if (bookmark.id !== action.payload.id){
                    return bookmark
                }
                return {
                    ...bookmark,
                    favorite: !bookmark.favorite
                }
            })
        case DELETE_BOOKMARK:
            const removeDeletedBookmark = state.bookmarks.filter(bookmark => bookmark.id !== action.payload) 
            return {bookmarks: removeDeletedBookmark, loading: false}
        default:
            return state
    }
}

Enter fullscreen mode Exit fullscreen mode

A reducer is a function that returns a new state. It does not return the original state modified. A reducer takes in an action and a state as its arguments. For my bookmarkReducer, it takes in an initial state of "bookmarks" set equal to an empty object with one (1) attribute: "loading" set to "false". Thus, when I call "state" in any of my case statements, it is pointing to this initial state.

export default (state = {bookmarks: [], loading: false}, action) => {
    switch (action.type) {
Enter fullscreen mode Exit fullscreen mode

Speaking of case statements, my reducer is coded in a switch-case statement syntax. This allows for many action types. I use a switch statement to pass in "action.type"; a switch statement will evaluate whatever expression is passed to it, matching the expression's value to a case, and finally, executes the code written within that specific case. Basically, the switch statement looks at the action.type field to decide what happens.

My first case "LOADING_BOOKMARKS" returns a spread operator of "state" (...state). The spread operator is used to take an existing object (my initial state) and add/modify it while still keeping the integrity of the initial state. So, we return a new state object that has all the existing state data but has a new array "bookmarks: [...state.bookmarks]" for our state's bookmarks field. This new array encompasses all of the old bookmarks and any new bookmark object (hence the spread operator again). I then finally change the loading attribute to "true" since we are loading our bookmarks.

        case LOADING_BOOKMARKS:
            return {
                ...state, 
                bookmarks: [...state.bookmarks],
                loading: true
            }
Enter fullscreen mode Exit fullscreen mode

My next case "GET_BOOKMARKS" returns our state's field "bookmarks" and hands it the "action.payload" to fulfill its value. Our payload is the data given to us from fetch after an action has been dispatched. We will talk more about this later. I then change our loading attribute to false because we fetched the bookmarks collection; so it is not loading.

         case GET_BOOKMARKS:
            return {bookmarks: action.payload, loading: false}
Enter fullscreen mode Exit fullscreen mode

Almost identical to the case "LOADING_BOOKMARKS", "CREATE_BOOKMARK" takes in the initial state and adds to it our new bookmark field that holds both our existing bookmarks (...state.bookmarks) and any new bookmark(action.payload). Using a spread operator we shovel the new content and merge it to our initial state and return a completely new state.

         case CREATE_BOOKMARK:
            return {
                ...state, 
                bookmarks: [...state.bookmarks, action.payload],
                loading: false 
            }
Enter fullscreen mode Exit fullscreen mode

This next case statement "FAVORITE_BOOKMARK" the initial state's bookmark collection and iterates over it. By iterating over the collection, we can search to see if the bookmark's id (of the bookmark we are trying to favorite) matches the action.payload's id. If it does not, we return the bookmark object unchanged. However, if it does then we use a spread operator to return the new state of the bookmark with an inverted value of the bookmark's favorite attribute.

         case FAVORITE_BOOKMARK:
            return state.bookmarks.map(bookmark => {
                if (bookmark.id !== action.payload.id){
                    return bookmark
                }
                return {
                    ...bookmark,
                    favorite: !bookmark.favorite
                }
            })
Enter fullscreen mode Exit fullscreen mode

Finally we can discuss my "DELETE_BOOKMARK" case statement. I start by declaring and assigning a constant variable called "removeDeletedBookmark". I assign it to the initial state's bookmark collection and I use the filter method to filter through each bookmark. If the iteration comes across a bookmark's id that does not match the action.payload's id, then it is deleted, or removed, from the collection. I finish the case statement by return the new state of the bookmark collection and set the loading attribute to false.

case DELETE_BOOKMARK:
            const removeDeletedBookmark = state.bookmarks.filter(bookmark => bookmark.id !== action.payload) 
            return {bookmarks: removeDeletedBookmark, loading: false}
Enter fullscreen mode Exit fullscreen mode

At the very end of our switch-case statement, we have a default case. This is essential to every switch-case. If an action type occurs that we have not defined or does not exist in our application, our default case returns the initial state as to not break anything.

🌱🌱🌱

Again, this is just for my own amusement + studying. Yet, if you do find yourself reading over this and you would like to suggest, ask a question or continue the discussion, please feel free!

Top comments (0)