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

Need for context properties #46

Closed
askbeka opened this issue May 10, 2018 · 16 comments

Comments

@askbeka
Copy link

@askbeka askbeka commented May 10, 2018

I like the turn project took, and I suppose Redux is going to be mainstream in this area. And there is one thing that might improve the way LitElement works with Redux.

This idea is not unique, React also has it.

Now, in order pass a property down the hierarchy, each component needs to define it and pass over to its children

Better approach might be to have context properties that will be available for all affected scope/branch it will be applied.

I am not sure about implementation, but maybe scoped custom element registry and WeakMap might come in handy?

And it will be possible to have this sort of code

my-component-container.js

   import MyComponent from './my-component.js';

   const mapStateToProps = state => ({ someState: state.someState });
   const mapDispatchToProps = dispatch =>
            ({ doSomething: (value) => dispatch({ type: 'DO_SOMETHING', payload: value }) });

   export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);

my-component.js

   ...
   export default class MyComponent extends LitElement {
        static get properties() {
            return {
               someState: String,
               doSomething: Function,
            };
        }
        _render({ someState, doSomething }) {
           return html`<input value=${someState} on-change=${doSomething}/>`;
       }
   };

connect.js

   export const connect = (mapStateToProps, mapDispatchToProps) => baseElement => {
       return class extends baseElement {
           connectedCallback() {
                const { store } = this.context;
                if (!store) {
                   throw new Error('no store passed over');
                }
                this.__storeUnsubscribe = store.subscribe(() => this._stateChanged(store.getState()));
                ....
           }

           disconnectedCallback() {
              if (this.__storeUnsubscribe) {
                  this.__storeUnsubscribe();
              }
               ...
           }
       }
   }

index.html

   <store-provider>
      <my-component></my-component>
   </store-provider>
   <script>
      import { createContext } from  '@polymer/lit-element';
      createContext('store-provider', { store: createStore() }));
   </script>
@Lodin

This comment has been minimized.

Copy link

@Lodin Lodin commented May 12, 2018

I've been thinking a lot about implementing React-like context to Web Components and finally found an answer: why it should be React-like? Especially when React team itself decided to kill old-style context and create a new one.

In my opinion, React relies on components too much. They even replaced class inheritance with HOCs, trying to make classes looking like a function. It still doesn't work, because component have their own lifecycle, and it breaks the very idea of component = function (I think React is great project changed everything in web development though).

Web Components are different. While React has its own compiler starting with React.render(), Web Components don't have any external system to inject context as React does. But they don't have to do it, because we have a great platform-close tool to achieve any necessary context we need.

Just create a factory that consumes your store and produces connect function, and it will work without any special context:

// createConnect.js
export const createConnector = store => (mapStateToProps, mapDispatchToProps) => baseElement => {
       return class extends baseElement {
           connectedCallback() {
                this.__storeUnsubscribe = store.subscribe(() => this._stateChanged(store.getState()));
                ....
           }

           disconnectedCallback() {
              if (this.__storeUnsubscribe) {
                  this.__storeUnsubscribe();
              }
               ...
           }
       }
   }
// connect.js
export default const connect = createConnect(createStore());

That's it. You don't need extra code to support complex system of context as it was in React.

Also I think it is very similar approach the new React context has. They use a function to create Provider and Consumer components that are exchanging value property between each other. But we don't even need components to do it, everything is built-in.

@askbeka

This comment has been minimized.

Copy link
Author

@askbeka askbeka commented May 13, 2018

@Lodin . Thank you for your response. But, one of the problems this issue addresses is testability and SSR. Problem with having static connect or store exported from the module makes it singleton, and harder to mock the store while testing and when doing SSR, especially when your app is a part of a larger app. This is issue also mentioned here.

I agree that having component in dom that does not have any visual footprint but just passes context can be confusing. probably this should not be a part of LitElement, more of a pwa-helpers. I´ll do a PR with and idea I have and link it here soon.

@Westbrook

This comment has been minimized.

Copy link
Contributor

@Westbrook Westbrook commented May 13, 2018

This talk was posted to the Slack channel, and it might be of interest to this conversation as it came up in the context of Context Properties there: https://youtu.be/6o5zaKHedTE

@askbeka

This comment has been minimized.

Copy link
Author

@askbeka askbeka commented May 13, 2018

@Westbrook , Thanks, that is an amazing approach, Never occurred to me.

@askbeka

This comment has been minimized.

Copy link
Author

@askbeka askbeka commented May 19, 2018

I have created a small lib called it wc-context, no packaging yet, its coming soon. In meantime want to get some feedback. There is a Redux exmaple in demo folder.

@Lodin

This comment has been minimized.

Copy link

@Lodin Lodin commented May 25, 2018

@askbeka I still think the functions are better for it. I've got an idea to make them static and mimic new React.Context API completely.

const createContext = (default) => {
  let ctx = default;

  const provide = (newCtx) => {
    ctx = newCtx;
  }

  const consume = () => {
    if (!ctx) {
      throw new Error('Context is not defined');
    }

    // do whatever you want from consumer
  }

  return {consume, provide};
}

export {
  consume,
  provide,
} = createContext();

What do you think about this approach?

@askbeka

This comment has been minimized.

Copy link
Author

@askbeka askbeka commented May 28, 2018

@Lodin , This approach is not much different from just using store in component, I am not saying that it is bad, it may be all you need if you have one team working on whole app and it is not big. But in other cases this apprach does not scale very well.

Imagine that you have an app that is being developed by multiple teams some of them can be offshore and they have their own source control. You can store context as a global object in page and make them use that, but then you have a problem if you need multiple stores in your app, and components should be used with different stores.

Good thing that we have DOM and custom element and events to solve that. I am experimenting with it, for now I am not really happy with API, but I will try to release alpha version soon to get more feedback.
I don't have much time unfortunately:(

@Lodin

This comment has been minimized.

Copy link

@Lodin Lodin commented May 28, 2018

@askbeka Oh I see it now. Thanks, talking with you was really useful. Waiting for your context lib!

@sorvell

This comment has been minimized.

Copy link
Member

@sorvell sorvell commented Jun 8, 2018

This outside the scope of LitElement since it is focused on creating custom elements that work with normal DOM API's. We might reconsider adding an API like this in the future if there's enough interest and a concrete proposal.

We recommend looking at PWA Starter Kit for docs, examples, and best practices around using LitElement when making PWAs and other web apps. It uses Redux for state management.

@sorvell sorvell closed this Jun 8, 2018
@blikblum

This comment has been minimized.

Copy link

@blikblum blikblum commented Nov 10, 2018

I created wc-context library that supports lit-element and skatejs as well vanilla web components

The API is still in flux

@rikakomoe

This comment has been minimized.

Copy link

@rikakomoe rikakomoe commented Aug 22, 2019

Thanks for @Westbrook mentioning Justin Fagnani's presentation, @askbeka's demo code and @blikblum's library.

@askbeka

This comment has been minimized.

Copy link
Author

@askbeka askbeka commented Aug 22, 2019

@rikakomoe you are welcome. Thanks for reminding. Almost forgot about this issue.
I couldn't find time to update on my final solution.

But, since then I have contributed context implementation for haunted.
Which implements reacts's hooks pattern with webcomponents.

There is also recent ongoing attempt to make react-redux work you can track here

@rikakomoe

This comment has been minimized.

Copy link

@rikakomoe rikakomoe commented Aug 22, 2019

@askbeka The official guide requires a constant existing store before defining the element. The issue is, besides it is not tidy enough though, I am not building a website but a component, if the user creates more than one instance, all of them need their independent state. So I was looking for a declarative method to inject this store into children components, for convenience, without passing down the store over and over. React provides this dependency injection feature as its context while lit-element doesn't yet.

I'm interested about your final solution, if there were any updates/improvements against the solution I've seen so far.

@Lodin

This comment has been minimized.

Copy link

@Lodin Lodin commented Aug 22, 2019

@rikakomoe, you can also try my context approach that implemented in the @corpuscule/context library. It allows avoiding unnecessary HTML elements whose only purpose is to serve as a provider. With this library, you can make your root component a theme provider, a redux provider, a router provider, etc. at the same time without creating expensive HTML elements and increase nesting. It works with any implementation of web components.

The library is based on decorators, so you will need Babel to transpile your components while the decorator proposal is in development. Corpuscule project provides solution for it as well.

BTW, the redux bindings (based on the context library) are also there: @corpuscule/redux. It also works with any web component library as well as vanilla web components.

@rikakomoe

This comment has been minimized.

Copy link

@rikakomoe rikakomoe commented Aug 22, 2019

@Lodin Thanks!

@askbeka

This comment has been minimized.

Copy link
Author

@askbeka askbeka commented Aug 22, 2019

@Lodin More news on this topic,
With new CSS display type display: contents.
You should not be afraid of extra DOM, since it does not affect layout.
It is slready supported by 83% of browsers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
6 participants
You can’t perform that action at this time.