warning when using React Hook useCallback when setting state - reactjs

I have a website with a super simple navigation.
It just displays different components based on the boolean value of a state property.
It's working fine, but I have this nagging warning that shows on the functions that handle clicks.
The warning is here:
useCallback does nothing when called with only one argument.
But I'm not sure why it's telling me that because my navigation is working.
Do I need to add something else to make it work better?
Thanks!
Here is my little component for navigation.
// creat state
const [showCreationPage, setCreationPage] = useState(true);
const [showDisplayPage, setDisplayPage] = useState(false);
// here is warning sign: useCallback does nothing when called with only one argument.
const handleCreationPage = useCallback(e => {setDisplayPage(false) || setCreationPage(true) });
const handleDisplayPage = useCallback(e => { setCreationPage(false) || setDisplayPage(true) });
// navigation buttons
<a href="#" onClick={handleCreationPage}>Create Beer</a>
<a href="#" onClick={handleDisplayPage}>Display Beer</a>
<div id="main">
<div>
{showCreationPage && <Create />}
</div>
<div>
{showDisplayPage && <Display />}
</div>
</div>

useCallback expects an array of dependencies as a second argument. That tells the memoization to update whenever the value of one of the dependencies is updated. If you never want the callback function to update, just pass an empty array.
Reference: https://reactjs.org/docs/hooks-reference.html#usecallback

Related

How to re-render a map loop in JSX?

I have a sidebar with buttons links that targets div id and URL ends with #id_name. Now I want to render data that matches #id_name in a map loop using this code:
<div>
{entries.map((item, index) => {
if (asPath.endsWith(`#${item.section}`))
return (
<div id={item.section} key={index}>
<h3>{item.title}</h3>
<p>{item.summary}</p>
</div>
);
})}
</div>
It works on refresh if #id_name matches item.section but if I click another link nothing happens even if item.section matches #id_name.
How can I re-render the map loop without refreshing or leaving the page when #id_name changes?
If you want to re-render to work
asPath or entries should be in useState or redux state.
I think asPath is more correct one for this case... try using useEffect and useState
I did figure it out without using a state. Instead of using Next Link or <a> on button links, I used push from useRouter. Weird how that one works.
const router = useRouter();
const handleHref = (link) => {
router.push(link);
};
<Botton onClick={() => handleHref(`#${String(item.section).replace(/ /g, "-")}`)}>{item.section}</Botton>

nextjs conditional jsx not re-rendering

Up in my component I have:
let isClicked = false;
Down where things are rendered I have:
<div className="relative">
{!isClicked ? (
<div className="relative">
1
</div>
) : (
<div className="relative">
2
</div>
)
}
</div>
The issue is nothing changes when the variable changes.
I can ensure the variable changes on the command line.
And the logic works if I change it and manually reload the page.
The issue is it won't re-render when the variables changes during the run time.
There are special variables in react that cause rerender on their change. These are state variables. Imagine the problem you will face if every variable change causes a rerender.
If this is a functional component you can make use of useState:
const [isClicked,setIsClicked] = useState(false);
.
.
//some method triggered by button click etc.
const onclick =() => {
setIsClicked(true);
}
.
.
Read : Link and Link

How to mitigate useless React renders when large state is present but only one changes?

I have a search query component. User types query, clicks run and executes it. However, due to how state is organized, it causes excessive re renders. Following is a simplification of it. This organization of the code feels natural to me. QueryEditor is normally a complex editor, hence it stores its own value. Similar for query results. I got the value of it on each change, updated the query in the parent component. However this causes all HTML elements in the parent component to re-render even though their state does not change.
First of all, not being very familiar with the concepts;
Is this expected behavior? What I've done might not be ideal, but causing every DOM elment to rerenders because parent state changed feels unncesseary to me. Or maybe I'm doing something wrong.
How would this be improved? Obviously thing to improve is not get every change as a callback, but I did not find a good way to call a method of child component to getValue() from a parent component.
Link if you want: https://codesandbox.io/s/purple-architecture-5kpx5?file=/src/App.tsx You can clearly see the performance impact with React DevTools and see the render time increases with the number of elements even the elements did not change but the query text changes.
function App() {
console.log("Re-render?");
const [name, setName] = useState("mustafa");
const [query, setQuery] = useState("");
const [data, setData] = useState<number[]>([]);
const runQuery = () => {
console.log("using query...", query);
setData([...Array(data.length + 1000).keys()]);
};
return (
<div>
<h1>Hello {name}</h1>
<p>Please enter your query</p>
<QueryEditor
onChange={(e) => {
setQuery(e.target.value);
}}
/>
<br />
<button onClick={runQuery}>Run Query</button>
<hr />
<h4>Results</h4>
{data.length > 0 && <p>There are {data.length} results </p>}
{data.length > 0 && <DataResults data={data} />}
</div>
);
}
interface EditorProps {
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
}
function QueryEditor({ onChange }: EditorProps) {
return <textarea onChange={onChange} />;
}
function DataResults({ data }: { data: number[] }) {
return (
<div>
<ul>
{data.map((a) => (
<li key={a}>Item {a}</li>
))}
</ul>
</div>
);
}
useState will always cause re-rendering. When you type in the textarea, you update query which cause rerendering (it's the normal behaviour of useState).
In your case, you don't use query until you click the button to run the query.
You can change const [query, setQuery] = useState(""); by const query = useRef(""). Then your QueryEditor onChange function becomes
{(e) => {
query.current = (e.target.value);
}}
This way you should not have all these re renders.
PS : when you run the query, you must use query.current instead of just query
you can't change the parent's behaviour of re-rendering. That is the way react works, when the children has to re-render, then the parent has to re-render the child, thus being called again. Have a look at the docs to understand the logic behind re-rendering.
What you can do in this case, is to memoized the siblings that has huge data.
So if I do this:
const DataResultsMemoized = React.memo(DataResults);
Then use it in the parent:
{data.length > 0 && <DataResultsMemoized data={data} />}
Then your sibling won't re-render every time you type in the textbox.
Here's code sandbox example, check that the console.log in the children doesn't go again when you type

React: Setting and updating based on props

Currently I am facing the problem that I want to change a state of a child component in React as soon as a prop is initialized or changed with a certain value. If I solve this with a simple if-query, then of course I get an infinite loop, since the components are then rendered over and over again.
Component (parent):
function App() {
const [activeSlide, setActiveSlide] = useState(0);
function changeSlide(index) {
setActiveSlide(index);
}
return (
<div className="app">
<div className="app__nav">
<Button icon="FiSun" handler={changeSlide} active={activeSlide} index="0" />
<Button icon="FiSettings" handler={changeSlide} active={activeSlide} index="1" />
</div>
</div>
);
}
Component (child):
function Button(props) {
const Icon = Icons[props.icon];
const [activeClass, setActiveClass] = useState("");
// This attempts an endless loop
if(props.active == props.index) {
setActiveClass("active");
}
function toggleView(e) {
e.preventDefault();
props.handler(props.index);
}
return(
<button className={activeClass} data-index={props.index} onClick={toggleView}>
<Icon />
</button>
)
}
Is there a sensible and simple approach here? My idea would be to write the if-query into the return() and thus generate two different outputs, even though I would actually like to avoid this
The React docs have a nice checklist here used to determine if something does or does not belong in state. Here is the list:
Is it passed in from a parent via props? If so, it probably isn’t state.
Does it remain unchanged over time? If so, it probably isn’t state.
Can you compute it based on any other state or props in your component? If so, it isn’t state.
The active class does not meet that criteria and should instead be computed when needed instead of put in state.
return(
<button className={props.active == props.index ? 'active' : ''} data-index={props.index} onClick={toggleView}>
<Icon />
</button>
)
This is a great use of useEffect.
instead of the if statement you can replace that with;
const {active, index} = props
useEffect(_ => {
if(active == index) {
setActiveClass("active");
}
}, [active])
The last item in the function is a dependency, so useEffect will only run if the active prop has changed.
React automatically re-renders a component when there is a change in the state or props. If you're just using activeClass to manage the className, you can move the condition in the className as like this and get rid of the state.
<button className={props.active === props.index ? 'active' : ''} data-index={props.index} onClick={toggleView}>
<Icon />
</button>
however, if you still want to use state in the child component, you can use the useEffect hook to to update the state in the child component.
Try to use the hook useEffect to prevent the infinite loop. (https://fr.reactjs.org/docs/hooks-effect.html)
Or useCallback hook. (https://fr.reactjs.org/docs/hooks-reference.html#usecallback)
Try this and tell me if it's right for you :
function App() {
const [activeSlide, setActiveSlide] = useState(0);
const changeSlide = useCallback(() => {
setActiveSlide(index);
}, [index]);
return (
<div className="app">
<div className="app__nav">
<Button icon="FiSun" handler={changeSlide} active={activeSlide} index="0" />
<Button icon="FiSettings" handler={changeSlide} active={activeSlide} index="1" />
</div>
</div>
);
}

Problem Too many re-renders.Try to render components in connection with a button and state hooks

I'm new to react and face following problem:
I have two buttons and want to render two different components. Pressing one button should set a variable true via state hook and the first component should be shown, on the other side the variable for the other component should be set to false. With this logic I try to render either one or the other component.
With this setup I get following Error Message:
Too many re-renders. React limits the number of renders to prevent an infinite loop.
I guess the solution is simple but I cannot see the why this causes a problem.
Thx for any help!
Here a simplified version of the relevant code:
import { Button } from "semantic-ui-react";
import React, { useState } from "react";
const App = () => {
const [showAggregated, setshowAggregated] = useState(true);
const [showDetailed, setshowDetailed] = useState(false);
return (
<div>
Test
<Button.Group>
<Button
size="mini"
onClick={(() => setshowAggregated(true), setshowDetailed(false))}
>
Aggregated Visualization{" "}
</Button>
<Button.Or />
<Button
size="mini"
onClick={(() => setshowDetailed(true), setshowAggregated(false))}
>
Detailed Visualization
</Button>
</Button.Group>
{/* Second Part of Page if Aggregated Component */}
{showAggregated && <div>Aggregated</div>}
{/* Second Part of Page if Detailed Component */}
{showDetailed && <div>Detailed</div>}
</div>
);
};
export default App;
onClick={(() => setshowDetailed(true), setshowAggregated(false))}
You are using Comma Operator above, which evaluates each of its operands.
In your case you have 2 operands: () => setshowDetailed(true) and setshowAggregated(false), so basically what happens is that each time the component is rendered, the second operand (setshowAggregated(false)) is called, which re-render the component which eventually ends up in an infinite loop.
What you really wanna do is this (an event handler that calls both functions):
onClick={() => {
setshowDetailed(true);
setshowAggregated(false));
}}
Same thing for the second button.

Resources