Calling Child’s function from Parent with react hooks

Hamidreza Nazemi
3 min readFeb 19, 2021

There are many scenarios that we want to call child’s function from it’s parent. This can be done easily with forwardRef and useImperativeHandle.

Let’s define our scenario.
We have a dialog component that could be open by clicking a button, this button is in the parent component.
This is the parent component:

const App = () => {
const [open, setOpen] = useState(false);
return (
<>
<Button
onClick={() => setOpen(true)}
>
Open Dialog
</Button>
<CustomDialog
open={open}
onClose={() => setOpen(false)}
name="React"
/>
</>
);
};

In this code we have open state that controls state of the dialog (show or hide).
Let’s see CustomDialog code:

const CustomDialog = ({ open, name, onClose }) => {
const closeHandler = () => {
onClose();
};
return (
<Dialog onClose={closeHandler} open={open}>
<DialogTitle>Hello {name}</DialogTitle>
</Dialog>
);
};

We define open and onClose in props, so it could be controlled from the parent component.

Now, we change CustomDialog component to control state of dialog inside component not from it’s parent:

const CustomDialog = ({ name }) => {
const [open, setOpen] = useState(false);
const closeHandler = () => {
setOpen(false);
};
const openHandler = () => {
setOpen(true);
};
return (
<Dialog onClose={closeHandler} open={open}>
<DialogTitle>Hello {name}</DialogTitle>
</Dialog>
);
};

So, we have to call openDialog function from parent to open the dialog.
So, this is what we want to learn now, call Child’s function from its’s parent.
To achieve this we use forwardRef and useImperativeHandle

With forwardRef we can get the ref that passed to component.

We need the ref to use in useImperativeHandle.

With useImperativeHandle we can customizes the instance value that is exposed to parent components when using ref.

In the code below, CustomDialog uses forwardRef to obtain the ref passed to it. Also the parent component that renders CustomDialog would be able to call dialogRef.current.openDialog().
So, each function that we want to call it from the parent component, should be defined in useImperativeHandle.

const CustomDialog = forwardRef((props, ref) => {
const [open, setOpen] = useState(false);
const closeHandler = () => {
setOpen(false);
};
const openHandler = () => {
setOpen(true);
};
useImperativeHandle(ref, () => ({
openDialog() {
openHandler();
}
}));
return (
<Dialog onClose={closeHandler} open={open}>
<DialogTitle>Hello {props.name}</DialogTitle>
</Dialog>
);
});

We change the parent component to be like this, We create a new ref with createRef and pass to CustomDialog and we can call openDialog() on onClick with dialogRef.current.

const App = () => {
const classes = useStyles();
const dialogRef = createRef();
return (
<div className={classes.app}>
<Button
variant="contained"
color="primary"
disableElevation
onClick={() => dialogRef?.current?.openDialog()}
>
Open Dialog
</Button>
<CustomDialog ref={dialogRef} name="React" />
</div>
);
};

So, now when click the button, our CustomDialog will be opened.

This approach is very useful to decrease parent component rendering, But you should use useImperativeHandle when it is necessary.

As always, imperative code using refs should be avoided in most cases.

Bonus: Adding types

Now we show how to make some changes to add types with typescript.
We add types for ref and props.

export interface CustomDialogProps {
name: string;
}
export interface CustomDialogRef {
openDialog: () => void;
}

and change CustomDialog component to this:

export interface CustomDialogProps {
name: string;
}
export interface CustomDialogRef {
openDialog: () => void;
}
const CustomDialog = forwardRef<CustomDialogRef, CustomDialogProps>(
(props, ref) => {
const [open, setOpen] = useState(false);
const closeHandler = () => {
setOpen(false);
};
const openHandler = () => {
setOpen(true);
};
useImperativeHandle(ref, () => ({
openDialog() {
openHandler();
}
}));
return (
<Dialog onClose={closeHandler} open={open}>
<DialogTitle>Hello {props.name}</DialogTitle>
</Dialog>
);
}
);

We can create a ref and set it’s type like this:

const dialogRef = createRef<CustomDialogRef>();

now we can use power of typescript and IDE intelligence while coding.

--

--

Hamidreza Nazemi

Software Engineer | Former Team Lead | Decentralizing Everything | I'm Good With JS