Toggle two components using react hooks - reactjs

I have two instances of the same component. The component can be opened or closed.
Component has const [isOpen, setIsOpen] = useState(false)
which is being set using useCallback
const openComponent = useCallback(() => {
setIsOpen(true)
}, [])
const closeComponent = useCallback(() => {
setIsOpen(false)
}, [])
Components are within their own context. But I have trouble even coming up with an idea how to handle via parent, when one instance opens, close the other conundrum.
Passing state to parent and closing all of them before opening a new one is not an option.
Any ideas?

You should lift the state of these two components (open or false) to parent, so parent can have this logic (only one open at a time).
Parent should pass through props the open state to each children, along with a reference to a changeStateHandler(boolean) which children could call to notify the parent that they have been toggled.
So in parent you would have :
const [firstChildState, setFirstChildState] = useState(false);
const [secondChildState, setSecondChildState] = useState(false);
const handlStateChanged = () => {
// toggle both states
}
[...]
<Child isOpen={firstChildState} onStateChange={handlStateChanged} />
<Child isOpen={secondChildState} onStateChange={handlStateChanged} />
In children :
// calls props.onStateChange on toggle, use props.isOpen to display proper state

Related

Reset state value in functional component in React native

I have created cart functionality and I have managed this using useState hook. though I can change quantity of items but if I close the Modal, it can't reset. (I want quantity 0 on closing a Modal).
let [addonData, setAddonData] = useState([]);
Here if i open modal again after closing I get previous values, but I want 0 instead of that. Please check attached image for your reference.
I have tried but I got this error: too many re-renders. react limits the number of renders to prevent an infinite loop.
So, If anyone have idea then please let me know.
Thanks in advance :)
For doing that, you'll probably need to fire a reset function on modal dismiss, but what that means exactly?!
Parent Component
Imagine that we have this modal which receives a state from the parent component.
const MyParentComponent = () => {
const [opened, setOpened] = useState(false);
const [state, setState] = useState([])
const onOpen = () => {
setOpened(true)
}
const onDismiss = () => {
setOpened(false)
setState([])
}
return (
<>
{myComponent()}
<Modal isOpened={opened} state={state} setState={setState} onDismiss={onDismiss} />
</>
)
}
What is happening here is that we pass a state from the Parent to the Modal Component, but every time we close the modal we reset the state we were passing to it.
This solve your problem, but check this section of the beta.reactjs docs

React state updated in child component but not in parent

I am aware that changing the state is asynchronous and requires using the previous state.
But lately I have encountered a strange problem. After the state is modified the change is visible in the child (grand-child in fact) component but during callback to the parent the change is not visible there.
Here is the pseudocode
const Parent = () => {
const [items, setItems] = React.useState([]);
... some method
const changeState = (...) => {
setItems( items => [...items, {id: 100, value: "Test"}]);
}
const callback = (id) => {
const v = items.find( i => i.id == id );
// ERROR: v is undefined
}
return(<Child items={items} callback={callback} />);
}
const Child = ({items, callback) => {
...
const onClick = () => {
callback(100);
}
...
}
The scenario is as follows:
The changeState method is called in Parent
the Child is rendered with the correct data
when the Item is rendered it has a onClick method with it's id as a param
onClick in Child is called
callback is called in the parent with id (which had been passed from Parent to Child)
there is an error because ... id does not exist in the Parent's state.
Any ideas where to look for the source of the problem?
I fully agree with CHess
Your code semms to be correct in my eyes. And as you pointed out, the parent component must have contained the valid itemsstate at some point. Otherwise it could not update it in the child component.
One thing I thought of:
Could it be that the setItems function is called from somewhere else? This could explain the strange behaviour.
I think a more complete code example could help getting to the bottom of this.

Using react Modal that can be triggered from multiple pages react/next.js

I have a Modal that is triggered but many different buttons across different components. I Have been able to get it working on the pages by passing the variables in the Layout, for example
const index = () => {
const [show, setShow] = useState(false)
const handleShow = () => { setShow(true) }
const handleClose = () => { setShow(false) }
<Layout pageTitle={pageTitle} metaUrl={metaUrl} show={show} onHide={handleHide}>
...
</Layout>
}
And using these variables to pass to the Modal component from the Layout which triggers the modal just fine. But it only works with pages because I can pass them in the layout, however I have buttons in the Navbar and Footer that are being imported into the Layout and not using the Layout so im not sure how to actually pass the variables to the modal from those.
Any Help would be amazing!
for this situation, I think the perfect solution would be to use React Context you could separate it in its own hook and then use this hook when needed across the app.
first, you will need to create the context
const ModalContext = React.createContext()
// give it a display name so it would be easier to debug
ModalContext.dispalyName = 'ModalContext'
then you need need to create the Provider for this context and put it higher in your app tree because you can only use this context under its Provider since you are using Next.js I would suggest doing it in the _app.js or just around your main app component.
const ModalContextProvider = ({children}) => {
const [isOpend, setIsOpend] = React.useState(false);
// handle any additional data needed with useState/useReducer
const [title, setTitle] = React.useState('default title');
const value = {setIsOpened, setTitle};
return <ModalContext.Provider value={value}>
<ModalComponent isOpend={isOpend} title={title}/>
{children}
</ModalContext.Provider>
}
so after creating the ModalContext component and putting it above your main app component you can extract this context functionality in its own hook like this
function useModalContext() {
const context = React.useContext(ModalContext);
// if context is undefined this means it was used outside of its provider
// you can throw an error telling that to your fellow developers
if(!context) {
throw new Error('useModalContext must be used under <ModalContextProvider/>');
}
return context;
}
then in any component, you can use this hook like this
const {setIsOpened, setTitle} = useModalContext();
const handleOpenModal() {
setIsOpened(true);
setTitle('Modal Title');
}
return <button onClick={handleOpenModal}>Show Modal</button>
and you can use this any place in the app if it was under the context provider component.

React access state after render with functional components

I'm a bit of a newbie with React functional components, I have a child and parent components with some state that gets updated with useEffect, which state apparently resets back to its initial values after render.
Parent has a list of users it passes to its child:
Parent:
const Parent = () => {
const [users, setUsers] = useState([])
const getUsers = () => {
setUsers(["pedro", "juan"])
}
useEffect(() => {
getUsers()
}, []);
return <div>
<Child users={users} />
}
Child:
const Child = () => {
const [users, setUsers] = useState([])
useEffect(() => {
setUsers(props.users)
}, [[...props.users]]);
}
If I for any reason try to access state (users) from either my child or parent components I get my initial value, which is an empty array, not my updated value from getUsers(), generally with a Parent Class component I'd have no trouble accessing that info, but it seems like functional components behave diffently? or is it caused by the useEffect? generally I'd use a class component for the parent but some libraries I use rely on Hooks, so I'm kind of forced to use functional components.
There are a couple of mistakes the way you are trying to access data and passing that data.
You should adapt to the concept of lifting up state, which means that if you have users being passed to your Child component, make sure that all the logic regarding adding or removing or updating the users stays inside the Parent function and the Child component is responsible only for displaying the list of users.
Here is a code sandbox inspired by the code you have shared above. I hope this answers your question, do let me know if otherwise.
Also sharing the code below.
import React, { useState } from "react";
export default function Parent() {
const [users, setUsers] = useState([]);
let [userNumber, setUserNumber] = useState(1); // only for distinctive users,
//can be ignored for regular implementation
const setRandomUsers = () => {
let newUser = {};
newUser.name = `user ${userNumber}`;
setUsers([...users, newUser]);
setUserNumber(++userNumber);
};
return (
<div className="App">
<button onClick={setRandomUsers}>Add New User</button>
<Child users={users} />
</div>
);
}
const Child = props => {
return (
props.users &&
props.users.map((user, index) => <div key={index}>{user.name}</div>)
);
};
it doesnt make sense to me that at Child you do const [users, setUsers] = useState([]). why dont you pass down users and setUsers through props? your child's setUser will update only its local users' state value, not parent's. overall, duplicating parent state all around its children is not good, you better consume it and updating it through props.
also, once you do [[...props.users]], you are creating a new array reference every update, so your function at useEffect will run on every update no matter what. useEffect doesnt do deep compare for arrays/objects. you better do [props.users].

How to set a child component without rendering it in the parent component?

My parent component retrieves a list and inflates it as a table.
I want the row click cb (onRowClick) to populate another component with some data from the parent component props/state.
I know this can be done using redux. Is there another way to simply set a child component without rendering it and then set its props on the click cb?
The click cb is currently looking like this:
onRowClick={(event) => {
return <ChildComponent data={props.data.data[event.index]} />
}}
This doesn't call the ChildComponent render function since the components aren't connected.
If you dont want to use redux, you can use simple props to pass a function that sets state, and pass the state data to the other component.
const App = () => {
const [apiData, setApiData] = useState([]);
useEffect(() => {
// fetch api here
setApiData();
})
const [data, setData] = useState();
return (
<>
<Component1 tableData={apiData} onRowClick={setdata} />
<ChildComponent data={data}/>
</>
);
}
If the two components are far apart in the tree, then you can use react contexts to pass data between them.

Resources