Giter VIP home page Giter VIP logo

redux-store-element's Introduction

CircleCI npm version dependency Status devDependency Status Published on webcomponents.org

<redux-store>

A custom element allowing a more declarative use of Redux.

Introduction

This custom element offers the basic Redux API declaratively. Because this is a custom element based on the web components standards, it should be compatible with all major front-end JavaScript frameworks and libraries that interoperate with custom elements, including Polymer, SkateJS, Vue.js, Angular, vanilla JavaScript, etc. See here for more information on interoperability of other frameworks and libraries with custom elements.

Simple and Declarative

Before we begin, I just want to highlight how easy it is to work with this element declaratively (using Polymer data binding syntax):

  • Hook up your root reducer (similar to Redux createStore):

    // HTML
    <redux-store root-reducer="[[rootReducer]]"></redux-store>
    
    // JS
    import {RootReducer} from '../../redux/reducers';
    
    ...
    
    connectedCallback() {
      this.rootReducer = RootReducer;
    }
    
  • Dispatch actions (similar to Redux dispatch):

    // HTML
    <redux-store action="[[action]]"></redux-store>
    
    // JS
    fireAnAction() {
      this.action = {
        type: 'CHANGE_THE_STATE'
      };
    }
    
  • Listen for state changes (similar to Redux subscribe):

    // HTML
    <redux-store on-statechange="stateChange"></redux-store>
    
    [[valueToBind]]
    
    // JS
    stateChange(e) {
      const state = e.detail.state;
    
      this.valueToBind = state.valueToBind;
    }
    
  • Explicitly grab the state (similar to Redux getState):

    // HTML
    <redux-store id="redux-store-element"></redux-store>
    
    // JS
    getReduxState() {
      const reduxStoreElement = this.querySelector('#redux-store-element');
      const state = reduxStoreElement.getState();
    }
    

Installation and Setup

Run the following:

npm install redux-store-element

Now import redux-store-element.js:

// HTML
<script type="module" src="node_modules/redux-store-element/redux-store-element.js">

// JavaScript

import 'redux-store-element';

This custom element depends on the custom elements and HTML imports web component specifications, which are not supported by all browsers yet. Include the webcomponentjs polyfills to ensure support across all modern browsers:

// CONSOLE
npm install --save @webcomponents/webcomponentsjs

// HTML
<script src="node_modules/webcomponentsjs/webcomponents-lite.js"></script>

This custom element also depends on native ES Modules support and bare specifier support. Use a bundler like Rollup or Webpack if your environment doesn't support ES Modules. If your environment does support ES Modules and you do not wish to use a bundler, you will need to use a static file server that provides rewrites for bare specifiers like Polyserve or Zwitterion.

The following examples are written with Polymer. It shouldn't be too hard to adapt them to other libraries and frameworks, keeping in mind their data binding systems and DOM interactions. This documentation is outdated, using HTML Imports instead of ES Modules, and will be updated in the future:

Creating the root reducer

At some point before you begin dispatching actions, you need to pass in your root reducer to any <redux-store></redux-store> element through the root-reducer attribute. From the example:

<link rel="import" href="../../../lib/bower_components/polymer/polymer.html">

<link rel="import" href="../../../src/redux-store.html">
<link rel="import" href="../input-area/input-area.component.html">
<link rel="import" href="../text/text.component.html">

<dom-module id="test-app">
    <template>
        <redux-store root-reducer="[[rootReducer]]"></redux-store>
        <test-input-area></test-input-area>
        <test-text></test-text>
    </template>

    <script>
        Polymer({
            is: 'test-app',
            ready: function() {

                var initialState = {
                    temp: 'initial temp'
                };

                this.rootReducer = function(state, action) {

                    if (!state) {
                        return initialState;
                    }

                    switch(action.type) {
                        case 'CHANGE_TEMP': {
                            var newState = Object.assign({}, state);

                            newState.temp = action.newTemp;

                            return newState;
                        }
                        default: {
                            return state;
                        }
                    };
                };

            }
        });
    </script>
</dom-module>

Subscribing to state changes

If your component needs to listen to state changes, simply pop a <redux-store></redux-store> element in and pass a listener function name in for the statechange event. From the example:

<link rel="import" href="../../../lib/bower_components/polymer/polymer.html">

<link rel="import" href="../../../src/redux-store.html">

<dom-module id="test-text">
    <template>
        <redux-store on-statechange="mapStateToThis"></redux-store>

        <div id="testText">Text from input above will go here</div>
    </template>

    <script>
        Polymer({
            is: 'test-text',
            mapStateToThis: function(e) {
                this.$.testText.innerHTML = e.detail.state.temp;
            }
        });
    </script>
</dom-module>

Dispatching actions

To dispatch from within an element, first bind the action property of the element to the action property on <redux-store></redux-store>. When you are ready to dispatch an action, set the action property on the element to the action that you want to dispatch. From the example:

<link rel="import" href="../../../src/redux-store.html">

<dom-module id="test-input-area">
    <template>
        <redux-store action="[[action]]"></redux-store>

        <input id="testInput" type="text">
        <button on-click="handleClick">Dispatch</button>
    </template>

    <script>
        Polymer({
            is: 'test-input-area',
            handleClick: function(e) {
                this.action = {
                    type: 'CHANGE_TEMP',
                    newTemp: this.$.testInput.value
                };
            }
        });
    </script>
</dom-module>

Multiple Stores

By default, there is one store for the entire application, meaning that each instance of a <redux-store></redux-store> will use the same store. You can however use multiple stores if you've determined it is necessary for your application. Simply reference the store name as follows:

  • Hook up your root reducer: <redux-store root-reducer="[[rootReducer]]" store-name="fooStore"></redux-store>
  • Dispatch actions: <redux-store action="[[action]]" store-name="fooStore"></redux-store>
  • Listen for state changes: <redux-store on-statechange="mapStateToThis" store-name="fooStore"></redux-store>

Important Things to Know:

  • Your runtime environment must support ES Modules natively. Check here for compatibility
  • You must pass in your root reducer to any <redux-store></redux-store> before actions will be dispatched and state change events raised
  • By default there is one store for the entire application. Each instance of a <redux-store></redux-store> will use the same store
  • The statechange event supplies the redux state in the detail.state property of the event

Development

npm install
npm run test-window

redux-store-element's People

Contributors

lastmjs avatar renovate-bot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

redux-store-element's Issues

Example Unit Testing / Mocking

I am sorry if this seems out of scope or the answer is obvious (I am new to webcomponents) but was wondering if there is a recommended approach to mocking / unit testing with a polymer application? If so would you be willing to include more details in the readme?

Allow for unsubscription

Right now there is no way to unsubscribe an element from redux state changes. This could become a memory leak issue and needs to be addressed.

How to access the current state?

Is there a way to access the current state from an element? I see that I can listen for state changes, but I don't see a way to just access the current state.

Allow reducers to be passed into the store from the outside

Right now you have to edit the source code of the element to get the reducers to be passed in. I'm not sure how to allow the user to pass in the reducers from their own code. That's one of the next steps in getting this element ready for real use.

I don't believe the user can dynamically set the store name

Right now I believe the user must set the store name almost immediately for it to register a new store. So, if the user wants to data-bind a store name, and do it at some distant point in the future, we will need to add that feature.

Add great documentation that can be generated from the source code

I'm not sure what the best solution to this is, but I'd like to go study and see what the best options are and then start to use them here. Ideally we'll be able to document all of our components in the source code, and have that turned into documentation that can be put on GitHub.

Instead of `on-statechange`, see if you can just databind the listener function into the element

The on-statechange event has some undesirable qualities. The biggest is that the parameter passed into the listener function is an event, and you have to go down a couple levels to actually get to the state. This makes it so that we have to create another TypeScript interface just to describe this kind of event. It would be a lot better if the first parameter to the listener function was just the state. To do this, we could just databind the listener function into the element. But, the last time I tried to databind class member methods into Polymer elements, everything exploded. I had a discussion with members of the Polymer team, and they don't seem to think this use case is valid without additional boillerplate code that doesn't make sense to me. Maybe I'll petition them again, or maybe Polymer 2 has fixed the issues.

Typescript Errors in project

I was really excited to find this project because I have been looking for a way to create a Polymer project with Typescript and Redux.

Unfortunately, when I cloned the project and opened it with my editor, I got a number of Typescript errors. I see in a recent commit that your converted to ES2015 classes. Does that mean that you're not supporting Typescript? I really hope not because Polymer/Redux/Typescript looks like a great combination!

Look into the attribute change listeners

We probably do not need an attribute change listener for root-reducer or action, because the values they would be set to would probably not be easily serializable.

On every action a "@@redux/INIT" action type is also triggered

I caught this by using the following in my reducer:

  switch(action.type) {
    default: {
      console.warn('uncaught action', action);
      return state;
    }
  }

This causes anything listening to the state change to trigger twice every time a single action is performed.

Explain the local state management capabilities

Now the element supports a local-state-name attribute. If passed in, the element can have LOCAL_STATE_CHANGE actions fired into it, allowing web components to manage their local component state with Redux without the extra boilerplate from reducers and actions. Once it is well-tested on your own applications, put it into the README.

Create a Polymer element the official way

I suppose I'm waiting for npm to be officially supported by Polymer, I know it's in the works right now. Once the npm ecosystem is ready, we need to convert this element over to the official Polymer way of creating reusable elements.

Clean up the SystemJS build configuration

Get rid of the test-config.js file, incorporate changes appropriately into es-no-build. Figure out how to get rid of the dist/ folder if possible, so that the production and development html files can be identical. It is tricky figuring out how to tell if you are in production or development...

Rethink this.listenersToAdd

Now that I'm using a class member property for listenersToAdd, I don't think it needs to be an array...I think there will only ever be one context that we need to save and subscribe to later. In fact...look into it.

Think about how to add middleware

People might want to add Redux middleware, I'm not sure how that would work right now. Perhaps middleware could be children of the element, that could work.

Figure out npm link errors with npm 5

npm link isn't working since the upgrade to node 8 and npm 5:

npm WARN checkPermissions Missing write access to /home/lastmj/development/redux-store-element/node_modules/redux-store-element
npm WARN [email protected] No license field.

npm ERR! path /home/lastmj/development/redux-store-element/node_modules/redux-store-element
npm ERR! code ENOENT
npm ERR! errno -2
npm ERR! syscall access
npm ERR! enoent ENOENT: no such file or directory, access '/home/lastmj/development/redux-store-element/node_modules/redux-store-element'
npm ERR! enoent This is related to npm not being able to find a file.
npm ERR! enoent 

npm ERR! A complete log of this run can be found in:
npm ERR!     /home/lastmj/.npm/_logs/2017-06-01T23_54_19_606Z-debug.log

Polymer2

are you planning to support Polymer2?

Add test for firing actions when there are multiple stores

  • Create an arbitrary number of stores
  • Instantiate an arbitrary number of redux-store elements
  • Randomly assign a store to the redux-store element
  • Fire actions on each and ensure that only the store that was fired on raises a statechange event

missing redux.min.js

the redux-store.html is trying to load this:

<script src="../redux/dist/redux.min.js"></script>

But in the bower directory there is no such file

stores[this._storeName] = undefined

Getting an error here:
set action(val) {
stores[this._storeName].store.dispatch(val);
}

When I set a break point and inspect in the console "stores" is {} and stores["DEFAULT_STORE"] is undefined.

Successf ully using this element in a Poly v1 app and now trying to set up a Polymer v2 app. Might be related?

Automatically run npm run link

I would like to figure out how to automatically run npm run link when the user does an npm install. It was causing infinite loops for me though.

Infinite recurse for actions

The infinite recurse seemed awesome, but has some problems. If you fire multiple actions against the store before your component is connected, and the actions themselves depend on the state being updated synchronously from the action before it, then bad things will happen. Maybe generators can help us here to do something synchronously that needs to act asynchronous. I've always wondered about doing something like this, I think it's time to dive in and see if it can be done.

Usability feedback for action attribute when re-rendering

The action attribute presumably tries to replace the imperative dispatch method in redux pattern, but in cases like lit-html where you just re-render the element, action is already set to the previous action, and creating a new <redux-store-element action=${action}> causes previous action to retrigger on a re-render.

Not quite a bug, but quite a blocker in the case of lit-html from my perspective, and requires work-around e.g. grabbing the redux element directly and setting action on it instead

Consider creating a vanilla web component version of this repo

From this discussion: https://polymer.slack.com/archives/projects/p1460401675000212

Maybe we should consider creating a vanilla version of this element. I'll need to think about it though, since from my understanding polymer elements should be interoperable with other web components, without modification. But, the component of course would be heavier than a vanilla component, since it would need to bring in the polymer library. Think about it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.