Calling Child’s function from Parent with react hooks
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 theref
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.