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

[labs/redux] Reactive controller for subscribing to a Redux store #4586

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

augustjk
Copy link
Member

@augustjk augustjk commented Mar 19, 2024

Putting this up as draft PR. There's a few more things needed here. Not sure if/when I might be able to get to it.
Changes are ready to review.

This PR adds a new @lit-labs/redux package and an example project making use of it.

It adds a reactive controller for subscribing to a redux store from context and trigger updates for host component when the redux store updates.
It also includes decorators that use the controller above for selecting state, and bringing in a dispatch to the component.

See the README file for the package for more details.

TODO

  • Add tests.
  • JSDoc on user facing exports.
  • README.
  • Renamed class to Connector with selector function being optional so it can do either select and/or dispatch, like the connect() function from react-redux.
  • Decorators for selector and dispatch for better ergonomics over directly using controllers? These would be more akin to useSelector and useDispatch hooks from react-redux.

Copy link

changeset-bot bot commented Mar 19, 2024

🦋 Changeset detected

Latest commit: 03bed21

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@lit-labs/redux Minor
@lit-examples/redux Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

github-actions bot commented Mar 19, 2024

📊 Tachometer Benchmark Results

Summary

nop-update

  • this-change, tip-of-tree, previous-release: unsure 🔍 -3% - +7% (-0.26ms - +0.75ms)
    this-change vs tip-of-tree

render

  • this-change: 45.43ms - 47.87ms
  • this-change, tip-of-tree, previous-release: unsure 🔍 -5% - +4% (-0.93ms - +0.71ms)
    this-change vs tip-of-tree
  • this-change, tip-of-tree, previous-release: unsure 🔍 -1% - +4% (-0.32ms - +1.50ms)
    this-change vs tip-of-tree
  • this-change, tip-of-tree, previous-release: unsure 🔍 -3% - +2% (-1.00ms - +0.49ms)
    this-change vs tip-of-tree

update

  • this-change: 456.73ms - 468.57ms
  • this-change, tip-of-tree, previous-release: unsure 🔍 -7% - +6% (-2.55ms - +2.16ms)
    this-change vs tip-of-tree
  • this-change, tip-of-tree, previous-release: unsure 🔍 -1% - +3% (-0.86ms - +1.83ms)
    this-change vs tip-of-tree
  • this-change, tip-of-tree, previous-release: unsure 🔍 -0% - +1% (-0.98ms - +5.28ms)
    this-change vs tip-of-tree

update-reflect

  • this-change: 466.37ms - 474.81ms
  • this-change, tip-of-tree, previous-release: unsure 🔍 -0% - +1% (-1.70ms - +5.70ms)
    this-change vs tip-of-tree

Results

this-change

render

VersionAvg timevs
45.43ms - 47.87ms-

update

VersionAvg timevs
456.73ms - 468.57ms-

update-reflect

VersionAvg timevs
466.37ms - 474.81ms-
this-change, tip-of-tree, previous-release

render

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
17.88ms - 18.90ms-unsure 🔍
-5% - +4%
-0.93ms - +0.71ms
unsure 🔍
-5% - +3%
-0.86ms - +0.50ms
tip-of-tree
tip-of-tree
17.86ms - 19.14msunsure 🔍
-4% - +5%
-0.71ms - +0.93ms
-unsure 🔍
-5% - +4%
-0.86ms - +0.71ms
previous-release
previous-release
18.12ms - 19.03msunsure 🔍
-3% - +5%
-0.50ms - +0.86ms
unsure 🔍
-4% - +5%
-0.71ms - +0.86ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
36.78ms - 39.94ms-unsure 🔍
-7% - +6%
-2.55ms - +2.16ms
unsure 🔍
-9% - +3%
-3.41ms - +1.29ms
tip-of-tree
tip-of-tree
36.81ms - 40.31msunsure 🔍
-6% - +7%
-2.16ms - +2.55ms
-unsure 🔍
-8% - +4%
-3.33ms - +1.61ms
previous-release
previous-release
37.68ms - 41.16msunsure 🔍
-3% - +9%
-1.29ms - +3.41ms
unsure 🔍
-4% - +9%
-1.61ms - +3.33ms
-

nop-update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
10.43ms - 11.09ms-unsure 🔍
-3% - +7%
-0.26ms - +0.75ms
unsure 🔍
-6% - +2%
-0.64ms - +0.26ms
tip-of-tree
tip-of-tree
10.13ms - 10.89msunsure 🔍
-7% - +2%
-0.75ms - +0.26ms
-unsure 🔍
-8% - +0%
-0.92ms - +0.05ms
previous-release
previous-release
10.65ms - 11.25msunsure 🔍
-2% - +6%
-0.26ms - +0.64ms
unsure 🔍
-1% - +9%
-0.05ms - +0.92ms
-
this-change, tip-of-tree, previous-release

render

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
33.68ms - 35.22ms-unsure 🔍
-1% - +4%
-0.32ms - +1.50ms
unsure 🔍
-1% - +5%
-0.23ms - +1.60ms
tip-of-tree
tip-of-tree
33.37ms - 34.35msunsure 🔍
-4% - +1%
-1.50ms - +0.32ms
-unsure 🔍
-2% - +2%
-0.60ms - +0.80ms
previous-release
previous-release
33.26ms - 34.26msunsure 🔍
-5% - +1%
-1.60ms - +0.23ms
unsure 🔍
-2% - +2%
-0.80ms - +0.60ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
66.51ms - 68.50ms-unsure 🔍
-1% - +3%
-0.86ms - +1.83ms
unsure 🔍
-0% - +3%
-0.06ms - +2.12ms
tip-of-tree
tip-of-tree
66.12ms - 67.93msunsure 🔍
-3% - +1%
-1.83ms - +0.86ms
-unsure 🔍
-1% - +2%
-0.47ms - +1.56ms
previous-release
previous-release
66.03ms - 66.93msunsure 🔍
-3% - +0%
-2.12ms - +0.06ms
unsure 🔍
-2% - +1%
-1.56ms - +0.47ms
-
this-change, tip-of-tree, previous-release

render

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
29.72ms - 30.45ms-unsure 🔍
-3% - +2%
-1.00ms - +0.49ms
unsure 🔍
-1% - +3%
-0.25ms - +0.77ms
tip-of-tree
tip-of-tree
29.70ms - 30.98msunsure 🔍
-2% - +3%
-0.49ms - +1.00ms
-unsure 🔍
-1% - +4%
-0.22ms - +1.25ms
previous-release
previous-release
29.47ms - 30.18msunsure 🔍
-3% - +1%
-0.77ms - +0.25ms
unsure 🔍
-4% - +1%
-1.25ms - +0.22ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
462.29ms - 467.70ms-unsure 🔍
-0% - +1%
-0.98ms - +5.28ms
unsure 🔍
-0% - +1%
-1.15ms - +6.02ms
tip-of-tree
tip-of-tree
461.27ms - 464.43msunsure 🔍
-1% - +0%
-5.28ms - +0.98ms
-unsure 🔍
-1% - +1%
-2.55ms - +3.12ms
previous-release
previous-release
460.21ms - 464.92msunsure 🔍
-1% - +0%
-6.02ms - +1.15ms
unsure 🔍
-1% - +1%
-3.12ms - +2.55ms
-

update-reflect

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
503.40ms - 509.49ms-unsure 🔍
-0% - +1%
-1.70ms - +5.70ms
unsure 🔍
-0% - +1%
-2.15ms - +6.17ms
tip-of-tree
tip-of-tree
502.34ms - 506.55msunsure 🔍
-1% - +0%
-5.70ms - +1.70ms
-unsure 🔍
-1% - +1%
-3.52ms - +3.54ms
previous-release
previous-release
501.60ms - 507.27msunsure 🔍
-1% - +0%
-6.17ms - +2.15ms
unsure 🔍
-1% - +1%
-3.54ms - +3.52ms
-

tachometer-reporter-action v2 for Benchmarks

Copy link
Contributor

The size of lit-html.js and lit-core.min.js are as expected.

@augustjk augustjk marked this pull request as draft March 19, 2024 22:31
@augustjk augustjk marked this pull request as ready for review April 10, 2024 23:18
packages/labs/redux/src/lib/selector.ts Outdated Show resolved Hide resolved
@@ -0,0 +1,2 @@
/node_modules/
/dist/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

who's writing to /dist/, Vite? Leave a comment?

@@ -0,0 +1,28 @@
BSD 3-Clause License

Copyright (c) 2017 Google LLC. All rights reserved.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2024?


### Connect a child component to the provided store

Note: The component must be a child of the provider above. Attempting to connect without a store provided via context will throw an error.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if you want to mention ContextRoot here...

Note: The component must be a child of the provider above. Attempting to connect without a store provided via context will throw an error.

#### Using the `Connector` controller

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a short description of the the Connector controller is first?


The `@dispatch()` decorator takes no arguments.

Note: Like all the Lit 3 decorators, these decorators will work as either TypeScript experimental decorators or [standard decorators](https://lit.dev/docs/components/decorators/#standard-decorators). When running as standard decorators, `@select()` decorated fields must also have the `accessor` keyword. `@dispatch()` decorated fields do not have this requirement.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Standard decorators can decorate private fields too.

*
* This controller must be used with a provider that supplies the Redux store
* using the `storeContext` from this package using the [Context
* Protocol](https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add link to @lit/context too

* @example
*
* ```ts
* // `AppStore` type gotten from created Redux store
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove "gotten"

this._store = store as S;
},
});
if (this._store === undefined) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this context subscribes, is this error correct here? What if the page is using a ContextRoot? Maybe this should be some kind of debug warning.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had that thought too..

I was hoping if it might be possible to kind of force this requirement on users to force the context provider be always set up first because I otherwise we have to consider that without having the store on start up, the selected value could be undefined and users need to always guard for that.

So if a user selected for an object, they would basically have to always null check any property access to it for their first render. Maybe that's not the worst thing, but I really like the idea that, assuming the user provided the correct store type and a valid selector, they can always expect a certain value to exist and use as such within their component code.

}

hostConnected() {
new ContextConsumer(this._host, {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need to make the consumer in the constructor and hold a reference to it. That's how I usually show examples of controller composition.

The callback in this case will keep the consumer instance alive even after this method exits, which means on a disconnect/reconnect you will create another long-lived consumer, causing a memory leak. This could be noticable for connected components inside repeat() or cache().

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would storing the consumer instance and checking to not re-create work? I think I had some timing issues when instantiating in the constructor because the context value (redux store) would not be immediately available.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants