The recommended alternative to managing state in multiple locations is pulling state up to the common parent.
React is a Functional Programming framework, where data enters the function to consistently produce results in child components. Messages flow up to change data. Data flows down to change presentation.
As mentioned above, this usage of useRef is an antipattern, because it allows data to flow up without any messaging.
Two alternatives are common throughout the React community: keeping state in Context, and using useReducer.
Using Context allows for a piece of state to be accessible across multiple descendants, retrieving the appropriate state and setters from anywhere underneath. Redux, the granddaddy of global state managers, uses Context to keep its global state, then only rerenders components that need parts of state that change, reducing the total number of rerenders required.
The other technique, using the useReducer hook, allows you to keep your state and setters in one object and one function, allowing you to support complex state with a minimum of fuss.
When these techniques are combined you get a really powerful pattern that lets you manage complex state and messaging with minimal repetition. The advantage is that it respects React’s principles.
While useRef is meant to track mutable data across renders, React is based upon immutable data transfers between components. Immutable data is fundamental to Functional Programming design. Using useRef for mutable data makes sense within a component, but the moment it leaves the component, it’s fighting against the framework.
So, yes, your technique is indeed one way to do it. But it goes counter to React design principles, and will make it harder to work in the larger React ecosystem.