


Engineering Team
2021-05-28
10 mins
Managing state in React can be challenging, especially when dealing with deeply nested components or reusable, complex child components. Often, you need access to a child component’s internal state without triggering unnecessary re-renders. This is where forwardRef and useImperativeHandle become powerful tools.
In this guide, you will learn how to use these hooks effectively to streamline your data flow, create reusable components, and improve your React applications’ performance.
The challenge of state management in complex React apps
Understanding forwardRef
Introducing useImperativeHandle
A practical, abstract example
Best practices and closing thoughts
The added challenge here is the fact that it is not usually a great idea to make whatever it is you’re working on go through a major refactor when in the middle of introducing a new feature.
The shared mindset was:
more optimising the new things we introduce and less reinventing the wheel which proved to be a useful mindset and allowed us to retrofit existing code to match the new patterns we discovered.
This new feature introduces several instances of a complex text editor to an already highly dynamic page so my code reusing patterns from the good and not-so-old C++ days came back to me and I thought:
If only there was a way to make those fancy editor components reusable and take advantage of the fact that they manage their own state internally, making only the data we’re interested in available at a given time, e.g. in event handling callbacks...
useImperativeHandle(ref, createHandle, [deps])
() => editorContext
we make the internal editor context data available to whoever knows about (or owns) the ref passed into the editor.
onBlur={() => {
if (props.onBlur) props.onBlur(ref)
}}
Recalling our need to avoid re-renders, let us build a simplified pattern that would allow us to access editor context from the parent component and dispatch a manipulated result to some state reducer:
import MyFancyEditor from './MyFancyEditor'
import { useSomeReducer } from './SomeContextProvider'
const ParentComponent = () => {
const { dispatch } = useSomeReducer();
const editorRef = React.createRef();
return(
// Some other components
<MyFancyEditor
ref={editorRef}
onBlur={({current}) => dispatch({
// Some other values
document: current.helpers.getJSON(current.state),
})
}
/>
)
}
Refs and imperative handles can have the level of complexity we’d like to provide them with, and that’s the beauty of using them!
With forwardRef and useImperativeHandle you can implicitly adopt the for React components.
Similar to anything in JavaScript, the returned context should have well-defined properties that can be modified or at least observed without completely breaking the component in the process — granted you do not forget this, move on do ref great things!