React hook dependent functions as props - reactjs

After reading through the intro to hooks I have an immediate feeling that it has a performance problem with passing function props.
Consider the following class component, where the function reference is a bound function, so no re-renders happen because of it.
import React from 'react';
class Example extends React.Component {
state = { count: 0 }
onIncrementClicked = () => setState({ count: this.state.count + 1 })
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={this.onIncrementClicked}>
Click me
</button>
</div>
);
}
}
Now compare it to the hooks-version where we pass a new function on each render to the button. If an <Example /> component renders, there's no way of avoiding the re-rendering of it's <button /> child.
import React, { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
I know it's a small example, but consider a bigger app where many callbacks are passed around that depend on hooks. How could this be optimised?
How would I avoid re-rendering everything that takes a function prop, that depends on a hook?

You can use useCallback to ensure that event handler doesn't change between renders with the same count value:
const handleClick = useCallback(
() => {
setCount(count + 1)
},
[count],
);
For better optimisation you can store count value as an attribute of the button so you don't need access to this variable inside event handler:
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
const handleClick = useCallback(
(e) => setCount(parseInt(e.target.getAttribute('data-count')) + 1),
[]
);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={handleClick} data-count={count}>
Click me
</button>
</div>
);
}
Also check https://reactjs.org/docs/hooks-faq.html#are-hooks-slow-because-of-creating-functions-in-render

Related

Why i have trouble using the state updater function while using useState hook?

While using "const [count, setCount] = useState(0)" useState as so i thought the setCount function is a simple function to update the test variable. But it turns i can't work with it unless i use it in the jsx as so <button onClick={() => setCount(count + 1)}>
here's what i tried and it shows no result but blank on the display
function App() {
const [count, setCount] = useState(0);
setCount(1);
return (
<div> {test + 1} A simple div </div>
);
}
i thought it would display "2 A simple div" but nothing displays.
however this code works
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
Thanks
when state update react component re-render.
update the state directly inside the component function re-render the component again. when it re-render again state update function is called so it went into an infinite loop so it renders nothing.

Basic React question. why is useEffect needed in this example from the React documentation?

This example is given in the react documentation to describe the use of useEffect.
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Could this not be re-written without using useEffect as below?
Why is useEffect preferred?
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
const handleChange = () => {
setCount(count + 1);
document.title = `You clicked ${count} times`;
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={handleChange}>
Click me
</button>
</div>
);
}
There are two issues here:
Calling a state setter won't reassign the stateful const variable in the current render - you need to wait until the next render for count to be updated, so
const handleChange = () => {
setCount(count + 1);
document.title = `You clicked ${count} times`;
}
won't work. Your second snippet will result in You clicked 0 times after a single click.
Even if that were possible, when there are multiple places where a state setter may be called, putting something that should happen afterwards after every single one isn't very maintainable - it'd be better to be able to put that code to run afterwards in only one place.

ReactJS: Increment counter keeps refreshing component

So recently I started a project for fun to mess around with NextJS.
I ran into a problem regarding my count state:
//Core
import React, { useState, useEffect } from 'react';
//Style
import styles from './form.module.scss';
export default function Form({ placeholder }) {
const [count, setCounter] = useState(0);
return (
<form>
<input
type='text'
placeholder={placeholder}
className={styles.error}
></input>
<button onClick={() => setCounter(count + 1)}>{count}</button>
</form>
);
}
This keeps refreshing my count state everytime I click the increment button. What is the correct way to do a increment on click, without rerendering the comoponent?
I found examples like these online:
https://codesandbox.io/s/react-hooks-counter-demo-kevxp?file=/src/index.js:460-465
How come my counter keeps resetting, and theirs is not?
You are inside a form.
Use preventDefault first in order for the form not to be submitted everytime.
<button
onClick={(e) => {
e.preventDefault();
setCounter(count + 1);
}}
>
{count}
</button>
See it in action here
You can also set the button's type to "button" to prevent form submission
<button type="button" onClick={() => setCounter(count + 1)}>{count}</button>
With a useRef you can keep the same data across re-renders
import React, { useState, useRef } from "react";
export const Form = () => {
const counterEl = useRef(0);
const [count, setCount] = useState(counterEl.current);
const increment = () => {
counterEl.current = counterEl.current + 1;
setCount(counterEl.current);
console.log(counterEl);
};
return (
<>
Count: <span>{count}</span>
<button onClick={increment}>+</button>
</>
);
};
I think this is because you wrap your button in form
by default when you press button in form submit trigger
and then whole page render so state reset

How to prevent double rerender when I use props, useState and useEffect?

For example I have this component
const FooBar = (props) => {
console.log("render")
const [foo, setFoo] = useState(props.foo)
useEffect(() => {
setFoo(props.foo)
}, [props.foo])
return (
<div>
{foo} <button onClick={() => setFoo(x => x + 1)}>component plus</button>
</div>
);
}
And I can change it props like this
const App = () => {
const [foo, setFoo] = useState(1)
return (
<div>
<FooBar foo={foo} />
<button onClick={() => setFoo(x => x + 1)}>parent plus</button>
</div>
);
}
When I click on parent plus button the FooBar component will rerender two times. the first one is from props change and the second one is from setFoo inside useEffect.
How can I prevent second rerender?
I am not exactly sure what your goal with this example would be.
If you want to have two buttons that increment the same counter, than your code currently does not work as intended.
If you click the 'component plus' button several times and afterwards click the 'parent plus' button, your counter will reset to the value when you last clicked the parent button.
In case you want to pass state to child components, you usually do not want to save the passed state as the child components state. That will make it unnecessarily harder to sync up both states.
What you would do, is to pass the state and a function to set that state to the child component.
Your example would then look like this:
const App = () => {
const [foo, setFoo] = React.useState(1);
return (
<div>
<FooBar foo={foo} setFoo={setFoo} />
<button onClick={() => setFoo(x => x + 1)}>parent plus</button>
</div>
);
};
const FooBar = props => {
console.log("render");
return (
<div>
{props.foo} <button onClick={() => props.setFoo(x => x + 1)}>component plus</button>
</div>
);
};

Unexpected(?) behavior of reusable React hook functions or what am I doing wrong?

I am trying to write my own reusable useEffect hook function useEffectOnce() to run only once (on mount and unmount), but it seems impossible. My function is called after every re-render: https://codesandbox.io/s/mjx452lvox
function useEffectOnce() {
console.log("I am in useEffectOnce");
useEffect(() => {
console.log("I am in useEffectOnce's useEffect");
return () => {
console.log("i am leaving useEffectOnce's useEffect");
};
}, []);
}
function App() {
const [count, setCount] = useState(0);
useEffectOnce(); // once!?
return (
<div className="app">
<h3>useEffectOnce (my reusable function)... Wait, once!?</h3>
<button onClick={() => setCount(count + 1)}>
You clicked me {count} times
</button>
</div>
);
}
Notes:
useEffect() hook inside useEffectOnce() works as expected
useEffectOnce(() => {}, []); changes nothing
... and then big surprise, same behavior with reusable useState hook function(!?): https://codesandbox.io/s/2145m37xnr
My function useButton is called after every re-render, and when is first, independent, button clicked.
function useButton(initialCounterValue) {
const [usebuttoncount, setUseButtonCount] = useState(initialCounterValue);
console.log("I am in useButton");
const handleuseButtonClick = () => {
setUseButtonCount(usebuttoncount + 1);
};
return {
usebuttoncount,
onClick: handleuseButtonClick
};
}
function App() {
const [count, setCount] = useState(0);
const useButtonCounter = useButton(0);
return (
<div className="app">
<h3>
useButton (my reusable useState function) is called only when... OMG!?
</h3>
<button onClick={() => setCount(count + 1)}>
You clicked me {count} times
</button>
<br />
<button {...useButtonCounter}>
(useButton hook) You clicked me {useButtonCounter.usebuttoncount} times
</button>
</div>
);
}
useEffectOnce and useButton are regular functions. They are expected to run on every component render. When component function is called, there's no way to prevent them from being called by conventional JavaScript means (i.e. with no eval).
It's useEffect callback function (I am in useEffectOnce's useEffect log entry) that is expected to be called once on component mount. All code that needs to be evaluated on component mount should reside there.

Resources