DEV Community

Cover image for Learn enough Redux to get started with your projects
Tulsi Prasad
Tulsi Prasad

Posted on

Learn enough Redux to get started with your projects

Alright, as promised we'll look into some code now, but this time we'll master the most basic methods and processes that are used by Redux to manage our state efficiently, before implementing it in our FE applications.

As I said, this is a learning series of posts that I am writing currently while learning to master Redux, so that you could learn with consuming the right amount of content from the start (if you're particularly new to this), without getting intimidated by building a working frontend app with React and Redux, right away. At least, that's not how learning for me takes place. ✌️

What Redux has to offer?

To make things simpler, let me tell you an outline of what we're going to learn today.

✅ To create the global store, that holds the complete state tree in our app

✅ Use various store methods to create, read and update our store.

✅ Basic ways to update our state in Immutable way

Importing Redux from NPM

Step Zero, create a file (say sandbox.js) to fiddle along with. We'll use node today to run the commands, instead of diving into any frontend framework. (so, install node, in not already)

Now, first of all, run this command to get redux from npm. npm install redux or yarn add redux

Then, we'll import Redux to our JS file to with the require command.

const redux = require("redux"); 
Enter fullscreen mode Exit fullscreen mode

Note, we're not using the import {} from "" to include packages as we're not using Babel or anything like that to compile our ES6, its just plain JS.

Redux.createStore()

Let's create the global store that we're talking about, that holds the complete state tree of the application. Most importantly, there should be just one store throughout the app.

const createStore = redux.createStore;

// We'll create the Reducer here, before writing `reducer` inside createStore
// Creating this just so that you won't get a reference error
const reducer = () => {}

const store = createStore(reducer)
Enter fullscreen mode Exit fullscreen mode

This returns what we know as the store. We can only change that by dispatching actions, through a reducer, more on this below.

Note: The createStore we wrote above is in its most basic form, but it can take more arguments than that, which can be useful in more adv usecases, beyond our scope here.

The actual skeleton for createStore is:

createStore(
    reducer,
    [preloadedState], // optional
    [enhancer] // optional
)
Enter fullscreen mode Exit fullscreen mode
  • reducer: It is a function here, passing which is mandatory to create the state. This function is the only point of contact with the store which can change the state object.
  • preloadedState: It is the initial state, however it is optional.
  • enhancer: It is optional and is a function. It is used to enhance the store with third party capabilities like middleware etc.

More on createStore:

Redux Docs

Quick Tip:

You can run the command node <filename.js> anytime, along with some console logs anytime between writing this codes, so that'll fortify your understanding.

Running, node <filename.js> now, will result in following message:

{
  dispatch: [Function: dispatch],
  subscribe: [Function: subscribe],
  getState: [Function: getState],
  replaceReducer: [Function: replaceReducer],
  [Symbol(observable)]: [Function: observable]
}
Enter fullscreen mode Exit fullscreen mode

Don't be scared, these are just some of the methods available on the store which we'll explore soon.

Building the Reducer Function

As discussed many times, prior to this, Reducer is the function that createStore accepts as a parameter in it and through which we can modify our state efficiently. This accepts the previous state and an action that tells which set of actions to do and returns the new state immutably, which goes on to modify the Redux store.

We are going to make a simple yet classic calculator application, that can do basic math operations. This will help to understand Redux better.

As the reducer function takes in the previous state as an argument, lets create an initial state that'll be the default state if not anything else is passed.

let initialState = {
    counter: 1
}
Enter fullscreen mode Exit fullscreen mode

Now, let's create a Reducer, which receives the action object and prepares the new state to pass onto Redux store.

const reducer = (state = initialState, action) => {
    // passing initialState as the default state here

    switch (action.type) {
        case "ADD":
            return {
                    ...state,
                    counter: state.counter + action.value
            }
        case "SUBSTRACT":
            return {
                    ...state,
                    counter: state.counter - action.value
            }
        case "MULTIPLY":
            return {
                    ...state,
                    counter: state.counter * action.value
            }
        case "DIVIDE":
            return {
                    ...state,
                    counter: state.counter / action.value
            }
        default:
            return state;
    }
}
Enter fullscreen mode Exit fullscreen mode

Note: Watch how the new state is returned immutably, using the ES6 spread operator (...), and not in any other mutable way, like this.

// 🚫 shouldn't return state in reducer like this

const newState = state
newState.counter += 1
return newState
Enter fullscreen mode Exit fullscreen mode

How we can return state immutably?

// ✅ should return state in reducer like this

return {
        ...state,
        counter: state.counter + 1
}

// ✅ also possible using ES6 Object.assign()

return {
        Object.assign(
            {},
            state,
            { counter: state.counter + 1 }
        )
}
Enter fullscreen mode Exit fullscreen mode

PS: For now, we only need this much, however we'll learn different immutable update patterns of state in the upcoming post.

Creating an Action Object

Actions are the only source of information that sends data from your application, to the store. These are simple JS objects that has at least a type property which holds a string which tells the Reducer what to do, and then the reducer returns a brand new state.

// An example of an action
{ type: "ADD", value: 5 }
Enter fullscreen mode Exit fullscreen mode

This is the basic most way to declare an action, but however as it's most important property type which is a string and can be easily mistyped by us humans. Resulting in hours and hours of debugging, so we store it in a variable, which on being misspelled our linter throws an error and we can catch the error easily. This is how its done.

const ADD = "ADD"
{ type: ADD, value: 5 }
Enter fullscreen mode Exit fullscreen mode

Learning how to use various Store methods

We are going to use the following store methods to help us manage state efficiently.

☑️ store.dispatch

☑️ store.getState

☑️ store.subscribe

Running our First Action Ever

This is how we are going to signal the reducer that there's a change in any UI component or anything that needs the global store object to change, and you pass the correct type command so that the reducer returns the correct new state. This is how its done.

// we'll dispatch an add action
store.dispatch({ type: "ADD", value: 1 })

// lets check the current state
console.log(store.getState()) // { counter: 2 }
Enter fullscreen mode Exit fullscreen mode

So now you know, how to dispatch an action to the store using store.dispatch() command and how to get the current state object from the store using store.getState(). This is amazing.

Now, just think how many times shall we console.log the getState command to check the current state! Pretty boring, isn't it? That's why we have a store.subscribe() method.

This accepts a callback function which executes everytime the store is modified (or updated). Here's how you can write this.

// writing a subscribe method
store.subscribe(() => {
    console.log("Store:", store.getState())
})
Enter fullscreen mode Exit fullscreen mode

NOTE: Make sure you keep your subscribe method above the dispatch method so that it shows up everytime you dispatch an action.

Writing actions in a better way

// using variables to store strings is better than using
// strings directly on the action object for error handling purposes

const ADD = "ADD"
const SUBSTRACT = "SUBSTRACT"
const MULTIPLY = "MULTIPLY"
const DIVIDE = "DIVIDE"

// writing a function for each of the action to makes it convinient
// to include inside dispatch method

function toAdd(val) {
    return { type: ADD, value: val }
}

function toSubstract(val) {
    return { type: SUBSTRACT, value: val }
}

function toMultiply(val) {
    return { type: MULTIPLY, value: val }
}

function toDivide(val) {
    return { type: DIVIDE, value: val }
}

// calling dispatches

store.dispatch(toAdd(12)) // Store: { counter: 13 }
store.dispatch(toSubstract(7)) // Store: { counter: 6 }
store.dispatch(toDivide(3)) // Store: { counter: 2 }
store.dispatch(toMultiply(6)) // Store: { counter: 12 }
Enter fullscreen mode Exit fullscreen mode

Now running node <filename.js> gives the following output:

Store: { counter: 13 }
Store: { counter: 6 }
Store: { counter: 2 }
Store: { counter: 12 }
Enter fullscreen mode Exit fullscreen mode

What next? 🎉

As this was a pretty much simple calculator app, so we didn't require any complex data types in our store and so didn't quite explore different ways to return states immutably from the reducer. So we'll keep that for the next time.

https://media.giphy.com/media/KctrWMQ7u9D2du0YmD/giphy.gif

I'm quite active on Twitter, lately. Reach me, @heytulsiprasad.

Top comments (0)