How to set useState while checking propsType - javascript

hello I'm trying to set useState but it crashing the whole code. here I'm sending propstype from parent component to child component. so when propstype condition is checked/true then I'm trying to set useState but instead of setting it crashes. please checked below what I tried, Here I've removed unwanted code otherwise it'll too big for readers and they might lost the focus from question. can anyone suggest me any new solution for this I've tried a lot of and then came here for the proper solution.
parent component (BillComponent)
const BillComponent = () => {
return(
{/* Add Bill form */}
<Drawer
title={"Add Bill"}
placement="right"
onClose={onClose}
visible={visible}
className="ant-drawer-half"
>
<AddBill parentCallback={handleCallback} type="add" /> // <---This AddBill is child component and here I'm passing props type as add
</Drawer>
</>
</>
);
};
export default BillComponent;
child component (AddBill)
const AddBill = (props) => {
const [checked, setChecked] = useState(false);
if(props.type === "add"){
setChecked(false); //<---This is where main code is crashing. when I set useState
}else{
setChecked(true);
}
return(
// divs of project
);
};
export default AddBill;

Not sure if that's your case because you didn't show the error. Probably the error will be something related to "Too many re-renders".
It's a bad practice to use those React hooks in the "root" of the component. In your particular case, I'd do the following:
const [checked, setChecked] = useState(false);
useEffect(() => {
if (props.type === "add") {
setChecked(false);
} else {
setChecked(true);
}
}, [props.type]);

Please share the error you're getting.
Also why not just use:
const [checked, setChecked] = useState(props.type === 'add');

Related

why is my if else statement returns true two times?

so I am new to React and I am trying to learn the basics. I got stuck at a point, where I try to show/hide an element on a page. The problem is that when I click Show details, it works, but Hide details must be clicked 2 times in order to do what its supposed to do.
Can you help me understand this?
import React, { useState } from 'react'
const Playground2 = () => {
let visible = false;
const [details, showDetails] = useState();
const [buttonText, changeButtonText] = useState("Show details");
const toggleVisibility = () => {
if (visible) {
showDetails("");
visible = false;
console.log(visible);
changeButtonText("Show details")
} else {
showDetails("Here are some details");
visible = true;
console.log(visible);
changeButtonText("Hide details");
}
}
return (
<>
<section>
<div className="container">
<h1>Visibility Toggle</h1>
<button onClick={toggleVisibility}>{buttonText}</button>
<p>{details}</p>
</div>
</section>
</>
)
}
export default Playground2
You should use the state in your condition. If you declare a variable like your visible one, this will be assigned on every render (every time you set the state with changeButtonText or showDetails. So every time will be set to false. You can simplify your component by doing:
import React, { useState } from 'react'
const Playground2 = () => {
const [visible, setVisible] = useState();
const toggleVisibility = () => {
setVisible(prevState => !prevState)
}
const buttonText = visible ? 'Hide details' : 'Show details'
const details = 'Here are some details'
return (
<>
<section>
<div className="container">
<h1>Visibility Toggle</h1>
<button onClick={toggleVisibility}>{buttonText}</button>
{visible && <p>{details}</p>}
</div>
</section>
</>
)
}
export default Playground2
Well it'd solve your problem if you turn visibility to state as well.
What I think happening is that when you click on button, the visibility variable is turned to false but component isn't refreshed. In order for component to get refreshed, there must be some change in state.
Maybe try that. That should do the trick.
Tip: Variables like loading, visibility, modal closed/open should be state variables.
Move let visible = false out of the component body and this will work as expected, since you are putting visible inside Component, every time the component updates false will be stored in visible.
let visible = false
const Playground 2 = () => {}

Can not update a component while rendering a different component

I am using the useAuth hook to my authContext, so that I can use hook to set global states for my components.
Now, I am trying to navigate to the '/' page if the user was not logged in, and also display a requireLoginAlert to the user in the '/' page after the user was redirected. However, I got the following error when I try to include the setRequireLoginAlert(true) function in requireAuth.js.
Warning: Cannot update a component (`AuthProvider`) while rendering a different component (`RequireAuth`). To locate the bad setState() call inside `RequireAuth`
My requireAuth.js:
const RequireAuth = () => {
const { auth, setRequireLoginAlert } = useAuth();
const location = useLocation();
return (
auth?.user
? <Outlet />
: <Navigate to='/' state={{ from: location }} replace />
&& setRequireLoginAlert(true)
);
};
export default RequireAuth;
My AuthContext.js
const AuthContext = createContext({});
export const AuthProvider = ({ children }) => {
const [auth, setAuth] = useState({});
const [requireLoginAlert, setRequireLoginAlert] = useState(false);
return (
<AuthContext.Provider value={{ auth, setAuth, requireLoginAlert, setRequireLoginAlert }}>
{children}
</AuthContext.Provider>
);
};
export default AuthContext;
I have try using useEffect the display the alert whenever the location has changed, but in this way the alert will keep popping out whenever I go to other pages, which is not ideal. I need a trigger to change the requireLoginAlert state after the user was redirected without error.
Feel free the drop a comment if you have any idea. Thanks a lot.
I think you were on the good way with the useEffect approach.
Indeed you have use the location in the dependencies array, but you also need to include a condition based on the current page so that the requireLoginAlert is not called every time
Have you tried something like the following piece of code ?
useEffect(
() => auth?.user && location === "/" && setRequireLoginAlert(true),
[auth?.user, location]
);

How to get updated state from react parent component

I have the following structure
=Parent Component (with a list as a state)
== Sub Comp list={list})
=== Sub Comp2 list={list}
==== Node list={list}
export const Node = (props) => {
// some state setup
const [checked, setChecked] = useState(false)
const handleCheckbox = () => {
if(!checked){
//Checkbox checked, add this Node to list in Parent Component
props.updateList(someLabel)
}
else{
props.removeFromList(someLabel)
}
}
return(
<TreeItem
icon = {<Checkbox checked={checked} onChange={handleCheckbox}}
>
{expanded && !fetching ? childNodes : <TreeItem label="reached end" />
</TreeItem>
)
}
Now this work the way that I intended, but the problem is since it's a TreeView if I collapse and expand one of the parent nodes, I lose the checked value.
To fix this I put
useEffect(() => {
var inList = props.list.find(function(item) { return item.name === label} ) !=== undefined
if(inList){ setChecked(true)} else{setChecked(false)}
}, [])
Now the above works as intended, but only if I go to the next page on my form and then come back. The problem is that when the list is updated, the useEffect is still using the old state of the list.
How do I force useEffect to use the most updated state, or force the state to update since it's asynchronous?
You have a few options depending on your circumstance. If you just want the latest parent state in the child component you can pass the parent's state as well as the updater as a prop to the child component as below.
Parent:
const ParentComponent = () => {
const [checked, setChecked] = React.useState(false);
return (
<ChildComponent checked={checked} setChecked={setChecked} />
)
}
Child:
const ChildComponent = ({checked, setChecked}) => {
return (
<>
<p>{checked}</p>
<button onClick={() =>setChecked(!checked)}>Button!</button>
</>
)
}
If you are trying to use the state information in parallel components or if you are passing state more than one or two levels down, consider using a context.

What is going wrong when I pass a boolean from a child to its parent component using React Hooks?

I'm currently trying to revise the dropdown menu component on my Gatsby site so that it reports a boolean to its parent component, a navbar. I plan on using that boolean to trigger some conditional CSS in Emotion.
The boolean isOpen reports if the dropdown menu is open or not, so true means it's open, and false means it's not.
As of now, I'm using React Hooks to pass that data from the child to the parent component. It seems like I'm successfully passing data, but when I click the dropdown menu, it sends both a true and a false boolean value in rapid succession, even as the menu remains open.
How do I revise this code so that isOpen in the child component is correctly reported to the parent component?
import React, { useState, useEffect } from "react"
const Child = ({ isExpanded }) => {
const [expandState, setExpandState] = useState(false)
useEffect(() => {
setExpandState(isOpen)
isExpanded(expandState)
})
return(
<dropdownWrapper>
<button
{...isExpanded}
/>
{isOpen && (
<Menu>
//menu items go here
</Menu>
)}
</dropdownWrapper>
)
}
const Parent = () => {
const [expandState, setExpandState] = useState(false)
const onExpand = (checkExpand) => {
setExpandState(checkExpand)
}
return(
<Dropdown
isExpanded={onExpand}
onClick={console.log(expandState)}
/>
)
}
Figured this one out myself. Parent needed a useEffect to register the incoming boolean.
Fixed code for the parent:
const Parent = () => {
const [expandState, setExpandState] = useState(false)
const onExpand = (checkExpand) => {
setExpandState(checkExpand)
}
useEffect(() => {
onExpand(expandState)
})
return(
<Dropdown
isExpanded={onExpand}
onClick={console.log(expandState)}
/>
)
}

What is the usage of material-ui useScrollTrigger with child target ref?

I am trying to use material-ui's useScrollTrigger with a target other than window like so:
export default props => {
let contentRef = React.createRef();
const scrollTrigger = useScrollTrigger({ target: contentRef.current });
return (
<React.Fragment>
<CustomHeader shrink={scrollTrigger} />
<div ref={contentRef}>
{props.children}
</div>
</React.Fragment>
);
};
This causes an error to be thrown because contentRef.current is null when useScrollTrigger is called. What is the proper usage of this utility with child elements as a target?
You were right, since the first time the component renders contentRef.current is null and so an error will be thrown.
We need two things a reference to the target Element, and a way to re-render so our scrollTrigger is calculated again. in other words we need a state.
The following code will work
export default props => {
// please keep it undefined here to not make useScrollTrigger throw an error on first render
const [scrollTarget, setScrollTarget] = useState(undefined)
const scrollTrigger = useScrollTrigger({ target: scrollTarget });
return (
<React.Fragment>
<CustomHeader shrink={scrollTrigger} />
<div ref={node => {
if (node) {
setScrollTarget(node);
}
}}>
{props.children}
</div>
</React.Fragment>
);
};
For you typescript users that feel left out, let me save you several hours of your lives figuring out the appropriate typing:
const [scrollTarget, setScrollTarget] = useState<Node | Window | undefined>()
const scrollTrigger = useScrollTrigger({ target: scrollTarget });
I would try something like this:
const scrollTrigger = useScrollTrigger(() => {{ target: contentRef.current }});

Categories

Resources