Using React.memo and useCallback to prevent functions from causing re-renders - reactjs

I am following this tutorial demonstrating react's 'useCallback' hook along with React.memo to prevent a function being render unnecessarily. To prove the concept we use useRef to console the number of renders. This worked with the function alone but i added a function to randomize the button background color and I can't seem to no prevent the rendering of both functions.
import React,{useState, useCallback, useRef} from 'react';
import './App.css';
const randomColor = () => `rgb(${Math.random()*255},${Math.random()*255},${Math.random()*255}`
const Button = React.memo(({increment, bgColor}) => {
const count = useRef(0)
console.log(count.current++)
return(
<button onClick={increment} style={{backgroundColor: bgColor}}>increment</button>
)
})
const App = React.memo(() => {
const [count, setCount] = useState(0)
const [color, setColor] = useState(`rgb(${Math.random()*255},${Math.random()*255},${Math.random()*255}`)
const increment = useCallback(() => {
setCount(previousCount => previousCount + 1)
setColor(randomColor)
},[setCount,setColor])
return (
<div className="App">
<header className="App-header">
<h2>{count}</h2>
<Button increment={increment} bgColor={color}>increment</Button>
</header>
</div>
);
})
export default App;
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
import React,{useState, useCallback, useRef} from 'react';
import './App.css';
const randomColor = () => `rgb(${Math.random()*255},${Math.random()*255},${Math.random()*255}`
const Button = React.memo(({increment, bgColor}) => {
const count = useRef(0)
console.log(count.current++)
return(
<button onClick={increment} style={{backgroundColor: bgColor}}>increment</button>
)
})
const App = React.memo(() => {
const [count, setCount] = useState(0)
const [color, setColor] = useState(`rgb(${Math.random()*255},${Math.random()*255},${Math.random()*255}`)
const increment = useCallback(() => {
setCount(previousCount => previousCount + 1)
setColor(randomColor)
},[setCount,setColor])
return (
<div className="App">
<header className="App-header">
<h2>{count}</h2>
<Button increment={increment} bgColor={color}>increment</Button>
</header>
</div>
);
})
export default App;

It the example in the video you mentioned, the Button component does not change, because the props stay the same all the time. In your example, the increment stays the same, but the problem is that the bgColor changes with each click.
That means, that if you rendered only the main component and not the Button component, the background would have to be the same, but because it receives different background color each time, it would not make sense.
React will always re-render the component if the props change (if you don't implement custom shouldUpdate lifecycle method).

Related

React Component not updating text when I change it

When I click the increment button I expect the value displayed to go up but it stays the same
`
import * as React from 'react';
const MyComponent = () => {
var count = 0;
return (
<div>
<h1>Hello World</h1>
<button onClick={() => count++}>{count}</button>
</div>
);
};
`
You're not going to force a re render so your updated variable won't show.
import React, {useState} from 'react';
const MyComponent = () => {
const [count, setCount] = useState(0);
return (
<div>
<h1>Hello World</h1>
<button onClick={() => setCount(count + 1)}>{count}</button>
</div>
);
};
In React you have to use state for rendering or updating.
This is example of increasing and decreasing counter with useState hook.
import React, { useState } from 'react';
export function App(props) {
const [count, setCount] = useState(0);
const handleIncrease = () => {
setCount(count + 1);
};
const handleDecrease = () => {
setCount(count === 0 ? 0 : count - 1);
};
return (
<div className='App'>
<div>
<h1>Hello World</h1>
<button onClick={handleIncrease}>Increase</button>
<button onClick={handleDecrease}>Decrease</button>
<h3>Count is: {count}</h3>
</div>
</div>
);
}
You need to use useState hook so the component knows when to rerender and display new data.
I also recommend using useCallback hook to create memoized functions so you prevent unnecessary rerenders of your component (I know in this example it's an overkill but it's still good to know that).
You shouldn't, if possible, return arrow functions on your handlers like onClick - this will also cause the your component to not create new instances of your functions on each render, hence better performance (again not really relevant in this super simple case but a good thing to know nevertheless).
Here are some docs that you can read, these are a really good place to get started with React.
Here's also the code:
const MyComponent = () => {
const [count, setCount] = useState(0);
const handleCountIncrease = useCallback(() => {
setCount((c) => c + 1);
}, [setCount]);
return (
<div>
<h1>Hello World</h1>
<button onClick={handleCountIncrease}>{count}</button>
</div>
)
}
I see here you are using Functional component, so have you tried using React hooks? useState() for example:
import * as React from 'react';
const MyComponent = () => {
const [count, setCount] = useState(0);
return (
<div>
<h1>Hello World</h1>
<button onClick={() => setCount(count + 1)}>{count}</button>
</div> ); };
Try this maybe?

React component is not rendering after state change with button click while using useContext hook

I am using useContext hook for the first time as I wanted the re-rendering of one component by click of a button component. Here's my code:
QuestionContext.js (for creating context):
import { createContext } from "react";
const QuestionContext = createContext()
export default QuestionContext
SectionState.js (for providing value to children):
import {React, useState} from 'react'
import QuestionContext from './QuestionContext'
import questions from '../data/questions.json'
const SectionState = (props) => {
// set questions from json to an array of 4 elements
const newQuestions = Object.keys(questions.content).map(key => questions.content[key].question)
const newState = {
"qID": 0,
"questionTxt": newQuestions[0],
}
//useState for Question state
const [currentQuestion, setCurrentQuestion] = useState(0)
const [questionCtx, setQuestionCtx] = useState(newState)
const updateQuestion = () => {
if(currentQuestion > newQuestions.length) {
console.log("no more questions")
}
else{
setCurrentQuestion(currentQuestion + 1)
setQuestionCtx(() => ({
"qID": currentQuestion,
"questionTxt": newQuestions[currentQuestion]
}))
}
}
return (
<QuestionContext.Provider value = {{newState, updateQuestion}}>
{props.children}
</QuestionContext.Provider>
)
}
export default SectionState
The following two components are child of <SectionState /> component
Buttons.js:
import React, { useContext } from 'react'
import QuestionContext from '../context/QuestionContext'
const Buttons = () => {
const example = useContext(QuestionContext)
const clickHandler = () => {
example.updateQuestion()
}
return (
<div className='flex flex-row justify-between'>
{/* <button className='btn backdrop-blur-md bg-slate-600 rounded-full xl:w-48 md:w-44 text-slate-50' onClick={ clickHandler }>Prev</button> */}
<button className='btn btn-accent rounded-full xl:w-48 md:w-44' onClick={ clickHandler }>Next</button>
</div>
)
}
export default Buttons
Questions.js
import { React, useContext } from 'react'
import './styles/Questions.css'
import QuestionContext from '../context/QuestionContext'
const Questions = () => {
const newContext = useContext(QuestionContext)
return (
<>
<h1 className='text-4xl text-zinc-50'>{ newContext.newState.questionTxt }</h1>
</>
)
}
export default Questions
Every time I have clicked on the button, I could check in the console that newState state has changed, but this new state won't render in <Questions /> component. I could still see newContext.newState.questionTxt holding the initial value i.e. newQuestions[0]. What am I doing wrong here?
Here's a reproduced link in code sandbox
<QuestionContext.Provider value = {{newState, updateQuestion}}
Here you passed newState and updateQuestion as a value of context. In Button component you update currentQuestion and questionCtx using updateQuestion() but in Questions component, you are using the value of newState as
const newContext = useContext(QuestionContext)
<h1 className='text-4xl text-zinc-50'>{ newContext.newState.questionTxt }</h1>
Here newState is not a state. It is just a variable and it is not updated at all so you don't get an updated value in Question component.
Solution:
So I think you should pass the questionCtx as a value of context Provider like
<QuestionContext.Provider value = {{questionCtx , updateQuestion}}
Use it like
<h1 className='text-4xl text-zinc-50'>{ newContext.questionCtx.questionTxt }</h1>
Working Codesandbox link: https://codesandbox.io/s/react-usecontext-forked-frgtw1?file=/src/context/SectionState.js

Why using state setter callback as ref callback does not cause a re-render?

import "./styles.css";
import React from "react";
export default function App() {
const [element, setElement] = React.useState(null);
const [count, setCounter] = React.useState(0);
console.log(element);
const handleClick = (e) => {
setCounter(count + 1);
};
return (
<div className="App">
<h1
ref={setElement}
>
Hello CodeSandbox
</h1>
</div>
);
}
ref={setElement}
should cause an infinite loop, however, it prints element once. Why is that happening ?
sandbox
This is because ref initialization only happens on the first render only!

Why react rerender another variable?

Code like this:
import React, {useState, useEffect} from 'react'
function App() {
const [menuitems, setMenuitems] = useState(null)
useEffect(() => {
console.log("Init")
setMenuitems(["menu1","menu2","menu3"])
},[])
const MenuItems = () => {
const renderMenuItems = () => {
if (menuitems && menuitems.length){
console.log("Render")
return menuitems.map((name) => {
return (
<button key={name}>{name}</button>
)
})
}
}
return (
renderMenuItems()
)
}
const [searchTi, setSearchTic] = useState('')
return (
<div className="App">
{menuitems && <MenuItems/>}
<p>Value: {searchTi}</p>
<input value={searchTi} onChange={(e) => setSearchTic(e.target.value)}/>
</div>
);
}
export default App;
When the input tag is used, the variable MenuItems is reloaded. What's wrong in my code? Why is it rerendering and how to prevent this from happening?
As far as I understand, this happens after setting the variable "searchTi" through the function "setSearchTic". This updates the variable "menuitems " and reloads this section of code:
{menuitems && <MenuItems/>}
you are using MenuItems like it was a component, but it's only a render function. should just call it like this:
import React, {useState, useEffect} from 'react'
function App() {
const [menuitems, setMenuitems] = useState(null)
useEffect(() => {
console.log("Init")
setMenuitems(["menu1","menu2","menu3"])
},[])
const renderMenuItems = () => {
if (menuitems && menuitems.length){
console.log("Render")
return menuitems.map((name) => {
return (
<button key={name}>{name}</button>
)
})
}
return null;
}
const [searchTi, setSearchTic] = useState('')
return (
<div className="App">
{renderMenuItems()}
<p>Value: {searchTi}</p>
<input value={searchTi} onChange={(e) => setSearchTic(e.target.value)}/>
</div>
);
}
export default App;
Compact example:
Also, there's no need to check to the menuitems.length. Best way to render the menu items would be something like this:
const renderMenuItems = () => menuitems?.map((name) => <button key={name}>{name}</button>);
useMemo:
If you want to avoid re-render the menu items over and over, you should also use React.useMemo like this:
const renderMenuItems = useMemo(() => menuitems?.map((name) => <button key={name}>{name}</button>), [menuitems]);
Note that it's now an object (similar to your JSX), and you should not call it, just put it as part of your JSX like this:
return (
<div className="App">
{renderMenuItems}
<p>Value: {searchTi}</p>
<input value={searchTi} onChange={(e) => setSearchTic(e.target.value)}/>
</div>
);
I came across your question and it seemed interesting so I researched about it and finally, I found out that NEVER CREATE A COMPONENT INSIDE ANOTHER FUNCTION COMPONENT.
And I found an article written by Kuldeep Bora.
you can go through the article to understand this completely.
https://dev.to/borasvm/react-create-component-inside-a-component-456b
React components automatically re-render whenever there is a change in their state or props.
Function renderMenuItems will re-create on every re-render and it is not an issue.
But if you don't want this behavior you can use the useCallback hook, and then the function will re-create only when one of the dependencies will change.
useCallback hook docs: https://reactjs.org/docs/hooks-reference.html#usecallback
import React, {useState, useEffect} from 'react'
function App() {
const [menuitems, setMenuitems] = useState(null)
useEffect(() => {
console.log("Init")
setMenuitems(["menu1","menu2","menu3"])
},[])
// this function will re-create for every re-render
const renderMenuItems = () => {
if (menuitems && menuitems.length){
return menuitems.map((name) => {
return (
<button key={name}>{name}</button>
)
})
}
}
const [searchTi, setSearchTic] = useState('')
return (
<div className="App">
{renderMenuItems()}
<p>Value: {searchTi}</p>
<input value={searchTi} onChange={(e) => setSearchTic(e.target.value)}/>
</div>
);
}
export default App;

simultaneous updates of React in React

Is it possible to update one state in React and in the same render, can we use the updated value to update another state?
import "./styles.css";
import {useState} from 'react';
export default function App() {
const [num, setNum] = useState(0)
const [txt, setTxt] = useState(0)
handleClick= () =>{
setNum(num+1)
setTxt(num+1)
}
return (
<div className="App">
<input type="submit" value="button" onClick={handleClick} />
<h1>{num}</h1>
<h1>{txt}</h1>
</div>
);
}
In the above example, both num and txt are initially set to 0.
Clicking the button for the first time would increase both num and txt to 1 and it would update both to 2 when clicked again and so on.
Is there way to get the num updated to 1 as soon as setNum(num+1) is called, so that it can update it from 0 to 1 and so while calling setTxt(num+1), it can update it directly to 2?
Yes, you can do that using the useEffect hook with the num as a dependency.
Here's a CodeSandbox:
import { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [num, setNum] = useState(0);
const [txt, setTxt] = useState(0);
const handleClick = () => {
setNum(num + 1);
};
useEffect(() => {
if (num > 0) {
setTxt(num + 1);
}
}, [num]);
return (
<div className="App">
<input type="submit" value="button" onClick={handleClick} />
<h1>{num}</h1>
<h1>{txt}</h1>
</div>
);
}
If the actual state is just a number and not something complex like an array/object then you can simply do this:
const handleClick = () => {
setNum(num + 1);
setTxt(num + 2);
};

Resources