Calling Child component function from Parent component in React.js - javascript

I am trying to call a function in my child component from a button click event in my parent component.
Parent Component:
class Parent extends Component{
constructor(props){
super(props);
this.state = {
//..
}
}
handleSaveDialog = (handleSaveClick) => {
this.handleSaveClick = handleSaveClick;
}
render(){
return(
<div>
<Button onClick={this.openDialog}>Open Dialog</Button>
<Dialog>
<DialogTitle id="form-dialog-title">Child Dialog</DialogTitle>
<DialogContent>
<Child handleSaveData={this.handleSaveDialog}/>
</DialogContent>
<DialogActions>
<Button onClick={this.handleSaveClick} color="primary">
Save
</Button>
</DialogActions>
</Dialog>
</div>
);
}
}
In the above code, the Parent component renders a Child component modal dialog (based on Material-UI) on click of a button. The Save button, part of the Dialog component in Parent, when clicked should call a save function in the Child component. As you can see, I have passed a callback function handleSaveDialog through the Childcomponent props named handleSaveData. The save button click will call handleSaveClick on the child, once the Child component mounts and passes the callback to the Parent component.
Child Component:
class Child extends Component{
constructor(props){
super(props);
this.state = {
//..
}
}
componentDidMount(){
console.log('mount');
this.props.handleSaveData( () => this.handleSaveClick());
}
handleSaveClick = () => {
console.log('save clicked');
}
render(){
return(
<div>
//..
</div>
);
}
}
In the above code, I am using the accessing the callback function passed by the Parent component props and binding it to the Child component's save fucntion handleSaveClick.
Problem:
When I click the Open Dialog button in Parent, for the first time, the Dialog mounts the Child component. However, the click on Save button does not work (no error). After, closing the dialog, when I reopen the Dialog and click on Save, the handleSaveClick in the Child dialog is triggered and a message is logged in the browser console. Any idea why this works on the second time and not the first time?
Remember, the Child Component is mounted/loaded only when I click the Open Dialog on the Parent component.
References:
https://material-ui.com/components/dialogs/#form-dialogs
Call child method from parent
https://github.com/kriasoft/react-starter-kit/issues/909#issuecomment-390556015

It will not work because if you console log this.handleSaveClick in render function it will be undefined as there is no re-render. So there are 2 ways to go for this:
Saving function in the state. Link: https://codesandbox.io/s/fervent-browser-gw75c
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false
};
}
openDialog = () => {
this.setState(preState => ({
open: !preState.open
}));
};
handleSaveDialog = handleSaveRef => {
this.setState({
handleSaveClick: handleSaveRef
});
};
render() {
console.log("Render", this.handleSaveClick);
return (
<div>
<Button onClick={this.openDialog}>Open Dialog</Button>
<Dialog open={this.state.open}>
<DialogTitle id="form-dialog-title">Child Dialog</DialogTitle>
<DialogContent>
<Child handleSaveData={this.handleSaveDialog} />
</DialogContent>
<DialogActions>
<Button onClick={this.state.handleSaveClick} color="primary">
Save
</Button>
</DialogActions>
</Dialog>
</div>
);
}
class Child extends Component {
componentDidMount() {
console.log("mount");
this.props.handleSaveData(this.handleSaveClick);
}
handleSaveClick = () => {
console.log("save clicked");
};
render() {
return <div>//..</div>;
}
}
Using ref. Link: https://codesandbox.io/s/musing-kalam-nj29n
const childRef = React.createRef();
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false
};
}
openDialog = () => {
this.setState(preState => ({
open: !preState.open
}));
};
handleSaveClick = () => {
if (childRef.current) {
childRef.current.handleSaveClick();
}
};
render() {
return (
<div>
<Button onClick={this.openDialog}>Open Dialog</Button>
<Dialog open={this.state.open}>
<DialogTitle id="form-dialog-title">Child Dialog</DialogTitle>
<DialogContent>
<Child ref={childRef} />
</DialogContent>
<DialogActions>
<Button onClick={this.handleSaveClick} color="primary">
Save
</Button>
</DialogActions>
</Dialog>
</div>
);
}
}
class Child extends Component {
handleSaveClick = () => {
console.log("save clicked");
};
render() {
return <div>//..</div>;
}
}
Using callback to save instance and than use arrow function:
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false
};
}
openDialog = () => {
this.setState(preState => ({
open: !preState.open
}));
};
handleSaveDialog = handleSaveRef => {
this.handleSaveClick = handleSaveRef;
};
render() {
return (
<div>
<Button onClick={this.openDialog}>Open Dialog</Button>
<Dialog open={this.state.open}>
<DialogTitle id="form-dialog-title">Child Dialog</DialogTitle>
<DialogContent>
<Child handleSaveData={this.handleSaveDialog} />
</DialogContent>
<DialogActions>
<Button onClick={() => this.handleSaveClick()} color="primary">
Save
</Button>
</DialogActions>
</Dialog>
</div>
);
}
}
class Child extends Component {
componentDidMount() {
console.log("mount");
this.props.handleSaveData(this.handleSaveClick);
}
handleSaveClick = () => {
console.log("save clicked");
};
render() {
return <div>//..</div>;
}
}
You will need to use arrow function in onClick as it will create a new function every time we click and thus getting a new instance of handleClick. And if you pass this.handleClick it won't work because it is undefined. You can check this by logging the value of this.handleClick in render function.
Note: Use the 2 option its more reliable.
Hope this helps!

Well, I don't know why you have this sort of scenario but the first things that comes in my mind is that you can write handleSaveClick method in your Parent component. And in case you need to use that function on any action that might be happening in your child component, you can pass this function as a prop from parent component. So your both cases can be handled
You can call this method on something happening in parent component
You can use the same method on any action that is happening in your child component.
If still you think that you have to define the method in your child component, you can use the refs

Related

reuse Modals in React

I have a very simple modal that I want to reuse to display dynamic data
class Modal extends Component {
constructor(props) {
super(props)
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleCloseModal = this.handleCloseModal.bind(this);
}
handleKeyDown(event) {
if (event.key === 'Escape') {
this.handleCloseModal()
}
}
handleCloseModal() {
// What should I do here? Unmount?
}
componentDidMount() {
document.addEventListener('keydown', this.handleKeyDown, false);
}
render() {
return(
<div className="modal">
<button onClick={() => this.handleCloseModal()}>close modal</button>
{this.props.children}
</div>
)
}
}
export default Modal
I would like to open the modal with the data from elem.description for each elem in the records array.
{records.map(function(elem, index) {
return <button
key={index}
onClick={// Something like <Modal>{elem.description}</Modal>}
/> open modal for {elem.name}</button>
})}
I read about some implementations that will do something like
<Modal show=true>...</modal>
and toggle the visibility of it. can i Just Unmount the component? is that a a good practice?
One way of doing that is to save the clicked element in the state and then, when it is clicked display the modal.
{records.map(function(item, index) {
return <button
key={index}
onClick={this.openModal(item)} // open modal and set the current item in the state
/> open modal for {elem.name}</button>
})}
You need a function to set the element as current and open the modal
openModal = (item) =>() => {
this.setState({currentItem:item, isModalVisible: true})
}
Finally in your modal just pass an item component as chidlren and give it data coming from the state
<Modal isVisible={this.state.isModalVisible}>
<MyItem data={this.state.currentItem} />
</Modal>

React: How to close a modal from child opened from parent component

I am opening child component modal by passing parent state as props to child. Is there any way to close the modal from child component itself without any interference from parent component.
class Parent extends Component {
constructor(props) {
super(props);
this.showModal = this.showModal.bind(this);
this.state = {
showModal: false
};
}
showModal() {
this.setState({ showModal: true });
}
renderRow() {
return (
<tr>
<td onClick={() => this.setState({ show: true })}>test</td>
<ChildModal show={this.state.showModal}/>
</tr>
);
}
}
class ChildModal extends Component {
render() {
return (
<Modal show={this.props.showModal}>
<Modal.Header closeButton>
<Modal.Title>Test</Modal.Title>
</Modal.Header>
<Modal.Body>
{/* some text */}
</Modal.Body>
</Modal>
);
}
}
I want my child modal to be self contained. Is this even possible in react.
You need to pass a callback as a props in Child component, it will update Parent Component when you click on closeButton in child.
// Parent Component
callbackModal = () => {
this.setState({ showModal: false });
}
//ChildButton
closeButtonClickHandler = () => {
this.props.callbackModal();
}
Yes, you can close it from the child component, but you'll need at least a little interference of parent component, and that is because you've defined the toggle state of this model in parent component.
simply define a method that will close the modal in parent component, pass it down to the child component as a prop, and call it there.
//in your parent component
handleModalClose = ()=>{
this.setState({showModal: false})}
now pass it down to your child component and simply call it there on an event like
this.props.handleModalClose()
A local state variable can only be controlled inside the component in which it was declared in.
You will not be able to close the modal from inside the child component without passing a method from the parent component that changes the state that is passed down.
So in order to close your modal, you'll need to create a method this.closeModal and pass it from the Parent to the child...
// Parent
closeModal = () => {
this.setState({showModal: false});
}
// ...
<ChildModal show={this.state.showModal} handleClose={this.closeModal} />
// ...
When you define a state in a parent component, pretty much everything around that state is handled from there. The child component can only receive data from the parent component in the form of props.
The parent component controls the opening and closing state of the modal so for you to be able to close the modal from the child component, you have to define a function closeModal on the parent component which will set the showModal variable (defined in the state of your parent component) from true back to false.
closeModal = () => {
this.setState({ showModal: false });
}
Then pass this function as props to the child component and call the function from there. When you click on the close button on the modal, the state will be updated on the parent component.
class Parent extends Component {
constructor(props) {
super(props);
this.showModal = this.showModal.bind(this);
this.closeModal = this.closeModal.bind(this)
this.state = {
showModal: false
};
}
showModal() {
this.setState({ showModal: true });
}
closeModal() {
this.setState({ showModal: false });
}
renderRow() {
return (
<tr>
<td onClick={() => this.setState({ show: true })}>test</td>
<ChildModal show={this.state.showModal} close={this.state.closeModal}/>
</tr>
);
}
}
class ChildModal extends Component {
render() {
return (
<Modal show={this.props.showModal}>
<Modal.Header closeButton>
<Modal.Title>Test</Modal.Title>
</Modal.Header>
<Modal.Body>
<buttom onClick={this.props.closeModal()}> ......
</Modal.Body>
</Modal>
);
}
}

setState() does not change state when called from element that depends on state to render

I have a little component like this (Code below is simplified to the parts needed) that behaves very strange when it comes to updating the state.
class Componenent extends React.Component {
constructor(props) {
super(props);
this.state = {showStuff: false};
}
render() {
return(
//Markup
{this.state.showStuff && (
<button onClick={() => this.setState({showStuff: false})} />
)}
// More Markup
);
}
}
The state gets updated somewhere else in the component, so the prop is true when the button is clicked.
A click also triggers the setState function (callback gets executed), however the state does not update.
My guess is that it does not update because the function is called by an element that directly depends on the state prop to be visible.
I figured out that adding another prop test: true to the state and changing that property to false when the button is clicked also triggers the showStuff prop to change to false. So it works when I make strange hacks.
Can someone explain this weird behavior to me? I can't gasp why the above snippet does not work like intended.
Here is the entire component:
class ElementAdd extends React.Component {
constructor(props) {
super(props);
this.defaultState = {
showElementWheel: false,
test: true
};
this.state = this.defaultState;
}
handleAddCardClick() {
if (this.props.onCardAdd) {
this.props.onCardAdd({
type: ElementTypes.card,
position: this.props.index
});
}
}
handleAddKnowledgeClick() {
if (this.props.onCardAdd) {
this.props.onCardAdd({
type: ElementTypes.knowledge,
position: this.props.index
});
}
}
handleTabPress(e) {
if (e.key === 'Tab') {
e.preventDefault();
let target = null;
if (e.shiftKey) {
if (e.target.previousSibling) {
target = e.target.previousSibling;
} else {
target = e.target.nextSibling;
}
} else {
if (e.target.nextSibling) {
target = e.target.nextSibling;
} else {
target = e.target.previousSibling;
}
}
target.focus();
}
}
hideElementWheel() {
// This is somehow the only option to trigger the showElementWheel
this.setState({ test: false });
}
render() {
return (
<div
className="element-add"
style={{ opacity: this.props.invisible ? 0 : 1 }}
onClick={() => this.setState(prevSate => ({ showElementWheel: !prevSate.showElementWheel }))}
>
<PlusIcon className="element-add__icon" />
{this.state.showElementWheel && (
<React.Fragment>
<div className="element-add__wheel">
<button
autoFocus
className="element-add__circle"
onClick={this.handleAddCardClick.bind(this)}
onKeyDown={this.handleTabPress.bind(this)}
title="New element"
>
<ViewModuleIcon className="element-add__element-icon" />
</button>
<button
className="element-add__circle"
onClick={this.handleAddKnowledgeClick.bind(this)}
onKeyDown={this.handleTabPress.bind(this)}
title="New knowledge-element"
>
<FileIcon className="element-add__element-icon" />
</button>
</div>
<div
className="element-add__close-layer"
onClick={() => {
this.hideElementWheel();
}}
/>
</React.Fragment>
)}
</div>
);
}
}
By writing onClick={this.setState({showStuff: false})} you are actually calling setState as soon as your button is rendered.
You want to give a function reference to onClick, not call it immediately on render.
<button onClick={() => this.setState({showStuff: false})} />
If your button is inside another element with a click listener that you don't want to run on the same click, you must make sure that the click event doesn't propagate to the parent.
<button
onClick={(event) => {
event.stopPropagation();
this.setState({showStuff: false});
}}
/>
Actually the onClick prop expects a function, you are already providing a function call, so the setState will be called each time the component is rendered, not when clicked.
Try this:
<button onClick={() => this.setState({showStuff: false})} />
Should behave as you expect :)
Works perfectly fine when I update showStuff true (see updated code below.). My guess is the code that is supposed to set showStuff: true is not working. I also added some text in the button.
import React from 'react'
import ReactDOM from 'react-dom'
class Componenent extends React.Component {
constructor(props) {
super(props);
this.state = {showStuff: true};
}
render() {
return(
<div>
{this.state.showStuff && (
<button onClick={() => this.setState({showStuff: false})} > This is a button</button>
)}
</div>
);
}
}
ReactDOM.render(<Componenent />,
document.getElementById('root')
);
Before clicking
After clicking

How to make a react component that can be controled from outside?

I'm trying to make a Dialog component using React and Material-UI.
For now, I have the traditional behavior of the Material-UI dialog with a button inside the class that can open the dialog. But the behavior that i want for this component is the same that the Material-UI dialog have.
So I want to change his state and re-render the component calling it like this:
var open = false;
<AuthDialog open={open} />
And using an outside button that change the open variable to true.
This is my Code:
class AuthDialog extends React.Component {
constructor(props, context){
super(props, context);
this.state = {
open: false,
};
};
handleRequestOpen = () => {
this.setState({
open: true,
});
};
handleRequestClose = () => {
this.setState({
open: false,
});
};
render() {
return (
<div>
<RaisedButton children="login" onTouchTap={ this.handleRequestOpen } />
<Dialog open={this.state.open} onRequestClose={this.handleRequestClose}>
// Content
</Dialog>
</div>
);
}
}
I'm a beginer with JS, so if my code isn't right somewhere I will appreciate some comments too.
Edit: This is my code now after the modification:
class AuthDialog extends React.Component {
constructor(props, context){
super(props, context);
this.state = {
// State of authBody
};
};
render() {
const { onRequestClose, open } = this.props;
return (
<Dialog open={open} onRequestClose={onRequestClose}>
// Body of the dialog
</Dialog>
);
}
}
And in the parent:
closeAuthDialog = () => {
this.setState({
openAuthDialog: false,
});
}
openAuthDialog = () => {
this.setState({
openAuthDialog: true,
});
}
<RaisedButton children="login" onTouchTap={ this.openAuthDialog } />
<AuthDialog open={this.state.openAuthDialog} onRequestClose={this.closeAuthDialog} handleLoginSuccess={ this.handleLoginSuccess } />
In order to achieve that, you need to use the props instead of the local state.
class AuthDialog extends React.PureComponent {
static propTypes = {
open: React.PropTypes.bool.isRequired,
onClose: React.PropTypes.func.isRequired,
onOpen: React.PropTypes.func.isRequired,
};
render() {
const { onClose, onOpen, open } = this.props;
return (
<div>
<RaisedButton children="login" onTouchTap={onOpen} />
<Dialog open={open} onRequestClose={onClose}>
// Content
</Dialog>
</div>
);
}
}
and then from the parent class you need to send the two function and the open prop.
<AuthDialog open={open} onOpen={this.toggleOpenToTrue} onClose={this.toggleOpenToFalse} />
Just get attributes you pass from this.props like this:
...
render() {
const { open, onRaisedButtonClick } = this.props;
return (
<div>
<RaisedButton children="login" onTouchTap={ onRaisedButtonClick } />
<Dialog open={open} onRequestClose={this.handleRequestClose}>
// Content
</Dialog>
</div>
);
}
...
Also, when you have stateless component with no lifecycle methods, you can use functional component; it's much simpler and readable:
const AuthDialog = ({ open, onRaisedButtonClick }) => (
<RaisedButton children="login" onTouchTap={ onRaisedButtonClick } />
<Dialog open={open} onRequestClose={this.handleRequestClose}>
// Content
</Dialog>
)
I am not sure what you're exactly looking for. But from what I have understood, you want to use your AuthDialog component like this;
<AuthDialog open={open} />
And when the <RaisedButton/> is clicked, you need to show the <Dialog /> and also to change the 'open' state/var on the parent as well.
In this case you're gonna wanna go for Flux or Redux to manage your app state. Since two-way binding in React is a bad practice that will be your best shot at achieving this.

Tie an action to a state change in pure React

Learning React so might be a bit nooby question. Consider this code:
class Application extends React.Component {
render() {
return <Container />
}
}
class Container extends React.Component {
constructor(props) {
super(props)
this.state = {
isOn: false
}
this.handleToggle = this.handleToggle.bind(this);
}
handleToggle(on) {
this.setState({
isOn: on
});
}
render() {
return (
<div>
<p>
{this.state.isOn ? 'on' : 'off'}
</p>
<MyButton handleToggle={this.handleToggle} />
</div>
);
}
}
class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = {
pressed: false
}
this.handleButtonToggle = this.handleButtonToggle.bind(this);
}
handleButtonToggle() {
const on = !this.state.pressed
this.setState({
pressed: on
});
this.props.handleToggle(on);
}
render() {
return (
<div>
<button onClick={this.handleButtonToggle}>
{this.state.pressed ? "pressed" : "depressed"}
</button>
</div>
);
}
}
ReactDOM.render(<Application />, document.getElementById('app'));
As you can see currently when the button inside the Container is clicked handleButtonToggle() is fired which changes the state of the button itself and then calls a function to change the state of the parent Container. Is this the React way to do it? At the moment Container state is changed when the function handleButtonToggle is fired. Ideally I would want Container state to be dependent on MyButton state directly (cuz maybe in future there will be ways to set button state other than through handleButtonToggle, and I don't want to manually call this.props.handleToggle every time the state changes.). In other words is there a way to do something like this.props.handleToggle(this.state.pressed) in the button component when its state changes.
Codepen
After reviewing the code, a better way to write the Button component is to make it a controlled component. If the container component requires to maintain the pressed state, a controlled button component would receive the pressed state from the container component as props.
<MyButton pressed={this.state.pressed} onToggle={this.handleToggle} />
And the render method of the Button component should be:
render() {
return (
<div>
<button onClick={this.props.onToggle}>
{this.props.pressed ? "pressed" : "depressed"}
</button>
</div>
);
}
The actual button toggle will be done in the handleToggle method of the container component:
handleButtonToggle() {
let { pressed } = this.state;
pressed = !pressed;
this.setState({
pressed
});
}
You can either pass a callback as the second argument to setState or implement componentDidUpdate on your component to wait for state changes. The smallest change would be the former:
handleButtonToggle() {
const on = !this.state.pressed
this.setState({
pressed: on
}, () => {
this.props.handleToggle(on);
});
}
or with componentDidUpdate:
componentDidUpdate(prevProps, prevState) {
if (prevState.on !== this.state.on) {
this.props.handleToggle(this.state.on);
}
}
Here is a working codepen: https://codepen.io/damien-monni/pen/XRwewV.
I tried to keep as much as I can of your code so you can focus on really needed changes.
You need to create a controlled component. The state of your button will be stored in the container and pass by props to the child MyButton component.
/*
* A simple React component
*/
class Application extends React.Component {
render() {
return <Container />
}
}
class Container extends React.Component {
constructor(props) {
super(props)
this.state = {
isOn: false
}
this.handleToggle = this.handleToggle.bind(this);
}
handleToggle() {
this.setState({
isOn: !this.state.isOn
});
}
render() {
return (
<div>
<p>
{this.state.isOn ? 'on' : 'off'}
</p>
<MyButton pressed={this.state.isOn} handleToggle={this.handleToggle} />
</div>
);
}
}
class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = {
pressed: this.props.pressed
}
this.handleButtonToggle = this.handleButtonToggle.bind(this);
}
handleButtonToggle() {
this.props.handleToggle();
}
componentWillReceiveProps(nextProps) {
if (nextProps.pressed !== this.props.pressed) {
this.setState({ pressed: nextProps.pressed });
}
}
render() {
return (
<div>
<button onClick={this.handleButtonToggle}>
{this.state.pressed ? "pressed" : "depressed"}
</button>
</div>
);
}
}
/*
* Render the above component into the div#app
*/
ReactDOM.render(<Application />, document.getElementById('app'));
You need to do it because you want to share a state between two of your components. This is documented here.
I let you look at the codepen and ask your questions about it. ;)

Categories

Resources