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
import { combineReducers } from 'redux';
import { TodoState, todos } from './todos/reducers';
export interface RootState {
todos: TodoState;
}
export const rootReducer = combineReducers({
todos,
});
Configure the Actions
import { RootState } from './reducers';
import { TodoAction } from './todos/actions';
export * from './todos/actions';
export type Action = (
TodoAction
);
Configure the Store
import { Store, applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import { RootState, rootReducer } from './reducers';
export const store: Store<RootState> = createStore(rootReducer, applyMiddleware(thunk, logger));
Configure Store in Root Component
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
:memo: 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.
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
import { ThunkAction } from 'redux-thunk';
export const changeName = (name: string): ThunkAction<Promise<void>, RootState, void, Action> => async (dispatch, getState) => {
await fetch(...);
};
In the component below, the type of this.changeName
is extracted from the action type to be (name: string) => Promise<void>
.
import { changeName } from '../../redux/actions';
export class MyComponent {
changeName!: Unthunk<typeof changeName>;
componentWillLoad() {
store.mapDispatchToProps(this, { changeName });
}
}