Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Data: Avoid lingering listeners from stringified data-bound components #11819

Closed
wants to merge 1 commit into from

Conversation

aduth
Copy link
Member

@aduth aduth commented Nov 13, 2018

This pull request seeks to explore a resolution to an issue raised by @jsnajdr at #11777 (comment) in that our current approach to subscribing to store updates from withSelect is problematic in leaving lingering subscribers to a registry if the component doesn't complete the full mounting lifecycle (e.g. renderToString only constructs, never componentDidMount nor componentWillUnmount).

Status:

In its current form, this pull request only includes a failing test case illuminating the issue.

In experimenting toward a resolution, a few challenges have surfaced:

  • Unlike react-redux, we do not require a top-level context provider, which introduces challenges in the direction of subscribing to an ancestor instance of the component rather than to the registry directly
  • Since a registry can consist of several stores, it is not trivial to determine whether state has changed between a component's constructor and its componentDidMount.
    • The "trivial" way would be to attach a subscriber to the registry and track whether the callback is invoked, but again, since we have no guarantee that componentDidMount will actually be called, this introduces a very similar issue with lingering subscribers

The two general angles I've tried taking here include some combination of:

  • Moving subscribe (whether to the registry or some context-provided ancestor proxy) into componentDidMount
    • Complicated by:
      • Parent > Child subscriber order (parent should be called before child)
      • If a change in state occurs between initial mergeProps being calculated in constructor and the componentDidMount
  • Creating a separate context Provider/Consumer pair dedicated to subscriptions, where each instance of a withSelect manages subscriptions for all descendent instances, to assure proper ordering of callbacks, to manage changes in registry, and to help in limiting the total number of store subscribers (rather than each withSelect subscribing to registry, it just receives new context when registry updates).
    • There's still an issue of: If state changed between constructor and componentDidMount (presumably the latter is where the registry subscription is attached), how is this known?

cc @jsnajdr @youknowriad @Tug

@aduth aduth added [Status] In Progress Tracking issues with work in progress [Package] Data /packages/data labels Nov 13, 2018
@youknowriad
Copy link
Contributor

Unlike react-redux, we do not require a top-level context provider, which introduces challenges in the direction of subscribing to an ancestor instance of the component rather than to the registry directly

Can we try to mimic the last version of react-redux, maybe something like that would work?

  • Subscribe only in the provider if a provider is used.
  • Subscribe only in the first withDispatch/withSelect if there's no parent provider.

@jsnajdr
Copy link
Member

jsnajdr commented Nov 14, 2018

I created a little experimental app that creates a nested tree of context providers and consumers, where each WithRegistry (similar to connect) passes an updated context value to its children:

const Registry = React.createContext("root");

const WithRegistry = props => (
  <Registry.Consumer>
    {registry => (
      <Registry.Provider value={`${registry}/sub`}>
        {props.children(registry)}
      </Registry.Provider>
    )}
  </Registry.Consumer>
);

const App = () => (
  <WithRegistry>
    {registry => (
      <ul>
        <li>{registry}</li>
        <WithRegistry>{registry => <li>{registry}</li>}</WithRegistry>
      </ul>
    )}
  </WithRegistry>
);

And it works as expected: renders this list:

  • root
  • root/sub

Consumer that has no parent provider uses the default value of the context ("root"). Each WithRegistry uses the registry value passed to it in context. And its child WithRegistry components receive an updated context based on the parent one ("root/sub"). Exactly what we need to implement a react-redux@5-style tree of nested subscriptions with no top-level provider.

But if we could rely on a top-level provider being available and reacting to registry changes, the implementation could be even simpler. Like in react-redux@next, WithRegistry could be a trivial context consumer with no magic at all.

It it possible to add such a provider? We already need the EditorProvider for the editor to work at all, so one additional DataProvider can't hurt, can it?

@aduth
Copy link
Member Author

aduth commented Feb 8, 2019

This still needs to be resolved, but the pull request in its current form is not under active development. The test case can be cherry-picked into a subsequent pull request, or this one reopened at a future time when effort can be directed to it.

@aduth
Copy link
Member Author

aduth commented Feb 14, 2019

I didn't realize there was not a corresponding issue here, so I've created one to at least ensure it continues to be tracked as a bug.

See #13879

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Package] Data /packages/data [Status] In Progress Tracking issues with work in progress
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants