I have a component with two children, one of them is a button that toggles a state (modalVisible) that decides whether the other child, a modal, is visible.
I'm having trouble sharing the on/off state across the parent and the modal child. I tried keeping the state in the parent and then passing it as a prop to the child, but it wasn't rerendering the child everytime the parent state changed.
<CommentsModal visible={modalVisible} />
Inside CommentsModal.js...
import Modal from 'react-native-modal';
...
const CommentsModal = ({visible}) => {
const [modalVisible, setModalVisible] = useState(visible);
...
return <Modal visible={modalVisible} />
}
I considered keeping the state entirely in the parent, without passing it into CommentsModal, like so:
function renderModal() {
if (modalVisible) {
return <CommentsModal visible={true} />
} else {
return <View />
}
}
But I realized that there has to be a state inside CommentsModal because I need an "X" button that toggles the modal off.
I'm not sure what the best way to do this is... I could do redux, but since there is a dynamic number of these modals; I don't want my store to be that complicated. The only way I can think of is to move all of the modal code into the parent component, then they can share states easily, but it seems dirty to me. Does anyone have a solution?
Your intuition to keep the state in the parent component is correct. To implement the x button all you need is to pass a onClose prop to the modal which would be a function that sets modalVisible to false. so you'll end up with something like this:
// parent component
const ParentComponent = () => {
const [modalVisible, setModalVisible] = useState(false);
const openModal = () => setModalVisible(true);
const closeModal = () => setModalVisible(false);
return (
<div>
<CommentsModal visible={modalVisible} onClose={closeModal} />
<button onClick={openModal}>open the modal</button>
<p>other children here...</p>
</div>
)
}
// CommentsModal
const CommentsModal = (props) => (
<Modal visible={props.visible}>
<button onClick={props.onClose}>X</button>
<p>more modal content here...</p>
</Modal>
)
Related
I have a parent component in which I'm struggling to properly open/close the child component (modal). The two code boxes below are simplified examples of my components.
EDIT: Here is a code sandbox with the following code -- there isn't an actual modal, however i've logged all of the stateful values that I assume will have an effect on this problem and you can see how they change/don't change as I hope they would.
Code Sandbox
When the parent component is open, I can click the MenuItem and I can see the state change, however the modal doesn't open unless I close the parent component temporarily and reopen it (then the parent component opens with the modal open already)
When the modal is open, and I try to close by clicking the close button (which has the state changing function from parent inside of the onClick method. this.state.showModal remains true, and doesn't change to false.
If I add a closeModal stateful value to the child component and change it during the close buttons onClick, this.state.showModal still remains true.
Thanks to whoever reaches out, and if you have any clarifying questions feel free to ask!
class Parent extends Component {
constructor(props) {
super(props);
this.showModal = this.showModal.bind(this);
this.closeModal = this.closeModal.bind(this)
this.state = {
showModal: false
};
this.showModal = this.showModal.bind(this)
this.closeModal = this.closeModal.bind(this)
}
showModal() {
this.setState({ showModal: true });
}
closeModal() {
this.setState({ showModal: false });
}
render() {
return (
<MenuItem onClick={this.showModal}>
<ChildComponent
prop1={prop1}
isOpen={this.state.showModal}
closeModal={this.closeModal}
/>
</MenuItem>
)}
const ChildComponent = ({
prop1,
isOpen,
closeModal
}) => {
const [modalOpen, setModalOpen] = useState(isOpen)
useEffect(() => {
setModalOpen(isOpen)
},[isOpen])
console.log('isopen on child', isOpen)
console.log('modalOpen', modalOpen)
return (
<div>
{modalOpen && (
<button
onClick={() => {
setModalOpen(false)
closeModal()
}}
>
{'click to close modal'}
</button>
)}
</div>
)}
)}
I figured out my problem!
In my parent component the onClick handler that sets the modal open wrapped my child component. I needed to remove it and conditionally render it separately like so:
<div>
<div onClick={this.showModal}>{"Click here to open modal"}</div>
{this.state.showModal && (
<ChildComponent
prop1={prop1}
isOpen={this.state.showModal}
closeModal={this.closeModal}
/>
)}
</div>
Am trying to render a new component onclick a button in react js. Am using functional components and I can't handle it. Eg: am in the UserManagement component and on a button click I need to render another component named employee management.
You can conditionally render your component.
Example :
EmployeeManagement.js
const EmployeeManagement = () => {
....
return (
<div>
EmployeeManagement
</div>
);
}
UserManagement.js
const UserManagement = () => {
const [hasRender, setRender] = useState(false);
const onShow = React.useCallback(() => setRender(true), []);
return (
<>
<button onClick={onShow}>Show Employee Management</button>
{hasRender && <EmployeeManagement />}
</>
)
}
One way to do this would be to add a local state in UserManagement,
that holds a boolean value indication whether the component should be hidden or shown.
Then you will have something like:
function UserManagement() {
const [compIsShown, setCompIsShown] = useState(false);
return (
// Whatever else you're rendering.
<button onClick={() => setCompIsShown(true)}>...</button>
{compIsShown && <OtherComp />}
)
}
What will happen is that compIsShown will initialize as false,
so this condition compIsShown && <OtherComp /> will prevent it from rendering.
Then, when you click the button, the state will set, causing a re-render, except now the condition will be true, so <OtherComp> will be rendered.
There are other ways to go about this.
Depends mostly on the use-case.
use a visible state & toggle it in onClick:
const [visible, setVisible] = useState(false)
onClick = () => {setVisible(true)}
then render it like this:
{visible && <EmployeeManagement onClick={onClick} />}
The child component passed to another component is losing value in its status
[environment]:
I have 5 components:
Default,
Wrapper,
ChildrenComponent,
Div1,
Div2
In Default I'm rendering the Wrapper and inside the Wrapper I'm rendering ChildrenComponent, all of this in Default, I'm passing to Wrapper a "props" called "isDiv1", which is also my status in Default, I'm changing its value through a button in Default.
In Wrapper I am performing a logic analyzing if the value in "props.isDiv1" is true, if it is the Div1 component if not, I keep the Div2 Component, and I use the result of this logic to involve the "props.children" in Div1 or Div2 .
In ChildrenComponent I have a status that stores an initial text, and an input where every time it is changed, it changes this initial text to what has been changed.
And in Div1 and Div2 I have a simple div with a different "className" attribute.
import React from "react";
const Div1 = props => <div className="iam-div-1">{props.children}</div>;
const Div2 = props => <div className="iam-div-2">{props.children}</div>;
const Wrapper = props => {
const Div = props.isDiv1 ? Div1 : Div2;
return <Div>{props.children}</Div>;
};
const ChildrenComponent = props => {
const [text, setText] = React.useState("initial");
return <input value={text} onChange={e => setText(e.target.value)} />;
};
export default props => {
const [isDiv1, setIsDiv1] = React.useState(false);
return (
<>
<Wrapper isDiv1={isDiv1}>
<ChildrenComponent />
</Wrapper>
<button onClick={() => setIsDiv1(!isDiv1)}>change div</button>
</>
);
};
[problem]:
The problem is that every time I change the status of ChildrenComponent and click the button in Default, the value in the status of ChildrenComponent is reset.
Below is the link for you to test yourselves:
https://codesandbox.io/s/headless-monad-ymc2i?file=/src/App.js
The issue is that your Wrapper component is mounting a new Div component every time the value of isDiv1 changes. By mounting a new Div component, you are in turn mounting a new ChildComponent, which will always have the initial value.
A component will not stay mounted if it's parent becomes unmounted, nor will that component's props (i.e. React.Children) be preserved. If you think about the component tree like a real tree; a twig will not stay hanging in mid-air when the branch connecting it to the trunk is cut off.
If you want to preserve state when a component is unmounted, then you need to move that state into a higher level of the component tree. In your case it might look something like this:
const ChildrenComponent = props => {
return <input value={props.text} onChange={e => props.setText(e.target.value)} />;
};
export default props => {
const [isDiv1, setIsDiv1] = React.useState(false);
const [text, setText] = React.useState("initial");
return (
<>
<Wrapper isDiv1={isDiv1}>
<ChildrenComponent text={text} setText={setText}/>
</Wrapper>
<button onClick={() => setIsDiv1(!isDiv1)}>change div</button>
</>
);
};
I'm exporting hooks with nested components so that the parent can toggle state of a child. How can I make this toggle work with hooks instead of classic classes or old school functions?
Child Component
export let visible;
export let setVisible = () => {};
export const ToggleSwitch = () => {
const [visible, setVisibile] = useState(false);
return visible && (
<MyComponent />
)
}
Parent
import * as ToggleSwitch from "ToggleSwitch";
export const Parent: React.FC<props> = (props) => {
return (
<div>
<button onClick={() => ToggleSwitch.setVisible(true)} />
</div>
)
}
Error: Linter says [setVisible] is unused variable in the child... (but required in the parent)
You can move visible state to parent like this:
const Child = ({ visible }) => {
return visible && <h2>Child</h2>;
};
const Parent = () => {
const [visible, setVisible] = React.useState(false);
return (
<div>
<h1>Parent</h1>
<Child visible={visible} />
<button onClick={() => setVisible(visible => !visible)}>
Toggle
</button>
</div>
);
};
If you have many child-components you should make more complex logic in setVisible. Put object to useState where properties of that object will be all names(Ids) of child-components
as you know React is one-way data binding so if you wanna pass any props or state you have only one way to do that by passing it from parent to child component and if the logic becomes bigger you have to make it as a global state by using state management library or context API with react hooks use reducer and use effect.
I want to render child component when I click the button
Parents
const Parents:React.FC<PropTypes> = ({inputs}) => {
return(
<div>
<Button onClick={() => <Child props={inputs}/>}
</div>
)}
Child
const Child:React.FC<AnotherPropTypes> = ({props}) => {
// ...
}
I am using React, TypeScript and Material-UI for it.
My question is that, it does not seem like onClick event trigger Child component. How can I run child component when I click the button?
Any help appreciated!
Add state to your component. The click event sets the state, and the state is used to decide whether or not to render extra things.
const Parents:React.FC<PropTypes> = ({inputs}) => {
const [showChild, setShowChild] = useState(false);
return(
<div>
<Button onClick={() => setShowChild(true)} />
{showChild && <Child props={inputs}/>}
</div>
)
}