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

Write an article on Reactive Controllers and Redux #1283

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

Conversation

e111077
Copy link
Contributor

@e111077 e111077 commented Dec 16, 2023

Write an article on integrating third party libraries into lit with reactive controllers using redux as an example. Once we're close to an LGTM I will convert the code examples into either switchable samples or live playroung element examples.

Link to article preview -> https://pr1283-b46091e---lit-dev-5ftespv5na-uc.a.run.app/articles/redux-reactive-controllers/

Copy link

github-actions bot commented Dec 16, 2023

@e111077 e111077 marked this pull request as ready for review December 16, 2023 06:48
Copy link
Member

@augustjk augustjk left a comment

Choose a reason for hiding this comment

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

Is there a playground link with the finished example or is that not included on purpose?

edit: Oops saw the PR description about that.

@e111077
Copy link
Contributor Author

e111077 commented Dec 21, 2023

Thanks for leading this blind man through Redux! All suggestions should be addressed. Though I think it def needs another pass and a bit more discussion about structure. Personally I think it's okay to be a bit Lit specific since it's hosted on Lit.dev, but i agree it's a bit lazy to talk about the lit implementation for a specific part and the generalized use case in the rest of it


[Reactive Controllers](/docs/composition/controllers/) can help with the problem of sharing logic across components without having to create a new web component. They are similar to custom hooks in React, and in this article, we will use them to integrate the state manager Redux with Lit's rendering lifecycle for a more self-contained, composable, idiomatic Lit experience.

By the end of this article, you will learn how to use Reactive Controllers to integrate third party libraries into Lit by integrating Redux into Lit. To do this, we will create a Reactive Controller that selects part of a Redux state and updates a component whenever the state updates.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: "of a Redux state"
I may be totally wrong, but it feel like this should be "part of the Redux state".

2. Actions
3. Reducers

Stores contain the current application state. Actions describe what kind of change to make to the state, and reducers take actions and apply them to the current state to return a new state. Here is a diagram derived from the [official Redux documentation](redux.js.org/tutorials/essentials/part-1-overview-concepts#redux-application-data-flow ?) that depicts the interaction pattern between these concepts:
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor

@AndrewJakubowicz AndrewJakubowicz left a comment

Choose a reason for hiding this comment

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

This post is awesome! I had a lot of fun going through it. Most of my comments are minor suggestions that could be ignored. I left a comment anywhere that I needed to leave the article to pursue other knowledge.

What is the assumed knowledge of the reader?

  • I ask this question because it can drive some of the comment resolutions that I left. E.g., if this is focussed on quite technical readers who can follow quite sophisticated TypeScript, then we can leave in the more complex types.

Similarly there is quite a nuanced discussion of ReactiveControllers that may be fine to leave here, but we should update the documentation pages with your excellent information.

The only blocking change I'd like to see is a working playground example at the top of the article (or bottom?). Because it's a bit hard to follow the code samples out of context.

Elliott, this was an amazing read. Thank you so much for writing this!


## What is Redux?

[Redux](https://redux.js.org/) is a mature library that introduces patterns to manage state across a JavaScript application. The Lit team does not have a particular endorsement for a single state management library, but we will be using Redux as an example for creating a Reactive Controller, because the patterns used in integrating Redux into Lit with a Reactive Controller may be used to integrate for other popular libraries.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: with a Reactive Controller may be used to integrate for other popular libraries., maybe consider rewording.
The second sentence is very long and very complex.

4. The reducer updates the state in the store
5. The UI is updated with the newest state with a state subscription and, in our case, a Reactive Controller

Lit would cover the UI (blue) section of this diagram – rendering and event handling. Redux would handle the orange, green, and red parts of this diagram. The example in this article is to create a Reactive controller that handles the interaction between the updated state and the UI by hooking into both Lit’s reactive update lifecycle and Redux’s state updates.
Copy link
Contributor

Choose a reason for hiding this comment

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

It may be preferred to refer to the titles of the boxes, e.g. Event Handler, UI, Store... instead of colors in the case where color perception of the user is different.

This could also be simplified to state what Lit will cover, and that Redux is the rest.

- [`hostUpdate()`](/docs/api/controllers/#ReactiveController.hostUpdate)
- [`hostUpdated()`](/docs/api/controllers/#ReactiveController.hostUpdated)

In Lit, `hostConnected()` is called when the host component is placed in the DOM, or, if the component is already placed in the DOM, when the Reactive Controller is attached to the component. This is a good place to do initialization work when the host component is ready to be used such as adding event listeners.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: when the host component is ready to be used such as adding event listeners.
Maybe: "when the host component is ready to be used, such as adding event listeners"


`hostUpdate()` is called before the element is about to render or re-render. This is a good place to synchronize or compute state before rendering.

`hostUpdated()` is called after an element has just rendered or re-rendered. This is a good place to synchronize or compute a state that is reliant on rendered DOM. It is often discouraged to request an update to the host in this part of the lifecycle unless absolutely necessary as it may cause an unnecessary re-render of the component just after it has already rendered. Request host updates in `hostUpdated()` only when `hostUpdate()` cannot be utilized and add guards against infinite re-renders.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is discussing whether or not to request a Lit re-render here a little too in the weeds? This section strikes me as an opportunity to quickly ramp a reader on what we have to work with. The reader's objective is to get to integrating with Redux.
I'd expect more heavy documentation of these sorts of implementation detail edge cases to exist in the documentation for Reactive Controllers (where it doesn't currently exist). Unless requesting an update here is important for the Redux implementation later.

Comment on lines +101 to +105
{% aside "info" "no-header" %}

If the host is already attached to the DOM or rendered onto the page – it is recommended that implementations of `ReactiveControllerHost` call `hostConnected()` after attaching the Reactive Controller via `addController()`.

{% endaside %}
Copy link
Contributor

Choose a reason for hiding this comment

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

This is an amazing aside, and we can keep it, but shouldn't this information also be available in the ReactiveControllerHost documentation: https://lit.dev/docs/composition/controllers/#controller-host-api

Should some of this be moved/copied there?


render() {
const shapeList = this.sc.value;
...
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure it's worth omitting the returned line here, since it shows that it is fine to re-render lists and it's efficient.

}
```

The `shape-list` component should now be responsive to changes in the Redux store!
Copy link
Contributor

Choose a reason for hiding this comment

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

responsive makes me think window size. Dare I say, I think I prefer the word "reactive" instead. (But this is a nit, not required).

Another small nit: Can the word "should now be" be replaced with "is now"? I read "should" as "probably", and sure hope the shape-list is reactive to the redux store now!


The `shape-list` component should now be responsive to changes in the Redux store!

We were able to accomplish this by initializing the `SelectorController` with the shared Redux store. We then selected only the `shapeList` from the state and updated the host element only when the arrays were truly not equal.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Did this opt-into past tense?


To prevent invalid inputs, we will use our `SelectorController` to disable the buttons in the `shape-dials` component. For example, we want to disable the decrement buttons when the respective shape count is 0, and we want to disable the reset button when the length of the `shapeList` is 0.

We will be using the entire state object again, so the selector will be broad. Let’s add the SelectorController to our `shape-dials` component.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Can maybe remove "so the selector will be broad".


Reactive Controllers are generally a good way to share logic across components and we cannot cover every possible use case here. For example [Material Design’s Material Components](https://material-web.dev) have written their own bespoke controllers such as the [SurfacePositionController](https://github.com/material-components/material-web/blob/v1.1.1/menu/internal/controllers/surfacePositionController.ts) which can position a popup surface next to an anchor or [TypeaheadController](https://github.com/material-components/material-web/blob/v1.1.1/menu/internal/controllers/typeaheadController.ts) which can automatically select an item from a list just by typing the first few letters of the item like an autocomplete.

Reactive Controllers are flexible, focused, and a great way to integrate libraries into your Lit project or any framework of your choice.
Copy link
Contributor

Choose a reason for hiding this comment

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

This whole ending section is awesome. It's a great upbeat section full of awesome examples and ideas.

Could also add a call to action to inspire readers to go and implement some controllers where they see the requirement to share behavior.

@vikjung
Copy link

vikjung commented Mar 13, 2024

Two things will help make this more useful -

  1. provide code in javascript
  2. add how it could replace e.g. used with the pwa starter kit example which used lazyReducerEnhancer and thunk

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.

4 participants