React fundamental

This post is a list of basic experiments, just to examine the React cache and DOM re-rendering fundamentals.

Let’s have the most simple React App. The purpose is to feel how changing one value will re-compute / re-rendering the app. Instead of checking the React-Dev-Tools, I added some logs.

const App = ({data}) => {
    console.log('App');

    return <div>
        <TheDis d={data.d1} />
        <TheDis2 d={data.d2} />
    </div>
}

const TheDis = ({ d }) => {
    console.log(`TheDis: ${d.title}`);
    return <p>{d.title}</p>
}

const TheDis2 = ({d}) => {
    console.log(`TheDis2: ${d.title}`);
    return <p>{d.title}</p>
}

let data = {
    d1 : {title:'d1'},
    d2 : {title:'d2'}
};

ReactDOM.render(<App data={ data} />, document.querySelector('#app'));

And direct DOM manipulation to be able to see when React re-renders the DOM:

setTimeout(() => {
    document.querySelectorAll('p').forEach((e,i) => {
        e.innerText = `P number ${i}` ;
    });
    console.log('Dom manipulation')
}, 1000)

Changes outside React:

Changing the data outside the React will not trigger any action:

ReactDOM.render(<App data={ data} />, document.querySelector('#app'));

setTimeout(() => {
        data = {...data, d1: { title: 'Nothing here' }};
}, 2000);

The console is empty, and the DOM didn’t change

Direct data change

Once more, changing the props has no meaning. And the console is empty.

const App = ({data}) => {
    console.log('App');

    setTimeout(() => {
        data = {...data};
    }, 2000);

    return <div>
        <TheDis d={data.d1} />
        <TheDis2 d={data.d2} />
    </div>
}

Changing a state value

Changing a state value will re-render all the React tree, even id the state data is not used. But the DOM will not be effected.

const App = ({data}) => {
    console.log('App');
    const [stateData,setStateData] = React.useState(data);
    
    setTimeout(() => {
        setStateData({...data});
    }, 2000);

    return <div>
        <TheDis d={data.d1} />
        <TheDis2 d={data.d2} />
    </div>
}

Note that the timeout re-renders couses and endless loop. So there is a need of useEffect

const App = ({data}) => {
    console.log('App');
    const [stateData,setStateData] = React.useState(data);
    React.useEffect(()=>{
        setTimeout(() => {
            setStateData({...data});
        }, 2000);
    },[]);
    return <div>
        <TheDis d={stateData.d1} />
        <TheDis2 d={stateData.d2} />
    </div>
}

The DOM will not change, but the log will show the re-computes:

App
TheDis: d1
TheDis2: d2

Dom manipulation

App
TheDis: d1
TheDis2: d2

Adding React Memo

React memo caches the component according to the props, and will prevent the re-compute if the prop didn’t change.

const TheDis = React.memo(({ d }) => {
    console.log(`TheDis: ${d.title}`);
    return <p>{d.title}</p>
})

const TheDis2 = React.memo(({d}) => {
    console.log(`TheDis2: ${d.title}`);
    return <p>{d.title}</p>
})

Now the DOM still will not change, and the unchanged components will not re-render. It can be seen as the console is:

App
TheDis: d1
TheDis2: d2

Dom manipulation

App

Changing the data

Now, if the data will actually change…

const App = ({data}) => {
    console.log('App');
    const [stateData,setStateData] = React.useState(data);
    React.useEffect(()=>{
        setTimeout(() => {
            data.d1 = {title: 'some new title'}
            setStateData({...data});
        }, 2000);
    },[])
    return <div>
        <TheDis d={stateData.d1} />
        <TheDis2 d={stateData.d2} />
    </div>
}

The sub-component will re-render, and the DOM will update:

App
TheDis: d1
TheDis2: d2

Dom manipulation

App
TheDis: some new title

Back to external changes…

Skadoosh, success. But the goal was to change the data outside React…

…with Redux

Do I need to introduce react-redux? Before diving, and for the whoever that will read this. The code can be nicer, here I focused on the shortness.

const initialState = {
        d1 : {title:'d1'},
        d2 : {title:'d2'}
  };
  
 function rootReducer(state = initialState, action) {
    if (action.type === "CHANGE_DATA") {
        return {...state, d1: { ...action.payload }};
      }
    return state;
 };

const store = createStore(rootReducer);

The changes in the React App

const mapStateToProps = (state) => {
    return  {data:{...state}};
};

const AppRedux =  connect(mapStateToProps)(App);

ReactDOM.render(<Provider store={store}><AppRedux  /></Provider>, document.querySelector('#app'))

And back to the Redux external changes

setTimeout(() => {
    store.dispatch({ type: "CHANGE_DATA", payload: {title:"Hi from Redux"} });
}, 2000)

…with mobX

import { makeAutoObservable } from "mobx"
import { observer } from "mobx-react"

class Data{
    d1 = {title:'d1'};
    d2 = {title:'d2'};
    constructor(){
        makeAutoObservable(this);
    }
}

const App = observer(({data}) => {
    console.log('App');

    return <div>
        <TheDis d={data.d1} />
        <TheDis2 d={data.d2} />
    </div>
});

let data = new Data();
ReactDOM.render(<App data={ data} />, document.querySelector('#app'))

setTimeout(() => {
    data.d1 = { title: 'Hi from mobX' }
}, 2000)

Conclusion

With both: mobX & Redux, React will be re-rendered even that the changes are executed outside the React. And the app will manipulate the DOM only for the relevant changed value. The console will display:

App
TheDis: d1
TheDis2: d2

Dom manipulation

App
theDis: TheDis Hi from [mobX|Redux]

There is nothing like a theoretical understanding, a thesis that materializes on an actional app.

About the differences between Redux vs mobX, on some other time.