DEV Community

olaseinde AYOWALE
olaseinde AYOWALE

Posted on

Data Flow In React

The understanding of how data flows in react is crucial to building a bug free app. As a self-taught developer, I many of the times wrote apps that broke or behave unexpectedly. Going through the code to find nothing wrong but the code is still breaking; until I had a thorough understanding of how data flows in react, I was able to write code that is bug free and should there be a bug, I'll hunt it down quickly. In this guide, I will be walking you through some common pitfalls in react and how I was able to navigate through them.

1)Unidirectional Data flow

React sends data from top to bottom. Data flows from the state to the rendered component and the nested component should there be one. To understand this, consider this code

import React, { useState, useEffect } from 'react';

const ScreenOne = () => {

    const [number, setNumber] = useState({current: 0});

    const { current } = number;


    const _onClick = () => {
         setNumber({current: number.current + 1})
         //THE CURRENT NUMBER LOGGED TO THE CONSOLE WILL 
         //BE A SNAPSHOT OF THE PREVIOUS STATE, NOT THE NEW 
         //VALUE UPDATED BY THE BUTTON

          console.log(current)
    }
    return (
        <div>
            The Current State is <span>{current}</span>
            <button onClick={_onClick}>INCREMENT</button>
        </div>
    )
}

export default ScreenOne
Enter fullscreen mode Exit fullscreen mode

To make sure the current number rendered on the screen is the same as that logged on the console, we can achieve this either by using a useEffect hook or by getting the snapShot of the previous state and adding directly to it as shown in this code:

//USING USEEFFECT HOOK
import React, { useState, useEffect } from 'react'

...
useEffect(() => {
        (() => {
            console.log(current)
        })()
    }, [current])

...

//ADDING DIRECTLY TO THE SNAPSHOT OF THE PREVIOUS STATE
 const _onClick = () => {

            setNumber(prevState => ({current: prevState.current + 1}))
    }
....

Enter fullscreen mode Exit fullscreen mode

2) Universal state update

There might be a better name to describe this but what is means is that, when a single source of truth like redux is integrated into a react app, when a dispatch function is fired, the state of the entire app get updated even if there is no change in the previous state compared to the current state. This behaviour can really affect performance and in some instances, break the app. To get a better understanding of this concept of universal state update, consider this code:

For this code snippet, redux, react-redux, redux-logger were installed.

ScreenTwo.component.jsx

import React from 'react';
import { connect } from 'react-redux'
import { setNumber } from '../redux/screen.action';
import ScreenThree from './screen-three.component';


const ScreenTwo = ({currentNumber, number}) => {

    const _onClick = () => {
        number(currentNumber + 1)
    }

    return (
        <div>
            This is the data from screen one { currentNumber }
            <button onClick={_onClick}>INCREMENT FROM SCREEN TWO</button>
            <ScreenThree />
        </div>
    )
}

const mapDispatchToProps = dispatch => ({
    number: data => dispatch(setNumber(data))
})


const mapStateToProps = ( { number : { initial_number } } ) => ({
    currentNumber: initial_number
})

export default connect(mapStateToProps, mapDispatchToProps)(ScreenTwo)
Enter fullscreen mode Exit fullscreen mode

ScreenThree.component.jsx

import React from 'react';
import { connect } from 'react-redux'
import { setValue } from '../redux/screen-three.action';

const ScreenThree = ({val, setVal}) => {

    const _onClick = () => {
        setVal(val + 1)
    }

    return (
        <div>
            <div>The is data from Screen Three {val} </div>
            <button onClick={_onClick}>INCREMENT SCREEN THREE</button>
        </div> 
    )
}

const mapStateToProps = ({value: { initial_value}}) => {
    //THIS LOG TO THE CONSOLE IS FIRED ANYTIME THERE IS A STATE 
    // UPDATE
    console.log('State is Being Updated from Screen Three')
    return { val: initial_value }
}

const mapDispatchToProps = dispatch => ({
    setVal: value => dispatch(setValue(value))
})

export default connect(mapStateToProps, mapDispatchToProps)(ScreenThree)
Enter fullscreen mode Exit fullscreen mode

Given we have installed a logging library in our app, we can view from our console State is Being Updated from Screen Three regardless of where state got updated.
This behaviour of the state always been updated can really slow down the app. To mitigate this, the state should be memoized. With memoization, only the component where state changed will get re-rendered. To memoize out react state, we use a library called reselect.
This code snippet shows a simple reselect setup:

screen-three-selector.js

import { createSelector } from 'reselect'


//THE SELECTOR TAKES THE ENTIRE STATE AND RETURNS JUST 
// FRACTION OF IT
const selectValue = state => state.value


//BASED ON THE STATED RETURNED FROM THE FUNCTION ABOVE
//WE CAN NOW RETURN A SPECIFIC STATE VALUE
export const selectInitialValue = createSelector(
    [selectValue],
    (value) => value.initial_value
)
Enter fullscreen mode Exit fullscreen mode

screen-two-selector.js

import { createSelector } from 'reselect';


const selectNumber = state => state.number;

export const selectInitialNumber = createSelector(
    [selectNumber],
    (number) => number.initial_number
)
Enter fullscreen mode Exit fullscreen mode

In our ScreenThree.component.jsx, we add these new lines of imports:

import { selectInitialValue } from '../redux/screen-three-selectors';
import { createStructuredSelector } from 'reselect';
Enter fullscreen mode Exit fullscreen mode

In our mapStateToProps function, we change it such that our selector has access to the state object.

const mapStateToProps = createStructuredSelector ({
    val: selectInitialValue
})
Enter fullscreen mode Exit fullscreen mode

Also in our ScreenTwo.component.jsx, we follow the same procedure, import createStructuredSelectors and selectInitialValues, the selector for ScreenTwo.

With this setup in place, we can be sure that a state update in one of the state values will not cause a re-rendering of the entire app state.
For more detailed documentation on the usage of the reselect library, please visit the official documentation reselect.

These are some of the few ways data flow in react can lead to a bad app performance.

Discussion (0)