Logic of rerendering in react function components - javascript

I have a simple App component
export default function App() {
const [count, changeCount] = useState(0)
const onIncreaseClick = useCallback(() => {
changeCount(count + 1)
}, [count])
const onPress = useCallback(() => {
alert('pressed')
}, [])
return (<>
<button onClick={onIncreaseClick}>Increase</button>
<ButtonPressMe onClick={onPress} />
</>);
}
I expect that onPress variable contains always the same link since parameters never change
And i expect that my ButtonPressMe component will be rendered just once - with the first App component rendering... because it has just one prop and value of this prop never change... therefore no need to rerender component. Correct?
Inside my ButtonPressMe component i check it with console.log
const ButtonPressMe = ({ onClick }) => {
console.log('Button press Me render')
return <button onClick={onClick}>Press me</button>
}
And against my expectations it rerenders each time when parent component rerenders after Increase button is pressed.
Did i misunderstood something?
sandbox to check

And against my expectations it rerenders each time when parent component rerenders after Increase button is pressed.
Did i misunderstood something?
That's the default behavior in react: when a component renders, all of its children render too. If you want the component to compare its old and new props and skip rendering if they didn't change, you need to add React.memo to the child:
const ButtonPressMe = React.memo(({ onClick }) => {
return <button onClick={onClick}>Press me</button>
})

By default, when a parent component re-renders, all of its child components re-render too.
useCallback hook will preserve the identity of the onPress function but that won't prevent a re-render of the ButtonPressMe component. To prevent a re-render, React.memo() is used. useCallback hook is used to avoid passing a new reference to a function, as a prop to a child component, each time a parent component re-renders.
In your case, combination of React.memo and useCallback hook will prevent a re-render of ButtonPressMe component.
function App() {
const [count, changeCount] = React.useState(0);
const onIncreaseClick = React.useCallback(() => {
changeCount(count + 1);
}, [count]);
const onPress = React.useCallback(() => {
alert("pressed");
}, []);
return (
<div>
<button onClick={onIncreaseClick}>Increase</button>
<ButtonPressMe onClick={onPress} />
</div>
);
}
const ButtonPressMe = React.memo(({ onClick }) => {
console.log("Button press Me render");
return <button onClick={onClick}>Press me</button>;
});
ReactDOM.render(<App/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>

The default behavior in React is to change everything in the App when anything changes, in your case you're changing the state of the parent of your custom button, therefore React re-renders everything including your button.
You can find an explanation on how React decides to re-render components here:
https://lucybain.com/blog/2017/react-js-when-to-rerender/#:~:text=A%20re%2Drender%20can%20only,should%20re%2Drender%20the%20component.

Related

In functional react, how to automatically simulate a button press immediately after a component renders?

So let's suppose that there is some clickable widget in a component? How do I tell the window to automatically click that widget immediately after the component renders?
You can do this with ref using hooks
import React, {useRef} from 'react';
const MyComponent = () => {
const myBtnRef= useRef(null);
useEffect(()=>{
myBtnRef.click();
}, []}
return (
<button ref={myBtnRef}>Click Me</button>
);
}
If the button is expected to be clicked after each rendering, the easiest way is
<button ref={() => console.log(`clicked`)}>Button</button>
You can add debounce in the callback to avoid redundant invocation.
If the button is expected to be clicked after each mounting, use the useEffect and useRef hooks.
const Containerr = () => {
const btnRef = useRef(null)
useEffect(() => {
ref.current.click()
}, [])
return (
<button ref={btnRef}>Button</button>
)
}
Well, in just one line but must be on the clickable element.
useEffect(()=>{
document.getElementById('Id').click();
}, []}

function declared with const uses the old value of useState

I have a React component (functional) that contains a child component modifying the state of the parent component. I am using the hook useState for this.
After the state change, there is a "Next" button in the parent component that executes a function referencing the updated state. The problem is this next function uses the old state from before the state was modified by the child component.
I can't use useEffect here as the function needs to execute on the click of the "Next" button and not immediately after the state change. I did some digging about JavaScript closures, but none of the answers address my specific case.
Here's the code
const ParentComponent = () => {
const [myState, setMyState] = useState(0);
const handleNext = () => {
console.log(myState); // prints 0 which is the old value
}
return (
<ChildComponent modifyState = {setMyState} />
<Button onClick={handleNext} > Next </Button>
)
}
export default ParentComponent;
BTW there are no errors.
It's a little difficult to understand without your ChildComponent code. setMyState suggests that you need to update the increase the state by one when you click the next button, but you can't do that without also passing in the state itself.
An easier (imo) solution is to pass a handler down to the child component that is called when the next button is clicked. The handler then sets the state.
const {useState} = React;
function ChildComponent({ handleUpdate }) {
function handleClick() {
handleUpdate();
}
return <button onClick={handleClick}>Click</button>
}
function Example() {
const [myState, setMyState] = useState(0);
function handleUpdate() {
setMyState(myState + 1);
}
function handleNext() {
console.log(myState);
}
return (
<div>
<ChildComponent handleUpdate={handleUpdate} />
<button onClick={handleNext}>Next </button>
</div>
)
}
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
try to modify like this
<ChildComponent modifyState={(value) => setMyState(value)} />

react memo is not getting props

React memo isn't capturing the props neither the prevProps nor the nextProps and the component render well. The react docs say
If your function component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost.
my problem is to stop twice rendering using react memo, but memo seems to be not working and the component renders twice with the same props.
The component renders when the Create New Event is clicked on /events
here is the live sandbox.
Child Component located at /components/Event/CreateEvent/CreateEvent.js
the parent component is located at /Pages/Event/Event.js line number 999' from where the child component is being triggered
Here is the Code:
import React from "react";
import AuthContext from "../../context/global-context";
import CreateEvent from "../../components/Event/CreateEvent/CreateEvent";
function Events({ location }) {
// Sate Managing
const [allEvents, setAllEvents] = React.useState([]);
const [creating, setCreating] = React.useState(false);
// Context As State
const { token, email } = React.useContext(AuthContext);
// Creating Event Showing
const modelBoxHandler = () => {
// works on when the ViewEvent is open
if (eventSelected) {
setEventSelected(null);
return;
}
setCreating(!creating);
};
return (
<div className="events">
{/* New Event Creating */}
{creating && (
<CreateEvent onHidder={modelBoxHandler} allEvents={allEvents} />
)}
{console.log("Event Rendered.js =>")}
</div>
);
}
export default React.memo(Events, () => true);
Child Component where the Rect memo doesn't have props:
import React from "react";
import AuthContext from "../../../context/global-context";
function CreateEvent({ onHidder, allEvents }) {
// Context
const { token } = React.useContext(AuthContext);
console.log("CreatedEvent.js REnder");
return (
... Some code here
);
}
export default React.memo(CreateEvent, (prevProps, nextProps) => {
console.log("Hello", prevProps, nextProps);
});
Thanks in advance for your valuable answer and times!
The problem is that on basis of creating variable you are actually remounting and not rendering the CreateEvent component. What it means is that if creating variable changes, the component is unmounted and re-mounted when creating is true, so its not a re-render
Also you must note that modelBoxHandler function reference also changes on each re-render so even if your CreateEvent component is in rendered state and the parent re-rendered due to some reason , the CreateEvent component too will re-render
There are 2 changes that you need to make to make it work better
Define modelBoxHandler with a useCallback hook
perform conditional rendering in createEvent based on creating prop
// Creating Event Showing
const modelBoxHandler = useCallback(() => {
// works on when the ViewEvent is open
if (eventSelected) {
setEventSelected(null);
return;
}
setCreating(prevCreating => !prevCreating);
}, [eventSelected]);
...
return (
<div className="events">
{/* New Event Creating */}
<CreateEvent creating={creating} onHidder={modelBoxHandler} allEvents={allEvents} />
{console.log("Event Rendered.js =>")}
</div>
);
and in createEvent
function CreateEvent({ onHidder, allEvents, creating }) {
// Context
const { token } = React.useContext(AuthContext);
console.log("CreatedEvent.js REnder");
if(!creating) {
return null;
}
return (
... Some code here
);
}
export default React.memo(CreateEvent);
In your example, you don't have an additional render for React.memo to work.
According to your render logic, there aren't any nextProps, you unmount the component with conditional rendering (creating).
// You toggle with `creating` value, there is only single render each time
creating && <CreateEvent onHidder={modelBoxHandler} allEvents={allEvents}/>
// Works, because there will be multiple renders (nextProps)
true && <CreateEvent onHidder={modelBoxHandler} allEvents={allEvents} />
In this case, you might not need React.memo.

is there any benefit of using useCallback without React.memo?

From what i understood from React documentation and other material across web, useCallback is used to avoid re-rendering of child component by ensuring that memoized version of callback is passed to it, thus referentially props remain same for child component. But all this is valid only if I am using React.memo on child component. Without React.memo, child component would re-render anyways. My question is what use is useCallback in this case i.e. without React.memo applied to child component. What are the other benefits of useCallback?
React.memo ensures that a shallow comparison is performed when props enter a component and skips rendering of the component when they are equal.
Given a child component Cell:
When applied to a function component that is created during the render of another, parent component, a new Cell component will be created on each render. This new Cell component will always make shallow comparisons on its props but it will be re-rendered on every render of its parent.
useCallback will however memoize this function callback Cell if it's dependency array does not change during a parent re-render.
Alone a function component Cell wrapped in a useCallback will always re-render when it receives props, which will happen on every render of its parent.
The difference however is that it's entire subtree is re-rendered if the component itself is recreated, as is the case when using React.memo by itself.
Paired together you can however get around re-rendering components defined inside of a parent.
As you will notice, when attempting to drag the Memoized cell, the one not wrapped in useCallback, no dragging happens. That is because the original element you attempt to drag is recreated as a new instance when it's parent re-renders.
This concept is explained in more detail here
Example
const { useCallback, useState, useRef } = React;
function App() {
const [state, setState] = useState(false);
const [title, setTitle] = useState("");
const Cell = useCallback(({ title }) => {
console.log(`${title} rendering`);
function onDragStart() {
setState(true);
}
function onDragEnd() {
setState(false);
}
return (
<div draggable onDragStart={onDragStart} onDragEnd={onDragEnd}>
Drag {title}
</div>
);
}, []);
const MemoizedCell = React.memo(({ title }) => {
console.log(`${title} rendering`);
function onDragStart() {
setState(true);
}
function onDragEnd() {
setState(false);
}
return (
<div draggable onDragStart={onDragStart} onDragEnd={onDragEnd}>
Drag {title}
</div>
);
});
const MemoizedCallbackCell = useCallback(
React.memo(({ title }) => {
console.log(`${title} rendering`);
function onDragStart() {
setState(true);
}
function onDragEnd() {
setState(false);
}
return (
<div draggable onDragStart={onDragStart} onDragEnd={onDragEnd}>
Drag {title}
<button onClick={() => setTitle(" Updated")}>Change Title</button>
</div>
);
}),
[]
);
return (
<div className="App">
<Cell title="Cell" />
<MemoizedCell title="Memoized Cell" />
<MemoizedCallbackCell title={"Memoized Callback Cell" + title} />
Is dragging {`${state}`}
<br />
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
You might also need to pass a function to useEffect without changing useEffect on every render
For example:
import { useCallback, useEffect } from "react";
function useExample() {
function fn() {
console.log("a function");
}
const callback = useCallback(fn, []);
useEffect(() => {
callback();
}, [callback]);
}
Real-world example with useDebounce:

Why does React.useCallback trigger rerender, thouh it should not?

I have redux connected Component with onClick action bound to it. Every time I click it rerenders, though I use useCallback hook. Here is my simplified component:
const Map = props => {
const dispatch = useDispatch(); // from react-redux
const coordinates = useSelector(state => state.track.coordinates); // from react-redux
const onClick = useCallback( // from react
data => {
return dispatch({type: 'ADD_COORDINATES', payload: data});
},
[dispatch]
);
return (
<div className="Map">
<GoogleMap
onClick={onClick}>
<Track
coordinates={coordinates}
/>
</GoogleMap>
</div>
);
};
Without giving any additional context, and that the component is really "simplified" (there is nothing else that may cause a render), Map component will re-render only on its parent render:
const Parent = () => {
const coordinates = useSelector(coordinatesSelector);
return <Map />;
};
On dispatching addCoordinates action you may trigger its parent.
You should try and memoize the Map component:
If your function component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result.
const Map = () => {
...
return ....;
};
export default React.memo(Map);
Edit after question update:
Your component re-renders due to useSelector as stated in the docs:
When an action is dispatched, useSelector() will do a reference comparison of the previous selector result value and the current result value. If they are different, the component will be forced to re-render. If they are the same, the component will not re-render.
Therefore, you might want to add additional equalityFn:
const coordinates = useSelector(state => state.track.coordinates, areSameCoords)

Categories