React has introduced Hooks in version 16.8, among which useImperativeHandle is a less commonly used but vital hook. It customizes the instance value that is exposed to parent components when using ref. In other words, you can decide what gets exposed to parent components accessing your function component through refs.
1 import React, { useRef, useImperativeHandle, forwardRef } from 'react'; 2 3 const Input = forwardRef((props, ref) => { 4 const inputRef = useRef(); 5 useImperativeHandle(ref, () => ({ 6 focus: () => { 7 inputRef.current.focus(); 8 } 9 })); 10 11 return ( 12 <input ref={inputRef} type="text"/> 13 ); 14 }); 15 16 function App() { 17 const inputRef = useRef(); 18 19 return ( 20 <div> 21 <Input ref={inputRef} /> 22 <button onClick={() => inputRef.current.focus()}> 23 Focus the input 24 </button> 25 </div> 26 ); 27 } 28 29 export default App; 30
This very basic example illustrates how to use useImperativeHandle to provide a focus() method on the ref passed by the parent component. The focus() method can then be used in the parent component to focus the input element inside the child component.
The useImperativeHandle hook takes two parameters, the ref that needs to be exposed, and a function that returns the valued that'll be binded to the ref.
1 useImperativeHandle(ref, () => customValue); 2
In the context of React, the flow of control is usually unidirectional and props are passed top-down from parent component to child component in such a way that child components have no direct way of sending anything back to the parent component.
But with the useImperativeHandle hook, the situation changes slightly. This hook allows the child component to expose certain values (like methods or variables) to the parent component, which can then use these values as it comes fit.
useImperativeHandle is generally used whenever we need a child function component to expose some of its functionalities to the parent component. For instance, maybe the function component will encapsulate some logic that might be useful in another part of the parent component.
Please note that resorting to useImperativeHandle is often discouraged unless necessary, as it breaks the convention of unidirectional data flow, making the code harder to follow and understand.
A practical example of using useImperativeHandle can be a Modal component where we needed to control its visibility from the parent. Here, the useImperativeHandle hook will allow the parent component to reference the function component to call methods to control the visibility of the Modal.
Remember that useImperativeHandle should be used sparingly as it leads to more imperative code. It is always advisable to first think about possible alternative design patterns that preserve a clear, top-down data flow before resorting to useImperativeHandle.
In React, the standard way to pass data is via props, which is also known as unidirectional data flow. This process is straightforward and effective; however, getting data back up from the child component to the parent component isn't as direct, but achievable.
There are several ways to transfer data from child to parent in React, including using state and props, callback functions, or more nuanced methods such as leveraging context or state management libraries like Redux.
The most favored technique is by using a form of function passed from parent to child via props, which allows the child to invoke a function where the parent component might update its state.
1 import React, { useState } from "react"; 2 3 function Child({ onChildClick }) { 4 return ( 5 <button onClick={() => onChildClick("Data from Child")}> 6 Pass data to parent 7 </button> 8 ); 9 } 10 11 function Parent() { 12 const [data, setData] = useState(""); 13 14 const handleChildClick = (dataFromChild) => { 15 setData(dataFromChild); 16 } 17 18 return ( 19 <div> 20 <Child onChildClick={handleChildClick} /> 21 <p>Received in Parent Component: {data}</p> 22 </div> 23 ); 24 } 25 26 export default Parent; 27
In this code snippet, we see that a button click on the Child component sends a String value back up to the Parent component by calling onChildClick(). This function is a 'callback' function, passed into the Child as a prop from the Parent.
As covered in the previous section, React's useImperativeHandle hook can be used in combination with forwardRef. This enables a parent component to interact imperatively with functions of a child component.
The following example is the same as the previous one, but the useImperativeHandle method is used to expose the state of the Child component to the Parent component.
1 import React, { useRef, useImperativeHandle, forwardRef } from "react"; 2 3 const Child = forwardRef((props, ref) => { 4 useImperativeHandle(ref, () => ({ 5 getData: () => "Data from Child" 6 })); 7 8 return null; 9 }); 10 11 function Parent() { 12 const childRef = useRef(); 13 14 const handleButtonClick = () => { 15 alert(childRef.current.getData()); 16 }; 17 18 return ( 19 <div> 20 <Child ref={childRef} /> 21 <button onClick={handleButtonClick}> 22 Get data from child 23 </button> 24 </div> 25 ); 26 } 27 28 export default Parent; 29
As we know, the parent component controls the props of child components. Consequently, if we need to call a function in a child component from the parent, we can set that function up to be called based on a prop value.
In the following example, we create a 'trigger' prop in the Child component. Once triggered, it executes a method to perform a specific task (e.g., updating state or triggering side effects).
useImperativeHandle works seamlessly with callback functions to interact with the child component's internal methods. This is especially helpful in cases where the child component encapsulates complex logic that the parent does not need to be aware of, and it simply needs to perform specific tasks at appropriate times.
Similarly, when transferring data from child components to parents, useImperativeHandle is an invaluable tool in event handling or managing user input, as it allows the input or events to be managed at the parent level while the UI interaction remains within the child.
In the world of React, we mostly deal with declarative code, where we describe what we want to achieve, and the library takes care of how to update the DOM. However, there are certain scenarios where we need to work with imperative code that interacts directly with elements in the DOM. Here, we use imperative logic. The hook that React provides for these kinds of needs is called useImperativeHandle.
The useImperativeHandle hook allows you to customize the instance value that is exposed to parent components when using ref. It enables us to choose which properties should be exposed to a parent component. The function passed to useImperativeHandle will return an object (usually containing functions) that will be assigned to the ref passed to the component.
1 import React, { useImperativeHandle, forwardRef } from 'react'; 2 3 const ChildComponent = forwardRef((props, ref) => { 4 useImperativeHandle(ref, () => ({ 5 sayHello: () => console.log('Hello!') 6 })); 7 8 return <div>I am a child component.</div>; 9 }); 10 11 export default ChildComponent; 12
In this example, the ChildComponent has a sayHello function, accessible by using a ref in the parent component.
The useEffect hook allows us to perform side effects in function components. Among these side effects, we often need to perform some sort of action when a component is mounted, updated, or will unmount. To accomplish this, we can use a callback function in useEffect.
1 import React, { useState, useEffect } from 'react'; 2 3 function App() { 4 const [count, setCount] = useState(0); 5 6 useEffect(() => { 7 document.title = `Count: ${count}`; 8 9 return () => { 10 document.title = 'React App'; 11 }; 12 }, [count]); 13 14 return ( 15 <div> 16 <p>You clicked {count} times</p> 17 <button onClick={() => setCount(count + 1)}> 18 Click me 19 </button> 20 </div> 21 ); 22 } 23 24 export default App; 25
In this React snippet, a count is initialized to 0. When the button is clicked and the count is updated, useEffect is run since it's watching the count variable in the dependency array. On useEffect invocation, the document title is updated based on the count. When the component unmounts, also known as clean-up, the document title is restored to 'React App'.
React Native also supports useImperativeHandle similar to ReactJS. With useImperativeHandle, a child component can expose specific functionalities to a parent component, while keeping the implementation details neatly encapsulated.
For instance, if a modal component is designed to hide/show based on the state in the child component, but we want to control this behavior from the parent component, useImperativeHandle might come in handy, by exposing the show/hide functionality to the parent.
The useLayoutEffect is similar to useEffect, but it fires synchronously after all DOM mutations. useLayoutEffect and useEffect, both allow you to write side effects that run after render, but useLayoutEffect updates are flushed synchronously, before the browser has had a chance to paint. This results in a potentially smoother user experience as less visual tearing occurs.
This hook is useful in performing actions that need to happen immediately after DOM updates, like measuring DOM nodes or preventing certain visual artifacts. It should be used sparingly, as synchronous work is blocking and can be resource-heavy on performance.
In most contexts of React, rendering components would also generate new instances of functions, objects, and more. This isn't typically a problem, since creating such instances is usually trivial. However, certain components (like child components) might rely on reference equality to determine if they should re-render. If those instances haven't changed, then the component should not re-render itself.
useMemo is a hook that can optimize performance aspects in specific cases where unnecessary renderings are occurring due to changes in reference. useMemo can greatly prevent unnecessary renders, enhancing the overall performance of the application.
1 import React, { useMemo, useState } from 'react'; 2 3 function ExpensiveComponent({ compute, count }) { 4 const computedValue = useMemo(() => compute(count), [compute, count]); 5 6 return <h1>{ computedValue }</h1>; 7 } 8 9 function App() { 10 const [count, setCount] = useState(0); 11 12 const increment = () => setCount(count + 1); 13 14 const compute = (num) => { 15 console.log("Computing"); 16 return num * 2; 17 }; 18 19 return ( 20 <> 21 <button onClick={increment}> 22 Increment 23 </button> 24 <ExpensiveComponent compute={compute} count={count} /> 25 </> 26 ); 27 } 28 29 export default App; 30
In the above example, ExpensiveComponent uses useMemo to only re-calculate the computedValue when compute or count changes - not when the component re-renders for any other reason.
Just like all the other hooks in React, useImperativeHandle plays well with the rest of the hooks list. It's often used in combination with useRef, useState, useEffect, and others to accomplish specific tasks. For instance, we could use useImperativeHandle with useRef to give the parent component a method to focus an input field within the child component.
It is worth noting that, despite its usefulness, useImperativeHandle should not be your first choice options when managing interactions between components. This hook breaks the usual top-down data flow in React and can lead to code that's harder to read and maintain. It's best suited for rare instances like managing focus, text selection, or triggering animations.
After exploring useImperativeHandle and its implementations extensively, hopefully, it is clear that while it is not needed in every React application, it can be a vital tool in certain scenarios. The examples given illustrate its usage and certainly make it less "scary" as it initially appears. It is always good to know about this tool in your React toolkit when such situations arise where we may need to break away from the conventional data-flow norms.
For an in-depth understanding, have a look at the official React documentation. Like any tool, it's not about if we will use it, but when we can use it effectively to write better, more optimized and maintainable React applications.
Remember, always start with simple solutions and only reach for more complex solutions or strategies like useImperativeHandle when your requirements are not being full-filled by the simpler ones. Happy coding!
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.