• Skip to main content
  • Skip to secondary menu
  • Skip to primary sidebar
  • Skip to footer
  • Articles
  • News
  • Events
  • Advertize
  • Jobs
  • Courses
  • Contact
  • (0)
  • LoginRegister
    • Facebook
    • LinkedIn
    • RSS
      Articles
      News
      Events
      Job Posts
    • Twitter
Datafloq

Datafloq

Data and Technology Insights

  • Categories
    • Big Data
    • Blockchain
    • Cloud
    • Internet Of Things
    • Metaverse
    • Robotics
    • Cybersecurity
    • Startups
    • Strategy
    • Technical
  • Big Data
  • Blockchain
  • Cloud
  • Metaverse
  • Internet Of Things
  • Robotics
  • Cybersecurity
  • Startups
  • Strategy
  • Technical

Immutability in JavaScript using Redux

Irine Papuc / 12 min read.
February 3, 2017
Datafloq AI Score
×

Datafloq AI Score: 84

Datafloq enables anyone to contribute articles, but we value high-quality content. This means that we do not accept SEO link building content, spammy articles, clickbait, articles written by bots and especially not misinformation. Therefore, we have developed an AI, built using multiple built open-source and proprietary tools to instantly define whether an article is written by a human or a bot and determine the level of bias, objectivity, whether it is fact-based or not, sentiment and overall quality.

Articles published on Datafloq need to have a minimum AI score of 60% and we provide this graph to give more detailed information on how we rate this article. Please note that this is a work in progress and if you have any suggestions, feel free to contact us.

floq.to/5um5E

In an ever growing ecosystem of rich and complicated JavaScript applications, theres more state to be managed than ever before: the current user, the list of posts loaded, etc.

Any set of data that needs a history of events can be considered stateful. Managing state can be hard and error prone, but working with immutable data (rather than mutable) and certain supporting technologies- namely Redux, for the purposes of this article- can help significantly.

Immutable data has restrictions, namely that it cant be changed once its created, but it also has many benefits, particularly in reference versus value equality, which can greatly speed up applications that rely on frequently comparing data (checking if something needs to update, for example).

Using immutable states allows us to write code that can quickly tell if the state has changed, without needing to do a recursive comparison on the data, which is usually much, much faster.

This article will cover the practical applications of Redux when managing state through action creators, pure functions, composed reducers, impure actions with Redux-saga and Redux Thunk and, finally, use of Redux with React. That said, there are a lot of alternatives to Redux, such as MobX, Relay, and Flux-based libraries.

Redux components.

Why Redux?

The key aspect that separates Redux from most other state containers such as MobX, Relay, and most other Flux-based implementations is that Redux has a single state that can only be modified via actions (plain JavaScript objects), which are dispatched to the Redux store. Most other data stores have the state contained in React components themselves, allow you to have multiple stores and/or use mutable state.

This in turn, causes the stores reducer, a pure function that operates on immutable data, to execute and potentially update the state. This process enforces unidirectional data flow, which is easier to understand and more deterministic.

The Redux Flow.

Since Redux reducers are pure functions operating on immutable data, they always produce the same output given the same input, making them easy to test. Heres an example of a reducer:

import Immutable from 'seamless-immutable'

const initialState = Immutable([]) // create immutable array via seamless-immutable

/**
 * a reducer takes a state (the current state) and an action object (a plain JavaScript object that was dispatched via dispatch(..) and potentially returns a new state.
 */
function addUserReducer(state = initialState, action) {
    if (action.type === 'USERS_ADD') {
      return state.concat(action.payload)
    }
    
    return state // note that a reducer MUST return a value
}

// somewhere else...

store.dispatch({ type: 'USERS_ADD', payload: user }) // dispatch an action that causes the reducer to execute and add the use

r

Dealing in pure functions allows Redux to easily support many use cases that are generally not easily done with mutative state, such as:

  • Time travel (Going back in time to a previous state)
  • Logging (Track every single action to figure out what caused a mutation in the store)
  • Collaborative environments (Such as GoogleDocs, where actions are plain JavaScript objects and can be serialized, sent over the wire, and replayed on another machine)
  • Easy bug reporting (Just send the list of actions dispatched, and replay them to get the exact same state)
  • Optimized rendering (At least in frameworks that render virtual DOM as a function of state, such as React: due to immutability, you can easily tell if something has changed by comparing references, as opposed to recursively comparing the objects)
  • Easily test your reducers, as pure functions can easily be unit tested

Action Creators

Reduxs action creators help in keeping code clean and testable. Remember that actions in Redux are nothing more than plain JavaScript objects describing a mutation that should occur. That being said, writing out the same objects over and over again is repetitive and error prone.

An action creator in Redux is simply a helper function that returns a plain JavaScript object describing a mutation. This helps reduce repetitive code, and keeps all your actions in one place:

    export function usersFetched(users) {
      return {
        type: 'USERS_FETCHED',
        payload: users,
      }
    }
    
    export function usersFetchFailed(err) {
      return {
        type: 'USERS_FETCH_FAILED',
        payload: err,
      }
    }
    
    // reducer somewhere else...
    const initialState = Immutable([]) // create immutable array via seamless-immutable
    
    /**
     * a reducer takes a state (the current state) and an action object (a plain JavaScript object that was dispatched via dispatch(..) and potentially returns a new state.
     */
    function usersFetchedReducer(state = initialState, action) {
        if (action.type === 'USERS_FETCHED') {
          return Immutable(action.payload)
        }
        
        return state // note that a reducer MUST return a value
    }

Using Redux with Immutable Libraries

While the very nature of reducers and actions make them easy to test, without an immutability helper library, theres nothing protecting you from mutating objects, meaning the tests for all your reducers have to be particularly robust.

Consider the following code example of a problem youll run into without a library to protect you:

const initialState = []

function addUserReducer(state = initialState, action) {
    if (action.type === 'USERS_ADD') {
      state.push(action.payload) // NOTE: mutating action!!
      return state
    }
    
    return state // note that a reducer MUST return a value
}

In this code example, time travel will be broken as the previous state will now be the same as the current state, pure components may potentially not update (or re-render) as the reference to the state has not changed even though the data it contains has changed, and mutations are a lot harder to reason through.

Without an immutability library, we lose all the benefits that Redux provides. Its therefore highly recommended to use an immutability helper library, such as immutable.js or seamless-immutable, especially when working in a large team with multiple hands touching code.

Regardless of which library you use, Redux will behave the same. Lets compare the pros and cons of both so that youre able to pick whichever one is best suited for your use case:

Immutable.js

Immutable.js is a library, built by Facebook, with a more functional style take on data structures, such as Maps, Lists, Sets, and Sequences. Its library of immutable persistent data structures perform the least amount of copying possible in between different states.

Pros:

  • Structural sharing
  • More efficient at updates
  • More memory efficient
  • Has a suite of helper methods to manage updates

Cons:

  • Does not work seamlessly with existing JS libraries (i.e lodash, ramda)
  • Requires conversion to and from (toJS / fromJS), especially during hydration / dehydration and rendering

Seamless-immutable

Seamless-immutable is a library for immutable data that is backwards compatible all the way to ES5.

Its based on ES5 property definition functions, such as defineProperty(..) to disable mutations on objects. As such, it is fully compatible with existing libraries like lodash and Ramda. It can also be disabled in production builds, providing a potentially significant performance gain.

Pros:

  • Works seamlessly with existing JS libraries (i.e lodash, ramda)
  • No extra code needed to support conversion
  • Checks can be disabled in production builds, increasing performance

Cons:

  • No structural sharing – objects / arrays are shallow-copied, makes it slower for large data sets
  • Not as memory efficient

Redux and Multiple Reducers

Another useful feature of Redux is the ability to compose reducers together.This allows you to create much more complicated applications, and in an application of any appreciable size, you will inevitably have multiple types of state (current user, the list of posts loaded, etc). Redux supports (and encourages) this use case by naturally providing the function combineReducers:

import { combineReducers } from 'redux'
import currentUserReducer from './currentUserReducer'
import postsListReducer from './postsListReducer'

export default combineReducers({
  currentUser: currentUserReducer,
  postsList: postsListReducer,
})

With the above code, you can have a component that relies on the currentUser and another component that relies on the postsList. This also improves performance as any single component will only be subscribing to whatever branch(es) of the tree concerns them.

Impure Actions in Redux

By default, you can only dispatch plain JavaScript objects to Redux. With middleware, however, Redux can support impure actions such as getting the current time, performing a network request, writing a file to disk, and so on.

Middleware is the term used for functions that can intercept actions being dispatched. Once intercepted, it can do things like transform the action or dispatch an asynchronous action, much like middleware in other frameworks (such as Express.js).


Interested in what the future will bring? Download our 2023 Technology Trends eBook for free.

Consent

Two very common middleware libraries are Redux Thunk and Redux-saga. Redux Thunk is written in an imperative style, while Redux-saga is written in a functional style. Lets compare both.

Redux Thunk

Redux Thunk supports impure actions within Redux by using thunks, functions that return other chain-able functions. To use Redux-Thunk, you must first mount the Redux Thunk middleware to the store:

    import { createStore, applyMiddleware } from 'redux'
    import thunk from 'redux-thunk'
    
    const store = createStore(
      myRootReducer,
      applyMiddleware(thunk), // here, we apply the thunk middleware to R
    )

Now we can perform impure actions (such as performing an API call) by dispatching a thunk to the Redux store:

    store.dispatch(
      dispatch => {
        return api.fetchUsers()
          .then(users  => dispatch(usersFetched(users)) // usersFetched is a function that returns a plain JavaScript object (Action)
          .catch(err => dispatch(usersFetchError(err)) // same with usersFetchErro
r      }
    )

Its important to note that using thunks can make your code hard to test and makes it harder to reason through code flow.

Redux-saga

Redux-saga supports impure actions through an ES6 (ES2015) feature called generators and a library of functional / pure helpers. The great thing about generators is that they can be resumed and paused, and their API contract makes them extremely easy to test.

Lets see how we can improve readability and testability of the previous thunk method using sagas!

First, lets mount the Redux-saga middleware to our store:

    import { createStore, applyMiddleware } from 'redux'
    import createSagaMiddleware from 'redux-saga'
    
    import rootReducer from './rootReducer'
    import rootSaga from './rootSaga'
    
    // create the saga middleware
    const sagaMiddleware = createSagaMiddleware()
    
    // mount the middleware to the store
    const store = createStore(
      rootReducer,
      applyMiddleware(sagaMiddleware),
    )
    
    // run our saga!
    sagaMiddleware.run(rootSaga)

Note that the run(..) function must be called with the saga for it to begin executing.

Now lets create our saga:

    import { call, put, takeEvery } from 'redux-saga/effects' // these are saga effects we'll use
    
    export function *fetchUsers(action) {
      try {
        const users = yield call(api.fetchUsers)
        yield put(usersFetched(users))
      } catch (err) {
        yield put(usersFetchFailed(err))
      }
    }
    
    export default function *rootSaga() {
      yield takeEvery('USERS_FETCH', fetchUsers)
    }

We defined two generator functions, one that fetches the users list and the rootSaga. Notice that we didnt call api.fetchUsers directly but instead yielded it in a call object. This is because Redux-saga intercepts the call object and executes the function contained within to create a pure environment (as far as your generators are concerned).

rootSaga yields a single call to a function called takeEvery, which takes every action dispatched with a type of USERS_FETCH and calls the fetchUsers saga with the action it took. As we can see, this creates a very predictable side effect model for Redux, which makes it easy to test!

Testing Sagas

Lets see how generators make our sagas easy to test. Well be using mocha in this part to run our unit tests and chai for assertions.

Because sagas yield plain JavaScript objects and are run within a generator, we can easily test that they perform the right behavior without any mocks at all! Keep in mind that call , take , put, etc are just plain JavaScript objects that are intercepted by the Redux-saga middleware.

    import { take, call } from 'redux-saga/effects'
    import { expect } from 'chai'
    import { rootSaga, fetchUsers } from '../rootSaga'
    
    describe('saga unit test', () => {
      it('should take every USERS_FETCH action', () => {
        const gen = rootSaga() // create our generator iterable
        expect(gen.next().value).to.be.eql(take('USERS_FETCH')) // assert the yield block does have the expected value
        expect(gen.next().done).to.be.equal(false) // assert that the generator loops infinitely
      })
      
      it('should fetch the users if successful', () => {
        const gen = fetchUsers()
        expect(gen.next().value).to.be.eql(call(api.fetchUsers)) // expect that the call effect was yielded
        
        const users = [ user1, user2 ] // some mock response
        expect(gen.next(users).value).to.be.eql(put(usersFetched(users))
      })
      
      it('should fail if API fails', () => {
        const gen = fetchUsers()
        expect(gen.next().value).to.be.eql(call(api.fetchUsers)) // expect that the call effect was yielded
        
        const err = { message: 'authentication failed' } // some mock erro
r        expect(gen.throw(err).value).to.be.eql(put(usersFetchFailed(err))
      })
    })

Working with React

While Redux isnt tied to any specific companion library, it works especially well with React.js since React components are pure functions that take a state as input and produce a virtual DOM as output.

React-Redux is a helper library for React and Redux that eliminates most of the hard work connecting the two. To most effectively use React-Redux, lets go over the notion of presentational components and container components.

Presentational components describe how things should look visually, depending solely on their props to render; they invoke callbacks from props to dispatch actions. Theyre written by hand, completely pure, and are not tied to state management systems like Redux.

Container components, on the other hand, describe how things should function, are aware of Redux, dispatch Redux actions directly to perform mutations and are generally generated by React-Redux. They are often paired with a presentational component, providing its props.

Presentational components and container components in Redux.

Lets write a presentational component and connect it to Redux via React-Redux:

    const HelloWorld = ({ count, onButtonClicked }) => (
      <div>
        <span>Hello! You've clicked the button {count} times!</span>
        <button onClick={onButtonClicked}>Click me</button>
      </div>
    )
    
    HelloWorld.propTypes = {
      count: PropTypes.number.isRequired,
      onButtonClicked: PropTypes.func.isRequired,
    }

Note that this is a dumb component that completely relies on its props to function. This is great, because it makes the React component easy to test and easy to compose. Lets look at how to connect this component to Redux now, but first lets cover what a Higher Order Component is.

Higher-Order Components

React-Redux provides a helper function called connect( .. ) that creates a higher order component from a dumb React component that is aware of Redux.

React emphasizes extensibility and re-usability through composition, which is when you wrap components in other components. Wrapping these components can change their behavior or add new functionality. Lets see how we can create a higher order component out of our presentational component that is aware of Redux – a container component.

Heres how you do it:

    import { connect } from 'react-redux'
    
    const mapStateToProps = state => { // state is the state of our store
      // return the props that we want to use for our component
      return {
        count: state.count,
      }
    }
    
    const mapDispatchToProps = dispatch => { // dispatch is our store dispatch function
      // return the props that we want to use for our component
      return {
        onButtonClicked: () => {
          dispatch({ type: 'BUTTON_CLICKED' })
        },
      }
    }
    
    // create our enhancer function
    const enhancer = connect(mapStateToProps, mapDispatchToProps)
    
    // wrap our "dumb" component with the enhance
r    const HelloWorldContainer = enhancer(HelloWorld)
    
    // and finally we export it
    export default HelloWorldContaine
r

Note that we defined two functions, mapStateToProps and mapDispatchToProps .

mapStateToProps is a pure function of (state: Object) that returns an object computed from the Redux state. This object will be merged with the props passed to the wrapped component. This is also known as a selector, since it selects parts of the Redux state to be merged into the components props.

mapDispatchToProps is also a pure function, but one of (dispatch: (Action) => void) that returns an object computed from the Redux dispatch function. This object will likewise be merged with the props passed to the wrapped component.

Now to use our container component we must use the Provider component in React-Redux to tell the container component what store to use:

    import { Provider } from 'react-redux'
    import { render } from 'react-dom'
    
    import store from './store' // where ever your Redux store resides
    import HelloWorld from './HelloWorld'
    
    render(
      (
        <Provider store={store}>
          <HelloWorld />
        </Provider>
      ), document.getElementById('container')
    )

The Provider component propagates the store down to any child components who subscribe to the Redux store, keeping everything in one place and reducing points of error or mutation!

Build Code Confidence With Redux

With this newfound knowledge of Redux, its numerous supporting libraries and its framework connection with React.js, you can easily limit the number of mutations in your application through state control. Strong state control, in turn, lets you move faster and create a solid code base with more confidence.

Categories: Technical
Tags: analysis, applications, Code, Javascript, Programming Language

About Irine Papuc

Irina is a scientist by training and an entrepreneur. Previously, she worked on the LHCb experiment at CERN as part of the Rise DAAD program. With a degree in Physics from the Illinois Institute of Technology, Irina is a member of Toptal, the top marketplace for finding freelance talent.

Primary Sidebar

E-mail Newsletter

Sign up to receive email updates daily and to hear what's going on with us!

Publish
AN Article
Submit
a press release
List
AN Event
Create
A Job Post
Host your website with Managed WordPress for $1.00/mo with GoDaddy!

Related Articles

The Advantages of IT Staff Augmentation Over Traditional Hiring

May 4, 2023 By Mukesh Ram

The State of Digital Asset Management in 2023

May 3, 2023 By pimcoremkt

Test Data Management – Implementation Challenges and Tools Available

May 1, 2023 By yash.mehta262

Related Jobs

  • Software Engineer | South Yorkshire, GB - February 07, 2023
  • Software Engineer with C# .net Investment House | London, GB - February 07, 2023
  • Senior Java Developer | London, GB - February 07, 2023
  • Software Engineer – Growing Digital Media Company | London, GB - February 07, 2023
  • LBG Returners – Senior Data Analyst | Chester Moor, GB - February 07, 2023
More Jobs

Tags

AI Amazon analysis analytics app Apple application Artificial Intelligence BI Big Data business CEO China Cloud Companies company content costs court crypto customers Data digital future Google+ government industry information machine learning market mobile Musk news Other public research revenue sales security share social social media strategy technology twitter

Related Events

  • 6th Middle East Banking AI & Analytics Summit 2023 | Riyadh, Saudi Arabia - May 10, 2023
  • Data Science Salon NYC: AI & Machine Learning in Finance & Technology | The Theater Center - December 7, 2022
  • Big Data LDN 2023 | Olympia London - September 20, 2023
More events

Related Online Courses

  • Oracle Cloud Data Management Foundations Workshop
  • Data Science at Scale
  • Statistics with Python
More courses

Footer


Datafloq is the one-stop source for big data, blockchain and artificial intelligence. We offer information, insights and opportunities to drive innovation with emerging technologies.

  • Facebook
  • LinkedIn
  • RSS
  • Twitter

Recent

  • 5 Reasons Why Modern Data Integration Gives You a Competitive Advantage
  • 5 Most Common Database Structures for Small Businesses
  • 6 Ways to Reduce IT Costs Through Observability
  • How is Big Data Analytics Used in Business? These 5 Use Cases Share Valuable Insights
  • How Realistic Are Self-Driving Cars?

Search

Tags

AI Amazon analysis analytics app Apple application Artificial Intelligence BI Big Data business CEO China Cloud Companies company content costs court crypto customers Data digital future Google+ government industry information machine learning market mobile Musk news Other public research revenue sales security share social social media strategy technology twitter

Copyright © 2023 Datafloq
HTML Sitemap| Privacy| Terms| Cookies

  • Facebook
  • Twitter
  • LinkedIn
  • WhatsApp

In order to optimize the website and to continuously improve Datafloq, we use cookies. For more information click here.

Dear visitor,
Thank you for visiting Datafloq. If you find our content interesting, please subscribe to our weekly newsletter:

Did you know that you can publish job posts for free on Datafloq? You can start immediately and find the best candidates for free! Click here to get started.

Not Now Subscribe

Thanks for visiting Datafloq
If you enjoyed our content on emerging technologies, why not subscribe to our weekly newsletter to receive the latest news straight into your mailbox?

Subscribe

No thanks

Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.

Necessary Cookies

Strictly Necessary Cookie should be enabled at all times so that we can save your preferences for cookie settings.

If you disable this cookie, we will not be able to save your preferences. This means that every time you visit this website you will need to enable or disable cookies again.

Marketing cookies

This website uses Google Analytics to collect anonymous information such as the number of visitors to the site, and the most popular pages.

Keeping this cookie enabled helps us to improve our website.

Please enable Strictly Necessary Cookies first so that we can save your preferences!