Given the following code:
export default function CrearContacto({setOpening}) {
const [open, setOpen] = useState(false);
setOpen(setOpening);
useEffect(() => {
console.log(setOpening);
setOpen(setOpening);
}, [setOpening]);
const handleClose = () =>{setOpening=false};
return (
<div>
<Dialog open={open} onClose={handleClose}>
//code continues
I'm getting a Too many re-renders error, however I'm unable so see where is the loop here. {setOpening} is being used in a button in another component, when clicked, true is sent. setOpen sets new open state as true, so Dialog can pop-up. At the same time useEffect is watching setOpening for changes (probably this is also wrong, I'm trying to make this work without success). If closed, handleClose is triggered and now setOpening equals false, so useEffect detects the change and executes setOpen to false. I don't see the loop, but also the code isn't working as intended.
Edit - Added parent relative code
function Agenda() {
const MiContexto = useOutletContext();
return (<CrearContacto setOpening={MiContexto.modalState}/>
//code continues
MainLayout class:
import Navbar from "./Navbar"
import Topbar from "./Topbar"
import React, {useReducer} from "react";
import { Outlet, useOutletContext } from "react-router-dom";
export const MainContexto = React.createContext({modalState:false});
const initialState=false;
const estadosPosiblesModal = (state, action) =>{
switch(action){
case false:
return false;
case true:
return true;
default:
return state;
}
}
const MainLayout = ()=> {
const [EstadoModal, dispatch] = useReducer(estadosPosiblesModal,initialState);
return (
<MainContexto.Provider value={{modalState:EstadoModal,modalDispatch:dispatch}}>
<div className="mainlayout">
<Navbar/>
<main className="mainlayout_topPlusContent">
<Topbar className="TopBar" prueba={EstadoModal}/>
<Outlet context={{modalState:EstadoModal,modalDispatch:dispatch}}/>
</main>
</div>
</MainContexto.Provider>
);
};
export default MainLayout;
I had to use simultaneously a custom context and the Router outlet context as my context wasn't going through the outlet.
Topbar contains:
const micontext = useContext(MainContexto);
...
<Button variant="contained" onClick={()=>micontext.modalDispatch(true)}>
Your problem is you call setOpen(setOpening) on the component level CrearContacto which is causing the infinite re-renderings on that component. You can imagine this cycle: state update (setOpen(setOpening)) > re-rendering > call state update again > ...
And another problem is you cannot set setOpening=false directly, it needs to be set with a state setter from the parent component.
export default function CrearContacto({setOpening}) {
const [open, setOpen] = useState(setOpening);
//setOpen(setOpening); //remove this part
useEffect(() => {
setOpen(setOpening)
},[setOpening])
const handleClose = () =>{setOpen(false)}; //set `open` state internally instead
return (
<div>
<Dialog open={open} onClose={handleClose}>
</div>)
}
If you want to update the parent component's state, you should pass the state setter and state value to your child component which is CrearContacto too
I'd assume that you have this state on the parent component
const [opening, setOpening] = useState(false);
Note that setOpening is following the naming convention of the state setter, so I'd prefer to do it this way instead
You can try to modify your component like below
export default function CrearContacto({setOpening, opening}) {
const [open, setOpen] = useState(opening);
//setOpen(setOpening); //remove this part
useEffect(() => {
setOpen(opening)
},[opening])
const handleClose = () =>{setOpening(false)};
return (
<div>
<Dialog open={open} onClose={handleClose}>
</div>)
}
If you only use open state for Dialog, you can remove that state declaration too
export default function CrearContacto({setOpening, opening}) {
const handleClose = () =>{setOpening(false)};
return (
<div>
<Dialog open={opening} onClose={handleClose}>
</div>)
}
To expand my comment:
From inside (or a child) component, you can change the open value by using the state, so call the setOpen function.
From a parent component you can change the value of open by changing the prop setOpening (rename this prop as it sounds like it's a function), so you would have:
export default function CrearContacto({setOpening}) {
const [open, setOpen] = useState(setOpening);
useEffect(() => {
console.log(setOpening);
setOpen(setOpening);
}, [setOpening]);
const handleClose = () =>{setOpen(false)};
return (
<div>
<Dialog open={open} onClose={handleClose}>
Related
I am building this project to try and improve my understanding of react :), so I am a n00b and therefore still learning the ropes of extracting components, states, props etc =)
I have a child Component DescriptionDiv, its parent component is PlusContent and finally the parent component is PlusContentHolder. The user types some input into the DescriptionDiv which then, using a props/callback passes the user input to the PlusContent.
My question/problem is: after setting useState() in the PlusContent component, I am after a button click in the PlusContentHolder component, returned with an undefined in the console.log.
How come I cannot read the useState() in the next parent component, the PlusContentHolder?
I know that useState() is async so you cannot straight up call the value of the state in the PlusContent component, but shouldn't the state value be available in the PlusContentHolder component?
below is my code for the DescriptionDiv
import './DescriptionDiv.css';
const DescriptionDiv = props => {
const onDescriptionChangeHandler = (event) => {
props.descriptionPointer(event.target.value);
}
return (
<div className='description'>
<label>
<p>Description:</p>
<input onChange={onDescriptionChangeHandler} type='text'></input>
</label>
</div>);
}
export default DescriptionDiv;
Next the code for the PlusContent comp
import React, { useState } from "react";
import DescriptionDiv from "./div/DescriptionDiv";
import ImgDiv from "./div/ImgDiv";
import "./PlusContent.css";
import OrientationDiv from "./div/OrientationDiv";
const PlusContent = (props) => {
const [classes, setClasses] = useState("half");
const [content, setContent] = useState();
const [plusContent, setPlusContent] = useState({
orientation: "left",
img: "",
description: "",
});
const onOrientationChangeHandler = (orientationContent) => {
if (orientationContent == "left") {
setClasses("half left");
}
if (orientationContent == "right") {
setClasses("half right");
}
if (orientationContent == "center") {
setClasses("half center");
}
props.orientationInfo(orientationContent);
};
const onDescriptionContentHandler = (descriptionContent) => {
props.descriptionInfo(setPlusContent(descriptionContent));
console.log(descriptionContent)
};
const onImageChangeHandler = (imageContent) => {
props.imageInfo(imageContent);
setContent(
<>
<OrientationDiv
orientationPointer={onOrientationChangeHandler}
orientationName={props.orientationName}
/> {/*
<AltDiv altPointer={onAltDivContentHandler} />
<TitleDiv titlePointer={onTitleDivContentHandler} /> */}
<DescriptionDiv descriptionPointer={onDescriptionContentHandler} />
</>
);
};
return (
<div className={classes}>
<ImgDiv imageChangeExecutor={onImageChangeHandler} />
{content}
</div>
);
};
export default PlusContent;
and lastly the PlusContentHolder
import PlusContent from "../PlusContent";
import React, { useState } from "react";
const PlusContentHolder = (props) => {
const onClickHandler = (t) => {
t.preventDefault();
descriptionInfoHandler();
};
const descriptionInfoHandler = (x) => {
console.log(x) // this console.log(x) returns and undefined
};
return (
<div>
{props.contentAmountPointer.map((content) => (
<PlusContent
orientationInfo={orientationInfoHandler}
imageInfo={imageInfoHandler}
descriptionInfo={descriptionInfoHandler}
key={content}
orientationName={content}
/>
))}
<button onClick={onClickHandler}>Generate Plus Content</button>
</div>
);
};
export default PlusContentHolder;
The reason why the descriptionInfoHandler() function call prints undefined in its console.log() statement when you click the button, is because you never provide an argument to it when you call it from the onClickHandler function.
I think that it will print the description when you type it, however. And I believe the problem is that you need to save the state in the PlusContentHolder module as well.
I would probably add a const [content, setContent] = useState() in the PlusContentHolder component, and make sure to call setContent(x) in the descriptionInfoHandler function in PlusContentHolder.
Otherwise, the state will not be present in the PlusContentHolder component when you click the button.
You need to only maintain a single state in the PlusContentHolder for orientation.
Here's a sample implementation of your use case
import React, { useState } from 'react';
const PlusContentHolder = () => {
const [orientatation, setOrientation] = useState('');
const orientationInfoHandler = (x) => {
setOrientation(x);
};
const generateOrientation = () => {
console.log('orientatation', orientatation);
};
return (
<>
<PlusContent orientationInfo={orientationInfoHandler} />
<button onClick={generateOrientation}>generate</button>
</>
);
};
const PlusContent = ({ orientationInfo }) => {
const onDescriptionContentHandler = (value) => {
// your custom implementation here,
orientationInfo(value);
};
return <DescriptionDiv descriptionPointer={onDescriptionContentHandler} />;
};
const DescriptionDiv = ({ descriptionPointer }) => {
const handleChange = (e) => {
descriptionPointer(e.target.value);
};
return <input type="text" onChange={handleChange} />;
};
I would suggest to maintain the orientation in redux so that its easier to update from the application.
SetState functions do not return anything. In the code below, you're passing undefined to props.descriptionInfo
const onDescriptionContentHandler = (descriptionContent) => {
props.descriptionInfo(setPlusContent(descriptionContent));
};
This shows a misunderstanding of the use of state. Make sure you're reading about "lifting state" in the docs.
You're also declaring needless functions, e.g. onDescriptionContentHandler in your PlusContent. The PlusContent component could just pass the descriptionInfoHandler from PlusContentHolder prop directly down to DescriptionDiv, since onDescriptionContentHandler doesn't do anything except invoke descriptionInfoHandler.
You may want to consider restructuring your app so plusContent state is maintained in PlusContentHolder, and pass that state down as props. That state would get updated when DescriptionDiv invokes descriptionInfoHandler. It'd subsequently pass the updated state down as props to PlusContent.
See my suggested flowchart.
I have a component defined as
export default function MyComp({
...someprops
}) {
const [data, setData] = useState([]);
const searchRef = useRef();
return (
<Box>
{!showEmptyState ? (
<LzSearch
onUpdate={(items) => setData(items)}
ref={searchRef}
/>
) : (
<Box />
)}
</Box>
);
}
Where as LzSearch is defined as
const LzSearch = forwardRef((props, ref) => {
const {
...rest
} = props;
const classes = useStyles();
const hashData = {};
console.log(hashData);
function updateHashData() {
// Function is called at some point after getting data from API
setHashData(...);
onUpdate(...)
}
return (
<Box>
{`Some components`}
</Box>
);
});
export default memo(LzSearch);
After calling onUpdate(), my main component is updated however it then re-render my LzSearch component and resetting the hashData. I have added memo however its doing the same thing.
How can I avoid re rendering.
This has nothing to do with the ref. LzSearch is rerendering because the onUpdate prop is changing. MyComp will need to use useCallback to keep the onUpdate function the same between renders:
export default function MyComp({
...someprops
}) {
const [data, setData] = useState([]);
const searchRef = useRef();
const onUpdate = useCallback((items) => {
setData(items);
}, [])
return (
<Box>
{!showEmptyState ? (
<LzSearch
onUpdate={onUpdate}
ref={searchRef}
/>
) : (
<Box />
)}
</Box>
);
}
re-render my LzSearch component and resetting the hashData
Be aware that memo is just meant as a performance optimization tool, not a way to fix bugs. Your component needs to work correctly if it happens to rerender for some reason. memo might stop some renders, but it can't guarantee that the component will never rerender. Examples of things that can cause a rerender even with useMemo include: changing props, changing state, changing context, strict mode's double render feature, concurrent mode aborting and then repeating a portion of the component tree.
I made a context to share the value of the variable "clicked" throughout my nextjs pages, it seems to give no errors but as you can see the variable's value remains FALSE even after the click event. It does not change to TRUE. This is my first time working with context, what am I doing wrong?
I'm using typescript
PS: After the onClick event the log's number shoots up by 3 or 4, is it being executed more than once, but how?
controlsContext.tsx
import { createContext, FC, useState } from "react";
export interface MyContext {
clicked: boolean;
changeClicked?: () => void;
}
const defaultState = {
clicked: false,
}
const ControlContext = createContext<MyContext>(defaultState);
export const ControlProvider: FC = ({ children }) => {
const [clicked, setClicked] = useState(defaultState.clicked);
const changeClicked = () => setClicked(!clicked);
return (
<ControlContext.Provider
value={{
clicked,
changeClicked,
}}
>
{children}
</ControlContext.Provider>
);
};
export default ControlContext;
Model.tsx
import ControlContext from "../contexts/controlsContext";
export default function Model (props:any) {
const group = useRef<THREE.Mesh>(null!)
const {clicked, changeClicked } = useContext(ControlContext);
const handleClick = (e: MouseEvent) => {
//e.preventDefault();
changeClicked();
console.log(clicked);
}
useEffect(() => {
console.log(clicked);
}, [clicked]);
useFrame((state, delta) => (group.current.rotation.y += 0.01));
const model = useGLTF("/scene.gltf ");
return (
<>
<TransformControls enabled={clicked}>
<mesh
ref={group}
{...props}
scale={clicked ? 0.5 : 0.2}
onClick={handleClick}
>
<primitive object={model.scene}/>
</mesh>
</TransformControls>
</>
)
}
_app.tsx
import {ControlProvider} from '../contexts/controlsContext';
function MyApp({ Component, pageProps }: AppProps) {
return (
<ControlProvider>
<Component {...pageProps}
/>
</ControlProvider>
)
}
export default MyApp
Issues
You are not actually invoking the changeClicked callback.
React state updates are asynchronously processed, so you can't log the state being updated in the same callback scope as the enqueued update, it will only ever log the state value from the current render cycle, not what it will be in a subsequent render cycle.
You've listed the changeClicked callback as optional, so Typescript will warn you if you don't use a null-check before calling changeClicked.
Solution
const { clicked, changeClicked } = useContext(ControlContext);
...
<mesh
...
onClick={(event) => {
changeClicked && changeClicked();
}}
>
...
</mesh>
...
Or declare the changeClicked as required in call normally. You are already providing changeClicked as part of the default context value, and you don't conditionally include in in the provider, so there's no need for it to be optional.
export interface MyContext {
clicked: boolean,
changeClicked: () => void
}
...
const { clicked, changeClicked } = useContext(ControlContext);
...
<mesh
...
onClick={(event) => {
changeClicked();
}}
>
...
</mesh>
...
Use an useEffect hook in to log any state updates.
const { clicked, changeClicked } = useContext(ControlContext);
useEffect(() => {
console.log(clicked);
}, [clicked]);
Update
After working with you and your sandbox it needed a few tweaks.
Wrapping the index.tsx JSX code with the ControlProvider provider component so there was a valid context value being provided to the app. The UI here had to be refactored into a React component so it could itself also consume the context value.
It seems there was some issue with the HTML canvas element, or the mesh element that was preventing the Modal component from maintaining a "solid" connection with the React context. It wasn't overtly clear what the issue was here, but passing the context values directly to the Modal component as props resolved the issue with the changeClicked callback becoming undefined.
A few things -
setClicked((prev) => !prev);
instead of
setClicked(!clicked);
As it ensures it's not using stale state. Then you are also doing -
changeClicked
But it should be -
changeClicked();
Lastly, you cannot console.log(clicked) straight after calling the set state function, it will be updated in the next render
There is a page contains a booking list and a popup window(A modal with survey questions). To reduce the impact on booking list loading time, I want to render the modal component after the booking list be completely loaded.
ps.there are network data request in both <BookingList/> and <Modal/>.
How should I do with React?
Thanks for help.
export default function Body() {
return (
<>
<BookingList .../>
<Modal .../>
</>
);
}
You can conditionally render the Modal component after the BookingList fetches the results.
For that, you'd need a state variable in the parent component of BookingList and Modal. Code sandbox https://codesandbox.io/s/romantic-cookies-zrpit
import React, { useEffect, useState, useCallback } from "react";
const BookingList = ({ setLoadedBookingList }) => {
useEffect(() => {
setTimeout(() => {
setLoadedBookingList();
}, 1000);
}, [setLoadedBookingList]);
return <h2>BookingList</h2>;
};
const Modal = () => <h1>Modal</h1>;
export default function App() {
const [loadBookingList, setLoadBookingList] = useState(false);
const setLoadedBookingList = useCallback(() => {
setLoadBookingList(true);
}, []);
return (
<div className="App">
<BookingList setLoadedBookingList={setLoadedBookingList} />
{loadBookingList && <Modal />}
</div>
);
}
You can use react hooks useState() and useEffect():
export default function Body() {
// `isLoaded` is a variable with false as initial value
// `setLoaded` is a method to modify the value of `isLoaded`
const [isLoaded, setLoaded] = React.useState(false)
React.useEffect(() => {
// some method used in loading the resource,
// after completion you can set the isLoaded to true
loadBookingList().then(loaded => setLoaded(true));
},
// second argument to `useEffect` is the dependency which is out `setLoaded` function
[setLoaded]);
return (
<>
// conditional rendering based on `isLoaded`
{isLoaded && <BookingList .../>}
<Modal .../>
</>
);
}
Here is the code , no idea why Mem re-render after set state, as it is a memoized component, or if i wanna remember the component with set state, i should use useRef? that stupid??
const Demo = () => {
console.log("render")
const data = LoadSomeData();
return (<>{data.id}</>)
}
const Mycomp = ({...props}) => {
const [showSearch, setShowSearch] = useState(false);
const Mem = useMemo(() => <Demo />, [props.iwandToReloadData]);
return (
<>
{ showSearch ?
<button onClick={()=>setShowSearch(false)}>Back</button>
:
<>
{Mem}
<button onClick={()=>setShowSearch(true)}>Search</button>
</>
}
</>
)
}
export default Mycomp;
Refer to the comment from Tony Nguyen, it is because i use conditional render for the memorised component, thus it will trigger unmount of the memorised component. Therefore nothing can be memorised.
the solution is using css to hide it instead of not render it for my case