First of all React is a JS Library not a Complete Framework ๐ so for making a complete web application you need to know a lot of other things ๐คญ that you can use with React. In this Post I will cover React concepts, Hooks and some good practices Ofc... ๐
We use React to make reusable components that can be used in a logical way to make UI. Making components in React is as easy as making a function ๐คฉ.
For Example ๐๐ป it is a simple react component in which we can pass data as arguments which can be easily referenced inside the functions
function Component(props){
return <h1>{props.text}</h1>
}
Ohk But Now what are States in React??
The state object is where you store property values that belongs to the component. When the state object changes, the component re-renders which basically allows us to manage changing data in an application ๐.
Now Lets Learn about states using useState()
useState()
const component = () => {
// Tip: use states only inside components
// lets console.log the state and lets see what it returns
console.log(useState(100));
// this will return an array [100,f]
// basically this returns a state and a function to update the state
// we can destructure the array and get the state and the function
const [state, setState] = useState(100);
return (
<div>
hiiieeee
</div>
)
}
but you can't โ ๏ธ directly update the value of state by using = operator as this will change the value but it will not re render the component so basically react wants ๐ฉ that you pass the value in the setState function if you need to change the state.
passing functions inside the useState() ๐ค
// you can also pass function in useState it will set the initial value to what function returns it is useful when you use computationaly high task as initial state
const [state, setState] = useState(() => {
console.log("initial state");
return 100;
});
passing functions inside the setState() ๐
onClick={() => {
// here value in the props is the state
setState((value) => {
//you can use this method when you need to update the state and you need the previous value of state
return value + 1;
});
}}
useEffect()
Use effect hook has 2 parts first one is a function and second one is dependency array which is optional
useEffect(()=>{},[])
// we will get a console log every time any state changes.
// for example if you have 2 states in your component and any of
// them changes then we will get the console log
// this is something we mostly dont want.
useEffect(() => {
console.log('change');
})
Your first useEffect() call will always run when your component gets mounted first time in the DOM.
Dependency array ๐ค
We can specify the states inside the dependency array of useEffect() so that it will only monitor the changes of those states which are mentioned in the dependency array๐ฎโ๐จ.
const [state1, setState1] = useState(0);
const [state2, setState2] = useState(0);
useEffect(() => {
console.log('state1 changed');
}, [state1])
Remember: Do not update the state in which you used the useEffect() without a proper logic it will create an infinite loop ๐ฅต
Cleanup function
useEffect always returns a cleanup function which you can use to remove unwanted behaviours thec leanup function does not only run when our component wants to unmount, it also runs right before the execution of the next scheduled effect read in detail
useEffect(() => {
console.log(`state1 changed | ${state1}`);
return () => {
console.log('state1 unmounted | ', state1);
}
}, [state1])
you can fetch data from an api like this ๐๐ป
useEffect(() => {
const url = "https://jsonplaceholder.typicode.com/todos/1";
const fetchData = () => {
fetch(url)
.then(res => res.json())
.then(data => {
setState(data.title)
})
}
fetchData();
}, []);
useContext()
The Context API provides data even to the deepest level of component in the react component tree without passing it in props
import { createContext } from "react";
import { useState } from "react";
const StoreContext = createContext();
const component = () => {
const data = useState({
name: 'Ritesh',
email: 'nyctonio.dev@gmail.com',
})[0];
const Child = () => {
return <div>
<StoreContext.Consumer>
{value => <h1>name is {value.name}</h1>}
</StoreContext.Consumer>
</div>
}
return (
<StoreContext.Provider value={data}>
<Child />
</StoreContext.Provider>
)
}
export default component;
You can wrap your top level component in Your Context Provider and use it inside a function by Context Consumer.What useContext do is it replaces Context Consumer and we can get data by using useContext directly.
See this example ๐๐ป.
import { createContext, useContext } from "react";
import { useState } from "react";
const StoreContext = createContext();
const component = () => {
const data = useState({
name: 'Ritesh',
email: 'nyctonio.dev@gmail.com',
})[0];
const Child = () => {
const value = useContext(StoreContext);
return <div>
<h1>name is {value.name}</h1>
</div>
}
return (
<StoreContext.Provider value={data}>
<Child />
</StoreContext.Provider>
)
}
export default component;
useReducer()
useReducer is used for state management in React it is somewhat similar to reducer function in javascript.
// useReducer function accepts 2 params reducer function and initialState
useReducer(reducer,initialState)
// reducer functions accepts 2 params currentState and action for that and returns a new State
reducer(currentState,action)
lets create a simple counter using useReducer
import { useReducer } from 'react'
const initialState = 0;
const reducer = (state, action) => {
switch (action) {
case 'increment':
return state + 1;
case 'decrement':
return state - 1;
default:
return state;
}
}
export default function main() {
const [count, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch('increment')}>+</button>
<button onClick={() => dispatch('decrement')}>-</button>
</div>
)
}
we can make it more complex by making our state an object
import { useReducer } from 'react'
const initialState = {
firstCounter: 0,
secondCounter: 0
};
const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return { ...state, firstCounter: state.firstCounter + action.value };
case 'decrement':
return { ...state, firstCounter: state.firstCounter - action.value };
default:
return { ...state };
}
}
export default function main() {
const [count, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {count.firstCounter}</p>
<button className='bg-gray-200 p-2' onClick={() => dispatch({ type: 'increment', value: 2 })}>
increase by 2
</button>
<button className='bg-gray-200 p-2' onClick={() => dispatch({ type: 'decrement', value: 4 })}>
decrease by 4
</button>
</div>
)
}
Or We can use multiple useReducers ๐๐ป
import { useReducer } from 'react'
const initialState = 0;
const reducer = (state, action) => {
switch (action) {
case 'increment':
return state + 1;
case 'decrement':
return state - 1;
default:
return state;
}
}
export default function main() {
const [count, dispatch] = useReducer(reducer, initialState);
const [count2, dispatch2] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {count}</p>
<button className="bg-gray-100 p-2 m-2"
onClick={() => dispatch('decrement')}>-</button>
<button className="bg-gray-100 p-2 m-2"
onClick={() => dispatch('increment')}>+</button>
<p>Count2: {count2}</p>
<button className="bg-gray-100 p-2 m-2"
onClick={() => dispatch2('increment')}>+</button>
<button className="bg-gray-100 p-2 m-2"
onClick={() => dispatch2('decrement')}>-</button>
</div>
)
}
When to use useState and when useReducer ????
useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks ๐.
useReducer() with useContext()
With the use of useContext and useReducer we can manage global states at any level of component tree try out this example ๐๐ป
// main.jsx
import React from 'react'
import { useReducer } from 'react'
import ChildrenA from '../components/ChildrenA';
export const StateContext = React.createContext();
const initialState = 0;
const reducer = (state, action) => {
switch (action) {
case 'increment':
return state + 1;
case 'decrement':
return state - 1;
default:
return state;
}
}
export default function main() {
const [count, dispatch] = useReducer(reducer, initialState);
return (
<div>
<StateContext.Provider
value={{ countState: count, countDispatch: dispatch }}>
<ChildrenA />
</StateContext.Provider>
</div >
)
}
// ChildrenA.jsx
import React from 'react'
import ChildrenB from './ChildrenB'
import { StateContext } from '../pages/main'
import { useContext } from 'react'
export default function ChildrenA() {
const { countState, countDispatch } = useContext(StateContext)
return (
<div>
In child A count state is {countState}
<ChildrenB />
</div>
)
}
// ChildrenB.jsx
import React from 'react'
import { StateContext } from '../pages/main'
import { useContext } from 'react'
export default function ChildrenB() {
const { countState, countDispatch } = useContext(StateContext)
return (
<div>
<p>Count is {countState}</p>
<button onClick={() => countDispatch('increment')}>+</button>
<button onClick={() => countDispatch('decrement')}>-</button>
</div>
)
}
your both state will change simultaneously
useCallback()
lets see this code and try to understand the behaviour of functions in React
import React from 'react'
export default function main() {
function Sum() {
return (a, b) => a + b;
}
const func1 = Sum();
const func2 = Sum();
console.log(func1 === func2);
return (
<div>main</div>
)
}
If you will run this code you will get false in console.log
Now with an example lets try to understand how we can use useCallback
// main.jsx
import React, { useState } from 'react'
import ChildrenA from '../components/ChildrenA';
import ChildrenB from '../components/ChildrenB';
import ChildrenC from '../components/ChildrenC';
const main = () => {
const [state1, setState1] = useState(0);
const [state2, setState2] = useState(0);
const handleClickA = () => {
setState1(state1 + 1);
}
const handleClickB = () => {
setState2(state2 + 1);
}
return (
<div className='flex flex-col justify-center items-center'>
<ChildrenA value={state1} handleClick={handleClickA} />
<ChildrenB value={state2} handleClick={handleClickB} />
<ChildrenC />
</div>
)
}
// what react memo do is it re-render the component only when the props change
export default React.memo(main);
// ChildrenA.jsx
import React from 'react'
function ChildrenA({ value, handleClick }) {
console.log('ChildrenA');
return (
<div>ChildrenA {value}
<button className='bg-gray-200 p-2 m-2' onClick={handleClick} >Click</button>
</div>
)
}
export default React.memo(ChildrenA);
// ChildrenB.jsx
import React from 'react'
function ChildrenB({ value, handleClick }) {
console.log('ChildrenB');
return (
<div>ChildrenB {value}
<button className='bg-gray-200 p-2 m-2' onClick={handleClick} >Click</button>
</div>
)
}
export default React.memo(ChildrenB);
// ChildrenC.jsx
import React from 'react'
function ChildrenC() {
console.log('ChildrenC');
return (
<div>ChildrenC</div>
)
}
export default React.memo(ChildrenC);
when you see console.log in your browser initially all three components renders but on clicking any click button only 2 components rerenders
Note: here we used React.memo() thats why ChildrenC do not got rerendered because the props do not changed but why on changing ChildrenA ChildrenB also gets re render
The reason is on rerender of main function the handleClick function do not remains the same as previous one I explained this above in the blog thats why React notices change in props so it rerenders both ChildrenA and ChildrenB.
To Solve this we will use useCallback
useCallback returns a memoized callback.
useCallback accepts a function and a dependency array same as useEffect
now lets change our code in main function and see the logs
// main.jsx
import React, { useState, useCallback } from 'react'
import ChildrenA from '../components/ChildrenA';
import ChildrenB from '../components/ChildrenB';
import ChildrenC from '../components/ChildrenC';
const main = () => {
const [state1, setState1] = useState(0);
const [state2, setState2] = useState(0);
const handleClickA = useCallback(() => {
setState1(state1 + 1);
}, [state1])
const handleClickB = useCallback(() => {
setState2(state2 + 1);
}, [state2])
return (
<div className='flex flex-col justify-center items-center'>
<ChildrenA value={state1} handleClick={handleClickA} />
<ChildrenB value={state2} handleClick={handleClickB} />
<ChildrenC />
</div>
)
}
// what react memo do is it re-render the component only when the props change
export default React.memo(main);
now you can see everything is fine ๐๐ป.
useMemo()
useCallback returns a memorized function similarly useMemo returns a memorized value for example we need to find the factorial and only recalculate when number changes not everytime when component re-renders so we will use useCallback
import React, { useState, useMemo } from 'react'
function factorialOf(n) {
console.log('factorialOf(n) called!');
return n <= 0 ? 1 : n * factorialOf(n - 1);
}
const main = () => {
const [number, setNumber] = useState(2)
const factorial = useMemo(() => factorialOf(number), [number])
const [count, setCount] = useState(0)
return (
<div className='flex flex-col justify-center items-center'>
{factorial}
<button className='bg-gray-200 p-2 m-2' onClick={() => setNumber(number + 1)}>+</button>
{count} <button className='bg-gray-200 p-2 m-2' onClick={() => setCount(count + 1)}>+</button>
</div>
)
}
export default main;
useRef()
lets console log useRef and see what it returns
console.log(useRef(100));
// this will return something like this ๐๐ป {current: 100}
useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.
when you compare an normal object with itself in useEffect after a rerender those are not the same, and that will trigger the useEffect on that object you can run this code ๐๐ป and try out.
import { useEffect, useState, useRef } from "react";
const component = () => {
const obj1 = { hi: 100 };
const obj2 = useRef({ hi: 100 });
console.log(obj1 === obj2.current);
const [state, setState] = useState(() => {
return 1;
});
useEffect(() => {
console.log('obj1 changed | ', obj1);
}, [obj1])
useEffect(() => {
console.log('obj2 changed | ', obj2.current);
}, [obj2])
return (
<div onClick={() => {
setState((value) => {
return value + 1;
});
}} className="w-screen h-screen flex justify-center items-center text-4xl font-extralight">
{state}
</div>
)
}
export default component;
you can also use useState to work similar to useRef
const obj = useState({current:10})[0];
๐ฅณ๐ฅณ๐ฅณ๐ฅณ๐๐๐๐ hurrayyyyy!!!!
you have covered all important hooks.
Connect me on Twitter :- Twitter ๐ค๐ป
Do check out my Github for amazing projects:- Github ๐ค๐ป
Connect me on LinkedIn :- Linkedin ๐ค๐ป
Read my another article :- Authentication in nodejs with mongodb bcrypt and jwt web tokens
Top comments (12)
My God this really shows how horrible React has become. It's a mess compared to modern frameworks. Great article though, thanks.
Could you link us to some of the mentioned "modern frameworks", please?
One example could be frameworks that are actually written in Typescript.
Frameworks that utilize observables and not promise-based logic.
Frameworks that don't require a "root component" in components, they can actually handle templates without a root component that doesn't do anything.
Frameworks that embrace standard HTML, not forcing absurd syntax like "className" just to set at class.
Just the clutter of having to use horrible syntax for useEffect() to have something running at the start of a component, is absolutely nuts.
These are just a few examples.
Sounds great! Would you mind posting a URL to one or two of these?
Lol actually there are a lot of advantages of React which make it 100% worth the disadvantages
Agree Ritesh. Lars is just flaming.. there are definitely use cases for running code at the start of a component. I was honestly hoping he would post a URL to these phantom "frameworks" he refers to.. ๐คทโโ๏ธ
Flaming? that was not my intention. Of course there's use for running code at the start of a component.
Great post ๐
Thank you karranx
Great tutorial
Great Article
Some comments have been hidden by the post's author - find out more