Eric S
Eric S

Reputation: 969

Is this a correct use case for the React useImperativeHandle hook?

I'm learning React hooks (and I'm fairly new to React), and was thinking through use cases for useImperativeHandle. I came up with a pretty useful scenario.

I do know that this can be accomplished without useImperativeHandle, but I think there are some advantages here.

What I don't know...

  1. Have I 'discovered the obvious' and this isn't really useful? ... or
  2. Is this bad form, or an anti-pattern?

My code is working- but I'm looking for input about best practices. Also, since there's a dearth of information right now about useImperativeHandle, this example that goes beyond an input ref might be useful to others.

I have a minimal example posted on Github if you want to play with it: https://github.com/ericsolberg/react-uih-hook

Using markup similar to:

const App = props => {
  return (
    <Foo>
      <Bar>This is Bar 0</Bar>
      <Bar>This is Bar 1</Bar>
      <Bar>This is Bar 2</Bar>
    </Foo>
  );
};

What I've accomplished is:

  1. Allow a parent component 'Foo' to provide state storage for it's children - so Foo can mount/dismount children yet allow them to restore state.
  2. 'Bar' uses useImperativeHandle to provide a 'call-in' so Bar can veto being dismounted, in case it is doing something important.

As I noted, this works perfectly. In React, data and props flow down the tree, while callbacks flow up. This gives you a way, for specific scenarios, to call down the tree. Is it advisable?

Upvotes: 2

Views: 3546

Answers (1)

Aprillion
Aprillion

Reputation: 22324

This is an anti-pattern: Inject props into the children without explicitly passing the props.

The idiomatic options are:

So if nothing simpler would be suitable for my business logic, I do something like following to avoid invisible tight coupling between Foo and Bar:

const App = () => (
  <Foo>
    {({selector, saveStateFactory}) => (<>
      <Bar state={selector(0)} saveState={saveStateFactory(0)} />
      <Bar state={selector(1)} saveState={saveStateFactory(1)} />
    </>)}
  </Foo>
)

const Foo = () => {
  const [state, dispatch] = useReducer(...)
  const selector = (id) => state[id]
  const saveStateFactory = (id) => {
    return (payload) => dispatch({type: id, payload})
  }
  // do something with whole state that cannot be done in App nor Bar
  return children({selector, saveStateFactory})
}

Upvotes: 4

Related Questions