react best practice (1)-- useRef, forwardRef, useImperativeHandle
Learning React with hooks
1  | yarn create react-app react-best-practice --template=typescript && cd react-best-practice  | 
1. Parent component define a ref and pass it to Child componet, Child component is used React.forwardRef to a real InputWithLabel component.
- types.tsx
types.tsx 1
2
3
4
5
6
7
8export interface IChildInputProps {
label: string
}
export interface IInputWithLabelProps {
label: string,
myRef: any
} - ParentPage.tsx
ParentPage.tsx 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import React, { FC, ReactElement, useRef } from 'react';
import ChildInput from './ChildInput';
const ParentPage: FC = (): ReactElement => {
const myRef = useRef<HTMLInputElement>();
const handleClick = () => {
const node = myRef.current;
console.log(node?.value);
node?.focus()
};
return (
<div>
<ChildInput label={"Child Name"} ref={myRef} />
<button onClick={handleClick}>Click</button>
</div>
);
}
export default ParentPage; - ChildInput.tsx
ChildInput.tsx 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25import React, { forwardRef, ReactElement, useState } from "react";
import { IChildInputProps, IInputWithLabelProps } from "./types"
const InputWithLabel = ({ label, myRef }: IInputWithLabelProps): ReactElement => {
const [value, setValue] = useState("");
const handleChange = (e: any) => {
console.log(`message from child.value=${e.target.value}`)
const value = e.target.value;
setValue(value);
};
return (
<div>
<span>{label}:</span>
<input type="text" ref={myRef} value={value} onChange={handleChange} />
</div>
);
}
const ChildInput = forwardRef(({ label }: IChildInputProps, ref: any) => (
<InputWithLabel label={label} myRef={ref} />
));
export default ChildInput; - IndexRouter.tsx
IndexRouter.tsx 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import React, { ReactElement } from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import ParentPage from '../components/forwardRef/ParentPage';
const IndexRouter = (): ReactElement => {
return (
<BrowserRouter>
<Routes>
<Route path="/forwardRef" element={<ParentPage />} />
</Routes>
</BrowserRouter>
)
}
export default IndexRouter;This method will export the whole
ChildInputtoParentPage, for security reason, we just want export some functions toParentPage, that we can useuseImperativeHandler
Otherwise, for example user can write following code inParentPage. Do you really want user writeonclickevent inParentPageif you are the owner ofChildInput?1
2
3
4
5
6const handleClick = () => {
const node = myRef.current;
console.log(node?.value);
node?.focus();
node.onclick = function() { alert('have clicked me!') }
}; 
2. Add innerRef and  useImperativeHandle in real inner component InputWithLabel
- update 
ChildInput.tsxChildInput.tsx 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import React, { forwardRef, ReactElement, useState, useRef, useImperativeHandle } from "react";
...
useImperativeHandle(myRef, () => ({
getValue,
focus() {
const node = innerRef.current;
node?.focus();
}
}));
const getValue = () => {
return value;
}
...
<input type="text" ref={innerRef} value={value} onChange={handleChange} /> - update 
ParentPageParentPage 1
2
3
4
5
6
7
8const myRef = useRef<any>();
const handleClick = () => {
const node = myRef.current;
console.log(node);
console.log(`the value '${node.getValue()}' from Child fucntion getValue()`);
node.focus();
};From the console, when click the
Clickbutton, we can see onlygetValueandfocuswere exported. 
3. Delete InputWithLabel and move the content into ChildInput
- Delete 
IInputWithLabelPropstypes.tsx 1
2
3export interface IChildInputProps {
label: string
} - Update 
ChildInput.tsxChildInput.tsx 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36import React, { forwardRef, ReactElement, useState, useRef, useImperativeHandle } from "react";
import { IChildInputProps } from "./types"
const ChildInput = ({ label }: IChildInputProps, ref: any): ReactElement => {
const [value, setValue] = useState("");
const innerRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
getValue,
focus() {
const node = innerRef.current;
node?.focus();
}
}));
const getValue = () => {
return value;
}
const handleChange = (e: any) => {
e.preventDefault();
console.log(`message from child.value=${e.target.value}`)
const value = e.target.value;
setValue(value);
};
return (
<div>
<span>{label}:</span>
<input type="text" ref={innerRef} value={value} onChange={handleChange} />
</div>
);
}
export default forwardRef(ChildInput); - No change in 
ParentPage