Giter Club home page Giter Club logo

stencil-redux's Introduction

Stencil Redux

A simple redux connector for Stencil-built web components inspired by react-redux.

Install

npm install @stencil/redux
npm install redux

Usage

Stencil Redux uses the redux library underneath. Setting up the store and defining actions, reducers, selectors, etc. should be familiar to you if you've used React with Redux.

Configure the Root Reducer

// redux/reducers.ts

import { combineReducers } from 'redux';

// Import feature reducers and state interfaces.
import { TodoState, todos } from './todos/reducers';

// This interface represents app state by nesting feature states.
export interface RootState {
  todos: TodoState;
}

// Combine feature reducers into a single root reducer
export const rootReducer = combineReducers({
  todos,
});

Configure the Actions

// redux/actions.ts

import { RootState } from './reducers';

// Import feature action interfaces
import { TodoAction } from './todos/actions';

// Export all feature actions for easier access.
export * from './todos/actions';

// Combine feature action interfaces into a base type. Use union types to
// combine feature interfaces.
// https://www.typescriptlang.org/docs/handbook/advanced-types.html#union-types
export type Action = (
  TodoAction
);

Configure the Store

// redux/store.ts

import { Store, applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk'; // add-on you may want
import logger from 'redux-logger'; // add-on you may want

import { RootState, rootReducer } from './reducers';

export const store: Store<RootState> = createStore(rootReducer, applyMiddleware(thunk, logger));

Configure Store in Root Component

// components/my-app/my-app.tsx

import { store } from '@stencil/redux';

import { Action } from '../../redux/actions';
import { RootState } from '../../redux/reducers';
import { initialStore } from '../../redux/store';

@Component({
  tag: 'my-app',
  styleUrl: 'my-app.scss'
})
export class MyApp {

  componentWillLoad() {
    store.setStore(initialStore);
  }

}

Map state and dispatch to props

πŸ“ Note: Because the mapped props are technically changed within the component, mutable: true is required for @Prop definitions that utilize the store. See the Stencil docs for info.

// components/my-component/my-component.tsx

import { store, Unsubscribe } from '@stencil/redux';

import { Action, changeName } from '../../redux/actions';
import { RootState } from '../../redux/reducers';

@Component({
  tag: 'my-component',
  styleUrl: 'my-component.scss'
})
export class MyComponent {
  @Prop({ mutable: true }) name: string;

  changeName!: typeof changeName;

  unsubscribe!: Unsubscribe;

  componentWillLoad() {
    this.unsubscribe = store.mapStateToProps(this, state => {
      const { user: { name } } = state;
      return { name };
    });

    store.mapDispatchToProps(this, { changeName });
  }

  componentDidUnload() {
    this.unsubscribe();
  }

  doNameChange(newName: string) {
    this.changeName(newName);
  }
}

Usage with redux-thunk

Some Redux middleware, such as redux-thunk, alter the store's dispatch() function, resulting in type mismatches with mapped actions in your components.

To properly type mapped actions in your components (properties whose values are set by store.mapDispatchToProps()), you can use the following type:

import { ThunkAction } from 'redux-thunk';

export type Unthunk<T> = T extends (...args: infer A) => ThunkAction<infer R, any, any, any>
  ? (...args: A) => R
  : T;

Example

// redux/user/actions.ts

import { ThunkAction } from 'redux-thunk';

export const changeName = (name: string): ThunkAction<Promise<void>, RootState, void, Action> => async (dispatch, getState) => {
  await fetch(...); // some async operation
};

In the component below, the type of this.changeName is extracted from the action type to be (name: string) => Promise<void>.

// components/my-component/my-component.tsx

import { changeName } from '../../redux/actions';

export class MyComponent {
  changeName!: Unthunk<typeof changeName>;

  componentWillLoad() {
    store.mapDispatchToProps(this, { changeName });
  }
}

stencil-redux's People

Contributors

adamdbradley avatar corysmc avatar dependabot-preview[bot] avatar dependabot[bot] avatar imhoffd avatar joewoodhouse avatar jthoms1 avatar larionov avatar mitchellsimoens avatar mlynch avatar ravshansbox avatar yjaaidi 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

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

stencil-redux's Issues

Is it possible to dynamically load my reducers

Right now, the way I have to compose reducers require to provide all reducers at once and then store is created. Is it possible that once I load a component, I load the reducer, saga for that particular component? Something like this where I inject a couple of reducers and then load others as they come:

import { combineReducers } from 'redux-immutable';
import appReducer from './containers/app-root/app.reducer';
import homeReducer from './containers/home/home.reducer';
/**
 * Creates the main reducer with the asynchronously loaded ones
 */
export default function createReducer(asyncReducers?) {
  return combineReducers({
    app: appReducer,
    home: homeReducer,
    ...asyncReducers,
  });
}

This createReducer can then be used while creating my store like this:

const store = createStore(
    createReducer(),
    immutable.fromJS(initialState),
    composeEnhancers(...enhancers)
  );

Typically, while routing using ion-router I would like to specify the reducer and saga (or thunk) for a route.

update npm package

Can you please update the npm package to a new build of this repo, as i would like to use the 'unsubscribe()' method without needing to link this repo.

This could also be a nightly build or a prerelease version with an alpha/beta/rc-tag.

Thank you!

declare typesafe `Action`

suggestion:

export declare type Action<T, TOut> = (...args: T[]) => TOut;

Allows for specifying input parameter types and return type.
e.g:

registerAccount: Action<IAccount, IReduxAction>;
updateValidationStatus: Action<IAccount | string[], IReduxAction>;

if and when implemented microsoft/TypeScript#5453 will make things even better in future.

Injecting the store for unit-tests: 'store' is undefined

Hi there,

I've recently played a bit with stenciljs and tried to integrate redux using this connector.
While everything works (approx.) fine for development und production build, I've faced some problems when trying to unit test components that have the Store as a Prop trying to connect to it.

A simple example is the my-app component, which attempts to set up the store: https://github.com/DorianGrey/stencil-playground/blob/master/src/components/my-app/my-app.tsx

When executing the unit test (see https://github.com/DorianGrey/stencil-playground/blob/master/src/components/my-app/my-app.spec.ts) for it, this raises an error like this:

 FAIL  src/components/my-app/my-app.spec.ts
  ● Console

    console.error node_modules/@stencil/core/dist/testing/index.js:7268
      Error: Cannot read property 'setStore' of undefined
      TypeError: Cannot read property 'setStore' of undefined
          at MyApp.Object.<anonymous>.MyApp.componentWillLoad (/home/linne/Projects/stencil-playground/src/components/my-app/my-app.tsx:14:20)
          at update (/home/linne/Projects/stencil-playground/node_modules/@stencil/core/dist/testing/index.js:896:48)
          at plt.queue.tick (/home/linne/Projects/stencil-playground/node_modules/@stencil/core/dist/testing/index.js:854:34)
          at flush (/home/linne/Projects/stencil-playground/node_modules/@stencil/core/dist/testing/index.js:445:33)
          at _combinedTickCallback (internal/process/next_tick.js:131:7)
          at process._tickCallback (internal/process/next_tick.js:180:9)
          at TestWindow.<anonymous> (/home/linne/Projects/stencil-playground/node_modules/@stencil/core/dist/testing/index.js:9525:27)
          at Generator.next (<anonymous>)
          at fulfilled (/home/linne/Projects/stencil-playground/node_modules/@stencil/core/dist/testing/index.js:9461:58)
          at <anonymous>
          at process._tickCallback (internal/process/next_tick.js:188:7)

This is obviously raised from the componentWillLoad look that attempts to configure the store. I'm not sure why this happens, since everything works fine in the other modes. I might need to somehow inject the store as a property in another way, but I did not find any clue how, especially since this needs to happen before componentWillLoad is called.

The repo can be found here:
https://github.com/DorianGrey/stencil-playground

Steps to reproduce

  • Clone the repository above
  • yarn for installing dependencies
  • yarn test will raise errors like mentioned above.

Further notes

  • An additional error will raise, complaining about TypeError: Cannot read property 'queue' of undefined. This seems to be raised when calling flush in the unit tests, and no longer occurs when adding a workaround for the store-stuff.
  • The "workaround" would be to prevent any calls to or on the store variable in case it is not defined, but that's somewhat unlikely, esp. when attempting to test a component under more realistic circumstances.
  • I've tried to get additional hints from the stencil-redux-demo, but its tests are raising the same problem (at least after fixing the wrong imports...).
  • €dit: I've tried to adjust the Context object manually as well, however it seems to be absent during the tests.

Stencil Redux not functioning anymore after upgrading to Stencil One

Stencil version:

I'm submitting a:

[X] bug report
[ ] feature request
[ ] support request => Please do not submit support requests here, use one of these channels: https://stencil-worldwide.herokuapp.com/ or https://forum.ionicframework.com/

Current behavior:
Components using redux code do not function correctly anymore using @stencil/redux after upgrading to Stencil One. The store seems to be returning undefined.

Expected behavior:
The store should be defined when initiating with the predefined setStore(store) method.

Steps to reproduce:
Create a store with reducers and initiate the store in a root component.

Related code:
store/index.ts

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import reducers from '../reducers/index';
import { composeWithDevTools } from 'redux-devtools-extension/developmentOnly';

const configureStore = (preloadedState: any) =>
  createStore(
    reducers,
    preloadedState,
    composeWithDevTools(applyMiddleware(logger, thunk)),
  );

export { configureStore };

Component

import { Component, Prop } from "@stencil/core";
import { Store } from "@stencil/redux";
import { configureStore } from "../../redux/store/index";

@Component({
  tag: "my-test",
  shadow: true
})
export class MyTestComponent {
  @Prop({ context: "store" }) store: Store;

  componentWillLoad() {
    // Configure store once in this root component
    this.store.setStore(configureStore({}));
  }

  render() {
    return (
      <div></div>
    )
  }
}

Other information:
Screenshot 2019-05-22 at 16 48 27

unit tests not working when importing @stencil/redux

I have this issue that when I try to unit test my component I get errors when that component imports @stencil/redux

To create this test project I just did

$> npm init stencil

I installed redux and @stencil/redux so my package.json looks like

{
  "name": "error",
  "version": "0.0.1",
  "description": "Stencil Component Starter",
  "main": "dist/index.js",
  "module": "dist/index.mjs",
  "es2015": "dist/esm/index.mjs",
  "es2017": "dist/esm/index.mjs",
  "types": "dist/types/index.d.ts",
  "collection": "dist/collection/collection-manifest.json",
  "collection:main": "dist/collection/index.js",
  "unpkg": "dist/error/error.js",
  "files": [
    "dist/",
    "loader/"
  ],
  "scripts": {
    "build": "stencil build --docs",
    "start": "stencil build --dev --watch --serve",
    "test": "stencil test --spec --e2e",
    "test.watch": "stencil test --spec --e2e --watchAll",
    "generate": "stencil generate"
  },
  "devDependencies": {
    "@stencil/core": "^1.8.4",
    "@types/jest": "24.0.25",
    "@types/puppeteer": "1.20.3",
    "jest": "24.9.0",
    "jest-cli": "24.9.0",
    "puppeteer": "1.20.0"
  },
  "license": "MIT",
  "dependencies": {
    "@stencil/redux": "^0.1.2",
    "redux": "^4.0.5"
  }
}

This is what my-component.tsx looks like:

import { Component, h } from '@stencil/core';
import '@stencil/redux';

@Component({
    tag: 'my-component',
    styleUrl: 'my-component.css',
    shadow: true
})
export class MyComponent {
    render() {
        return <div>Hello, World!</div>;
   }
}

And the my-component.spec.ts file

import { MyComponent } from "./my-component";

describe("my-component", () => {
    it("builds", () => {
        expect(new MyComponent()).toBeTruthy();
    });
});

Thats it, now when I run the test I get the following error:

$> yarn test
yarn run v1.21.1
$ stencil test --spec --e2e
[49:05.0]  @stencil/core v1.8.6 🚚
[49:05.1]  testing e2e and spec files
[49:05.1]  build, error, dev mode, started ...
[49:05.2]  transpile started ...
[49:05.2]  transpile finished in 5 ms
[49:05.2]  copy started ...
[49:05.2]  generate styles started ...
[49:05.2]  bundling components started ...
[49:05.3]  generate styles finished in 52 ms
[49:06.0]  copy finished (0 files) in 749 ms
[49:06.0]  bundling components finished in 744 ms
[49:06.0]  build finished in 908 ms

[49:06.1]  jest args: --e2e --spec --max-workers=16
 PASS  src/utils/utils.spec.ts
 FAIL  src/components/my-component/my-component.spec.ts
  ● Test suite failed to run

    Cannot find module '../esm/index.js' from 'index.js'

    However, Jest was able to find:
        './my-component.css'
        './my-component.spec.ts'
        './my-component.tsx'

    You might want to include a file extension in your import, or update your 'moduleFileExtensions', which is currently ['ts', 'tsx', 'js', 'jsx', 'json'].

    See https://jestjs.io/docs/en/configuration#modulefileextensions-array-string

      at Resolver.resolveModule (node_modules/jest-resolve/build/index.js:259:17)
      at Object.<anonymous> (node_modules/@stencil/redux/dist/index.js:2:18)

Test Suites: 1 failed, 1 passed, 2 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        0.677s, estimated 1s
Ran all test suites.

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

If I remove import '@stencil/redux'; from my-component it all works again like a charm

This error show similarities with this issue

dist/index.js file is referencing incorrect path

When building this library using npm run build the dist/index.js file that is output looks like the following:

// stencilredux: CommonJS Main
module.exports = require("../esm/index.js");

Since it lives in the same folder as the esm folder I think it should look like:

// stencilredux: CommonJS Main
module.exports = require("./esm/index.js");

Unsure if this is a configuration issue, I can't figure out how these paths are being generated.

Import declaration conflicts with local declaration of 'Action'.

Hi Getting this compilation error with stencil-redux 0.1.1 and 0.1.0

TypeScript: node_modules/@stencil/redux/dist/types/global/interfaces.d.ts:1:10
           Import declaration conflicts with local declaration of 'Action'.

      L1:  import { Action, AnyAction, Store as ReduxStore, Unsubscribe } from 'redux';
      L2:  export interface Store<S = any, A extends Action = AnyAction> {

on removing

export declare type Action = (...args: any[]) => any;

from node_modules/@stencil/redux/dist/types/global/interfaces.d.ts
It got compiled.

Here is my .tsconfig.json

{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "allowUnreachableCode": false,
    "declaration": false,
    "experimentalDecorators": true,
    "lib": [
      "dom",
      "es2017"
    ],
    "moduleResolution": "node",
    "module": "esnext",
    "target": "es2016",
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "jsx": "react",
    "jsxFactory": "h"
  },
  "include": [
    "src",
    "types/jsx.d.ts"
  ],
  "exclude": [
    "node_modules"
  ]
}

Could you please help us with this. And please let me know if any other information is required.

Discussion on Stencil-Redux future implementation

For the future of stencil-redux, I suggest to Impement a Decorator named Store, this should only be an adapter. Stencil-redux would use this adapter.

Would be awesome to have a syntax like this as soon as redux is available to a stencil project.

@Store({ name: 'default', action: this.documentChange, type: 'redux' }) document: any = {};

Each member decorated with @Store shares the same store. Action is associated with the redux action. @Store Decorator would inherit behaviour from @State. Name could be the name of the reducer.

Having the type as an optional parameter (default = 'redux') could allow to further define custom stores. Maybe a dynamic store for firebase could allow for a simple change like eg.

@Store({ name: 'default', action: this.documentChange, type: 'firestore' }) document: any = {};

Name could be the name of the database document.

Originally posted by @stanley85 in #70 (comment)

Multiple stores don't work

The way stencil-redux is implemented today allows only one store. Which makes totally sense for a single page application and is also the recommended approach by Redux itself.
However, when using Stencil to build a component library, where components can be consumed independently from each other, all of them need their own store (since they are an app themselves).

can't use redux-thunk's withExtraArgument mechanism with @stencil/redux

Since @stencil/redux has the peer dependency, I expectd that I could use redux middle where Thunk.

However, the MapDispatchToProps method is not handling the middleware's ability to inject another parameter into the delegate.

for example. normal delegate actions looks like:

export function executeAction() {
  return (dispatch, getStore) {
   dispatch({type: MY_ACTION, ...});
  };
}

and Thunk describes a feature withExtraArgument which should allow me to include my API Client the same way the getStore and dispatch actions are provided:

export function executeAction() {
  return (dispatch, getStore, apiClient) {
   dispatch({type: MY_ACTION, ...});
  };
}

However the Stencil code is ignoring this middleware.... I was wondering why this doesn't work....

Using <slot /> ?

As soon as using <slot />, the this.store.setStore() won't work anymore, because the slots componentWillLoad() hook will be executed before the parents componentWillLoad(), e.g.

<!-- index.html -->
<my-component> <!-- second here, setStore() happens here, too late -->
    <my-child-component /> <!-- first here, this already wants the store -->
</my-component>

This will throw an error:

TypeError: Cannot read property 'subscribe' of undefined
at Object.mapStateToProps

Does anyone have a nice and clean solution for this?
There already is a workaround, but this looks rather hacky to me: https://medium.com/@michakaliszewski/stencil-redux-slot-components-setup-d5efce091842
ionic-team/stencil#1261

Latest build fails with @stencil/core 1.8.0

This is the counterpart issue to ionic-team/stencil#2015

I'm submitting a:
[X] bug report

Current behavior:
Updating @stencil/core from 1.7.5 to 1.8.0 breaks compile for stencil-redux

Expected behavior:
Please add some ionic love to stencil-redux and up the core - its 0.18

Steps to reproduce:
Add stencil-redux to a new @stencil/core ^1.8.0 project

Other information:
TypeScript
Import declaration conflicts with local declaration of 'Action'.

./node_modules@stencil\redux\dist\types\global\interfaces.d.ts

import { Action, AnyAction, Store as ReduxStore, Unsubscribe } from 'redux';
export interface Store<S = any, A extends Action = AnyAction> {

mapDispatchToProps is wrong

The implementation of mapDispatchToProps seems to be wrong.

I think this is more appropriate implementation.

function mapDispatchToProps(component: any, props: any) {
    Object.keys(props).forEach(actionName => {
      const action = props[actionName];
      Object.defineProperty(component, actionName, {
        get: () => (...args: any[]) => _store.dispatch(action(...args)),
        configurable: true,
        enumerable: true
      })
    });
  }

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.