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
Related
When I click on the 'Rerender UI' button then it prints in the console for the first two clicks. I think it should print in the console only for the first time when I click on the 'Rerender UI' button because on the button click the component state is changed so UI will re-render and the console log will be printed in the console. Why is it printing for the second click? StrictMode is off. See code:
export default function UseCallbackComp() {
const [stateVar, setStateVar] = useState<any>()
console.log("Parent Rerendered!")
return (
<>
<div>UseCallbackComp content</div>
<div>
<button
onClick={() => {
setStateVar(1)
}}
>
Rerender UI
</button>
</div>
</>
)
}
When I put the console log line inside useEffect like below it prints only for the first time 'ReRender UI' button is clicked which is the expected behaviour.
useEffect(() => {
console.log("Parent Rerendered!")
})
From the below two links I got to know whats the behaviour of react when useState is used:
stackoverflow question
gitHub discussion
There are two different cases for which useState behaves differently for same value.
Case 1: When useState used with same value as the initial value.
Result: No rerendering at all.
export default function LifecycleEvents() {
const [stateVar, setStateVar] = useState<any>(0)
console.log("Parent Rerendered!")
return (
<>
<button
onClick={() => {
setStateVar(0) //same value as the initial value
}}
>
Rerender UI
</button>
</>
)
}
Case 2: When useState used with different value first and then with the same value.
Result: First both Parent and child will be re-rendered. But for the second time, only the render function of Parent component will get called, Nothing else.
See code:
export default function LifecycleEvents() {
const [stateVar, setStateVar] = useState<any>(0)
console.log("Parent Rerendered!")
return (
<>
<button
onClick={() => {
setStateVar(1) //different value then initial value.
}}
>
Rerender UI
</button>
</>
)
}
Conclusion: Nothing to worry unless you have an expensive render method of the component. In that case use memo.
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>
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>
);
}
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
I am writing a react app with css grids. I'm not going to include the css here but it is a 2x2 grid.
import { useState } from 'react';
function Container() {
const [count, setCount] = useState(0);
return (
<div className = "gridwrapper">
<div className = "top_left"> <SomeCustomComponent></div>
<div className = "bottom_left"> <CustomCounter counter = {count}></div>
<div className = "bottom_right"> <CustomCounter counter = {count+2}></div>
<div className = "top_right"><button onClick={() => setCount(count + 1)}>Click me</button><div>
</div>
);
}
function CustomCounter({count}){
return(<p>The count is {count}</p>)
}
I have two issues right now
Since setState would cause re-render, now it would re-render the whole thing. But I only need the bottom two cells to re-render since other parts of my Container component do not even depend on props.
In order for my grid structure to work properly, I need to wrap them in divs, why is that? I tried to assign classnames directly before but it didn't work
Regarding unnecessary re-renders - don't worry about it. The React diffing engine is smart enough to know which elements have actually changed and which ones haven't, and it will only update what is necessary.
You could use a useEffect listener to only change when specific variables change. As far as I know you can not just re-render a part of the component.
When you make a custom component, you need to make the className prop available on the child component aswell.
e.g.
function SomeCustomComponent(props) {
return (
<div className={props.className}>
// ... your component stuff here ..
</div>
)
}