20
Accessing Child Element States and functions using forwardRef , useImperativeHandle and the useRef hook
So I was working on a project and was trying to create a custom input component of type number and I did not want to do the whole state management and functions for incrementing and decrementing from the parent element, but instead inside the custom input element itself.
So I started thinking of ways to get the input value from the parent element, lo and behold I stumbled across forwardRef and useImperativeHandle and was able to solve my problem.
In this article I'm going to walk you through the steps of using these functions by building the custom input component;
First, we initialize a project using create-react-app using any of the three commands below.
npx create-react-app my-app
, npm init react-app my-app
, yarn create react-app
Secondly, we create a custom component CustomInput.js and start with forwardRef.
What the forwardRef basically does as the name implies is forward the ref attribute of the component so that whatever parent element that is using the component will have a direct access to the default properties of the component like name, value, type et cetera
All you have to do is wrap the component with forwardRef, expect a ref attribute along side your props in the component and pass the ref into the returned element.
import { forwardRef} from "react";
export const InputNumber = forwardRef((props, ref) => {
return (
<input type="number" ref={ref} />
);
});
So now what if I want to access more than just the default properties of the component?
What if I want to access say for example the state in the component?
With the useImperativeHandle hook, you can pass a value or a state to the parent element. It takes in two values;
- the ref
- a callback function that returns an object with keys and their respective values of properties to be passed together with the ref.
import { useState, forwardRef, useImperativeHandle } from "react";
import "./style.css";
export const InputNumber = forwardRef((props, ref) => {
const [state, setState] = useState(0);
const increment = () => {
setState((prev) => prev + 1);
};
const decrement = () => {
setState((prev) => {
if (prev === 0) {
return prev;
}
return prev - 1;
});
};
useImperativeHandle(ref, () => ({
inputValue: state,
}));
return (
<div className="input">
<span className="input-left" onClick={decrement}>
-
</span>
<span>{state}</span>
<span className="input-right" onClick={increment}>
+
</span>
</div>
);
});
In the parent element, you can access the inputValue property by calling the useRef hook on the component.
import { useRef } from "react";
import "./App.css";
import { InputNumber } from "./InputNumber";
function App() {
const inputRef = useRef();
const addToCartHandler= () => {
const noOfCartItems = inputRef.current.inputValue;
alert("you have " + noOfCartItems + "item(s) in the cart");
};
return (
<div className="App">
<InputNumber ref={inputRef} />
<button onClick={addToCartHandler}>Add to Cart</button>
</div>
);
}
export default App;
The css file for CustomeInput.js
.input {
background: #d36666;
border-color: #d36666;
position: relative;
cursor: default;
padding: 10px 30px;
color: #fafafa;
width: 50px;
margin: 15px 0;
}
.input-left,
.input-right {
position: absolute;
top: 0;
padding: 2.5px;
cursor: pointer;
height: 100%;
display: inline-flex;
align-items: center;
justify-content: center;
}
.input-left {
left: 5px;
}
.input-right {
right: 2.5px;
}
20