An opinionated way to build frontend applications. Works nicely with Warped Reducers.
Warped Components requires React 16.8.3 or later
$ npm install --save warped-components react xstream
Warped Components does two things to facilitate your application:
mapStateToProps
and mapDispatchToProps
functions, it takes
a selectors
object whose values are functions that map the state to
individual properties, and an actions
object whose values are
functions that given a payload, return a Redux Standard Action.This approach has the following benefits:
module.hot
, your reducer logic and side-effect
logic is also automatically hot-reloaded (given that you don't remount
the WarpedApp, but only its children).warped :: WarpedOptions -> ReactComponent -> ReactComponent
Does zero to two distinct things to a component, depending on the options:
The options (all optional, though at least one should be provided) are:
selectors
: An object whose keys correspond to properties, and values
are functions that receive the state and return the value for the
property.actions
: A hash-map of action creators. The underlying component will
receive them as props, and when called, the resulting action is
dispatched to the store.reducer
: A Redux reducer - a function which takes a state and an
action, and returns a new state. Warped Components makes sure that
whenever the connected component is mounted, this reducer will act as
part of the reducers of your store.effects
: A Cycle application - a function which takes a mapping of
stream / stream-producers, and returns a mapping of streams.If you would just like the reducer or effects to be mounted without a
"view", for example when a reducer is in change of handling some state
shared between multiple components, then pass null
as the ReactComponent.
The returned ReactComponent can still be rendered anywhere in the tree to
have its reducer and/or effects be mounted.
For the definition of actions
and reducer
, we recommend using
Warped Reducers, and for the definition of the selectors, we highly
recommend using an Optics library like Ramda's lens
related
functions or partial.lenses.
import {warped} from 'warped-components';
import {createReducer, noopAction} from 'warped-reducers';
import {lensProp, compose, set, view} from 'ramda';
import React from 'react';
// We use a lens to describe our slice of the global state.
// How you do it is up to you, though.
export const dataState = compose (
lensProp ('app'),
lensProp ('data')
);
// We use warped-reducers to create our reducer and actions.
export const {types, actions, reducer} = createReducer ('App') ({
loadData: noopAction,
setData: set (dataState)
});
// A small Cycle app describes the side-effects of our component.
export const effects = ({action, http}) => ({
http: action.filter (({type}) => type === types.loadData).mapTo ({
url: 'https://api.github.com/users/Avaq',
category: types.loadData
}),
action: http.select (types.loadData).flatten ().map (({body: {name}}) =>
actions.setData (name)
)
});
// The selectors are used to map the global state to component props.
export const selectors = {
data: view (dataState)
};
// This is our view.
export const App = ({data, loadData}) => (
<div>
<h1>{data || 'name unknown'}</h1>
<button onClick={loadData}>Load!</button>
</div>
);
// Warped Components wires the view to all of the above.
export default warped ({reducer, effects, selectors, actions}) (App);
WarpedApp :: ReactComponent
This component does the wiring for your application:
reducer
as needed.main
as needed.state
: A read-only driver exposing a memory stream of the latest
Redux state.action
: A read-write driver exposing a stream of dispatched
actions and allowing other actions to be dispatched.It takes the following optional props:
initialState
: Some initial state for your store.enhancer
: Store enhancer, allows middleware, debug tooling, etcetera.drivers
: Cycle drivers determine what kind of effects can occur.import {WarpedApp} from 'warped-components';
import {devToolsEnhancer} from 'redux-devtools-extension';
import {makeHTTPDriver} from '@cycle/http';
import {render} from 'react-dom';
import React from 'react';
import App from './my-app';
const drivers = {http: makeHTTPDriver ()};
render (
<WarpedApp enhancer={devToolsEnhancer ()} drivers={drivers}>
<App />
</WarpedApp>,
document.getElementById ('app')
);
If you prefer using React Redux and Redux directly, rather than
using the WarpedApp
, you can use these utilities to ease
the interaction with Warped Reducers.
compileSelectors :: StrMap ((a, b) -> c) -> (a, b) -> StrMap c
Given a mapping of selectors, returns a mapStateToProps
function, as
accepted by connect
from React Redux.
The selectors are given the state (and previous props), and are expected
to return a slice of the state. We recommend using Optics, such as the
lens
-related functions from Ramda, to create the selectors.
compileDispatchers :: StrMap (a -> b) -> (b -> c) -> StrMap (a -> c)
Given a mapping of action creators, as returned from
createReducer, returns a mapDispatchToProps
function,
as accepted by connect
from React Redux.
combineReducers :: Array ((a, b) -> a) -> (a, b) -> a
Given an array of reducers, returns a single reducer which transforms the state by calling all reducers in sequence.
combineCycles :: Array (StrMap Any -> StrMap Stream) -> StrMap Any -> StrMap Stream
Given an array of main
functions that take sources and return sinks,
returns a single main
function which combines the effects of each.