facebook / react

The library for web and native user interfaces.
https://react.dev
MIT License
229.84k stars 47.06k forks source link

Keep using legacy Context API - or how to achieve this with new API #12924

Closed Twisterking closed 6 years ago

Twisterking commented 6 years ago

Do you want to request a feature or report a bug?

more like a feature.

What is the current behavior?

So I am currently using the legacy context api very heavily. A typical "component tree" in my app might look a bit like this:

<App>
  <Component1> // provides 3 Objects via context all children might need at some point
    <Component2> // might need one of the 3 Objects passed via context
      <Foo> // additionally provides 2 Functions via context
        <SomeList> // needs some Objects from <Component1>
          <ListItem>
            <SomeChild> // needs both functions from <Foo> and maybe some Objects from <Component1>
              // ... and so on and so forth - you get the idea
            </SomeChild>
          </ListItem>
        </SomeList>
      </Foo>
    </Component2>
  </Component1>
</App>

So I Have a heavily nested component tree, where I use context all the time to pass functions, booleans, objects or whatever without having to use props all the time - I am trying to avoid "prop drilling" as much as possible.

Additionally, some of these context vars might be set in lifecycle methods after a first render or maybe after some HOC provided some data. It is basically all over the place.

What is the expected behavior?

My question now is: I can't see any proper solution to achieve all this with the next context api. It will be a huge pain in the a** to achieve it and make some of my code completely unreadable.

Is there any way to keep using the legacy context api? Maybe the React team could provide a extra package for that? Or maybe someone has a better idea on how to achieve this without having pretty bad prop drilling all over the place.

Looking forward to your answers! best, Patrick

gaearon commented 6 years ago

Context is essentially a global variable (scoped to a subtree). What you’re saying sounds a bit like “I want to avoid passing arguments between functions so I made all variables global”. That was never the recommended use of context (even the legacy one).

Specifically, the old context documentation page did say:

Why Not To Use Context

[...]

If you want your application to be stable, don't use context. It is an experimental API and it is likely to break in future releases of React.

[...]

If you aren't an experienced React developer, don't use context. There is usually a better way to implement functionality just using props and state.

[...] If you insist on using context despite these warnings, try to isolate your use of context to a small area and avoid using the context API directly when possible so that it's easier to upgrade when the API changes.

I think it was a fair warning.

In this situation I think there are two possible options:

  1. You may want to refactor your app after all. There’s nothing wrong with passing props, just like there’s nothing wrong with passing arguments between functions. And yes, those can go multiple levels deep. If you feel like you’re passing too many props that don’t get used by certain components, it might mean you need to make them accept props.children to "short-circuit" prop passing to just the children that use them.

  2. Figure out some way to polyfill something similar to the old context using a new one. I don’t think I’ve seen such a utility in the wild yet but I think with time somebody might write one (and you could too). I imagine it could use a single new context pair under the hood (or maybe a pair per key) and you’d have to wrap every component in a HOC that injects related context parts as props.

To reply so specific points:

I am trying to avoid "prop drilling" as much as possible.

I don’t think this is very wise. It leads to behavior that is very hard to understand. I think it might be better to optimize for readability than for terseness. And of course, don’t neglect to use abstractions like passing children in the design of your components.

Additionally, some of these context vars might be set in lifecycle methods after a first render or maybe after some HOC provided some data.

Not sure why that would be a problem with new context. Keep the value in the state, pass value={this.state.value} in a provider. New context supports all existing use cases.

It is basically all over the place.

That sounds like the real problem here. Legacy context wasn’t intended to use this way. To quote the old doc again:

If you insist on using context despite these warnings, try to isolate your use of context to a small area and avoid using the context API directly when possible so that it's easier to upgrade when the API changes.

I’m sorry this is frustrating but it was never meant to be an API to replace passing props.