Trying to change state in Parent from Child component React Hooks - reactjs

I've been looking through related posts and tried a few solutions but I wasn't able to adapt any of them to my situation.
I am trying to change the state of selectedPin by sending the function and calling it from the child component.
See below my code
Parent component :
export default function Pins(data) {
const chargers = data.data;
const [selectedPin, setSelectedPin] = useState(null);
const handleClose = () => {
setSelectedPin(null);
};
return (
<>
{chargers.map((charger, index) => (
<Marker
key={index}
longitude={charger.AddressInfo.Longitude}
latitude={charger.AddressInfo.Latitude}
>
<Button
onClick={(e) => {
e.preventDefault();
setSelectedPin(charger);
}}
>
<img src="./assets/pin.svg" alt="marker pin" />
</Button>
</Marker>
))}
{selectedPin ? (
<PopupInfo selectedPin={selectedPin} handleClose={handleClose} />
) : null}
</>
);
}
Child component :
export default function PopupInfo(selectedPin, { handleClose }) {
const pin = selectedPin.selectedPin;
console.log(pin);
console.log(handleClose);
return (
<Popup
latitude={pin.AddressInfo.Latitude}
longitude={pin.AddressInfo.Longitude}
onClose={handleClose}
>
<div>Popup Info</div>
</Popup>
);
}
When console logging handleClose in child component I am getting undefined.

I think you should pass them both down as named props:
export default function PopupInfo({ selectedPin, handleClose })
or do this:
export default function PopupInfo(props) and then use props.selectedPin and props.handleClose.

It won't work as React is Uni-directional data flow model - it'a core feature of React.
State of Parent can't be modified by Child. You have to come-up with an idea to handle the data in Parent itself or pass that as prop to bypass(not recommended)
If you are interested to know more : https://www.youtube.com/watch?v=hO8u07-WTOk

Related

Close modal if another one is opened

Im trying to create a react Modal component from scratch. I would like to add the functionality of closing the modal when another one is opened.
I know the logic how to solve(i think i know), but cant implement it. My approach would be using context, where i store the current modal opened(currentModal) and if another one is opened then it would check if there is a currentModal and if so it would close it.
So far i have the modal component:
export function Modal({title, isOpen, children, onClose}){
return(
createPortal(
<trds-modal class={isOpen ? 'opened': ''} onClick={onClose}>
<trds-modal_container onClick={e => e.stopPropagation()}>
<trds-modal_header>
<h2>{title}</h2>
<Icon icon="x" onClick={onClose} />
</trds-modal_header>
<trds-modal_body>
{children}
</trds-modal_body>
</trds-modal_container>
</trds-modal>, document.body)
)
}
i figured it out.
Created a context provider where i store the id of the current modal opened.
export function ModalContextProvider({children}){
const [currentModalId, setCurrentModalId] = useState(null);
return(
<modalContext.Provider value={[currentModalId, setCurrentModalId]}>
{children}
</modalContext.Provider>
)
}
then in the modal component i generate a uniqe id and set the context's currentModalId to that. And if the currentModalId changes then the modal checks if that equals to the modalId. If not, it calls the onClose function.
export function Modal({title, isOpen, children, onClose}){
const modalId = useMemo(() => generateId(), []);
const [currentModalId, setCurrentModalId] = useContext(modalContext);
useEffect(() => {
if(isOpen){
setCurrentModalId(modalId);
}
}, [isOpen, setCurrentModalId, modalId]);
useEffect(() => {
if(currentModalId !== modalId) onClose();
}, [currentModalId, modalId, onClose]);
return(
createPortal(
<trds-modal class={isOpen ? 'opened': ''} onClick={onClose}>
<trds-modal_container onClick={e => e.stopPropagation()}>
<trds-modal_header>
<h2>{title}</h2>
<Icon icon="x" onClick={onClose} />
</trds-modal_header>
<trds-modal_body>
{children}
</trds-modal_body>
</trds-modal_container>
</trds-modal>, document.body)
)
}
I hope it help you as i couldn't find an approach to this problem. (maybe its not even an existing problem :D)

'Maximum update depth exceeded' error when trying to use custom function passed as props in 'onClick' of button

I am trying to use a pass a function as props declared in App.js to handle a state variable in App.js in order to add items to a cart component but get this error as soon as I add the function to the onClick field of the "Add" button in my product component(at the end of post):
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
My App.js looks like this:
const App = () => {
const [cartItems, setCartItems] = useState([]);
const handleAddProduct = (product) => {
//some logic to add product to cartItems list here
}
return(
<Box className="App">
<AppRoutes handleAddProduct={handleAddProduct} cartItems={cartItems}/>
</Box>
);
}
Im passing the function and the state variable as props to my routes component so I can access it in my Products page:
const AppRoutes = ({ handleAddProduct, cartItems }) => {
return (
<Routes>
<Route exact path="/alldrip" element={<ProductsPage handleAddProduct={handleAddProduct} />} />
</Routes>
)}
And in my products page the function gets passed as props again to another component:
const ProductsPage = ({ handleAddProduct }) => {
return (
<AllProducts handleAddProduct={handleAddProduct} />
)}
And then I pass the function one last time in AllProducts to an individual Product component: ( I seperated the components this way so it is easier for me to read)
const AllProducts = ({ handleAddProduct }) => {
return (
{products.map(product => {
return (
<Product handleAddProduct={handleAddProduct} product={product} />
)
})}
)}
The products load fine but the app crashes with the error as soon as I add the function to the "Onclick" of the add to cart button:
const Product = ({ handleAddProduct, product }) => {
return (
<Heading>{product.name}</Heading>
<Text >{product.material}</Text>
<Text>{product.price} </Text>
<Button onClick={handleAddProduct(product)} >Add to Cart</Button>
)}
If I remove the function the app stays alive !
I'm not understanding why the error states setState is getting called repeatedly.
onClick={handleAddProduct(product)}
This should probably only be
onClick={() => handleAddProduct(product)}
otherwise you're calling the handleAddProduct method on render directly and not on click.
You call your handleAddProduct directly in your jsx that re-renders the component that directly call handleAddProduct and so on ...
You can try
onClick={() => handleAddProduct(product)}
A better approach is to avoid anonymous functions so in your Product component
const Product = ({ handleAddProduct, product }) => {
const onAddProduct = (product) => {
handleAddProduct(product)
}
return (
...
<Button onClick={onAddProduct}>Add to Cart</Button>
)
)}

How can i get multiple recoil atoms when using components multiple?

In some components i am using recoil atoms to manage my states. One example is my modal component. It look something like this:
export const modalState = atom({
key: "modalState",
default: false
})
export const useToggleModalState = () => {
const setModalState = useSetRecoilState(modalState)
return (state, callback) => {
setModalState(state)
if (callback) {
callback()
}
}
}
export const Modal = (props) => {
<Transition show={modalState}>
<Dialog>
<Dialog.Title>My Modal Headline</Dialog.title>
<Dialog.Description>My Modal Description</Dialog.Description>
</Dialog>
</Transition>
}
and i am using this modal like this:
const toggleModalState = useToggleModalState();
return (
<Modal />
<Button text="Close Modal" onClick={() => toggleModalState(false)} />
)
however, if I use the modal multiple times, the modal is automatically duplicated, but I still use only one state for all modals. Of course, I don't want that. If I use a component multiple times, I want the state to be created multiple times, so that I can change the state of each component individually.
I have read that there are also atomFamilys. Could I use these at this point? What should my code look like then? Can multiple atoms also be created automatically if I use a component multiple times?
Why do you want to use recoil for that? The state of the modal is tied to the modal itself, it doesn't need to access global state.
You can just use useState to determine if you want to show a modal within a component:
export const Modal = (props) => {
<Transition show={props.show}>
<Dialog>
<Dialog.Title>My Modal Headline</Dialog.title>
<Dialog.Description>My Modal Description</Dialog.Description>
</Dialog>
</Transition>
}
export const ComponentWithModal = () => {
const [showModal, setShowModal] = useState(false);
return (
<>
<Modal show={showModal}/>
<Button text="Open Modal" onClick={() => setShowModal(true)} />
<Button text="Close Modal" onClick={() => setShowModal(false)} />
</>
)
}

Handle children.props

so, in ParentComponent I have
<Component dropDownContent={<DropDownContent content={array} onSelect={handleSelect} />} />
my DropDownContent looks something like this
return (<ul>
{content.map((item) =>
{ return <li><button onClick={()=> onSelect(array.id)}>{array.name}</button></li>}
)}
</ul>)
Can I some how do something with the onSelect inside Component even if I add DropDownContent as a prop to Component?
Thanks :)
What I understand from your question is that you want to pass a function from the parent component to the child component. And when a local function inside child component is clicked, you want to call that passed function. if yes, then this is your solution:
Note: I do not know exactly what code you wrote and what your component consists of. So I will give you the answer by giving a simple example to fully understand the solution.
In your parent component:
export const ParentComponent = props => {
const handleSelect = () => console.log(`do something here`);
return (
<Component
dropDownContent={
<DropDownContent
content={array}
onSelect={() => handleSelect()}
/>}
/>
)
}
And in your child component you need to receive the passed function like below:
export const ChildComponent = props => {
const handlePassedFunction = () => props.onSelect?.();
return (<ul>
{content.map((item) => {
return <li>
<button onClick={() => handlePassedFunction(array.id)}>{array.name}</button>
</li>
}
)}
</ul>)
}

How to pass state from parent to child in react?

How do I pass a state attribute from parent to child? In the following implementation, the Dropdown component has a state "isActive" and I want to access it in the Button component to attach propper styling to it. The Dropdown has to generic as it is supposed to take different sorts of buttons.
<Dropdown items="...">
<Button active ="false" />
</Dropdown>
Dropdwon.js
...
constructor(props){
super(props)
this.state = {
isActive: true,
}
}
render (){
return (
<div className={styles.toggle} onClick={(event) => this.showMenu(event)}>
{this.props.children} /* want to set active prop for the child button here */
</div>
);
}
...
You have two possibilities:
Lift your Dropdown state and keep it in its parent component;
Use useContext hook;
The first approach would be better, but it may not be good for your application (I cannot know that). Let me make an example for both cases.
This is an example where I've lifted the isActive state to the parent component.
const ParentComponent = () => {
const [isActive, setIsActive] = useState(false);
handleIsActiveChange = (newValue) => {
setIsActive(newValue);
}
<Dropdown isActive={isActive} setIsActive={handleIsActiveChange}>
<Button isActive={isActive} />
</Dropdown>
}
const Dropdown = props => {
// You can use `props.isActive` to know whether the dropdown is active or not.
// You can use `props.handleIsActiveChange` to update the `isActive` state.
}
const Button = props => {
// You can use `props.isActive` to know whether the dropdown is active or not.
}
Instead, this exploits the useContext API:
const dropdownContext = React.createContext(null);
const Dropdown = props => {
const [isActive, setIsActive] = useState(false);
return (
<dropdownContext.Provider value={{ isActive }}>
{props.children}
</dropdownContext.Provider>
);
}
const Button = props => {
const dropdownCtx = React.useContext(dropdownContext);
// You can use `dropdownCtx.isActive` to know whether the dropdown is active or not.
}
Aside from the answer I linked, there might be another way of achieving this that I didn't see mentioned.
You can send a function as a children element of your dropdown which will take isActive as a variable :
<Dropdown items="...">
{isActive => <Button active={isActive} />}
</Dropdown>
Then, is the render function, simply call the function and send your state value as a parameter :
render(){
return (
<div className={styles.toggle} onClick={(event) => this.showMenu(event)}>
{this.props.children(this.state.isActive)}
</div>
);
}
<Dropdown >
<Button isActive={this.state.isActive} />
</Dropdown>
In your button get it with this.props.isActive

Resources