cycle-reactdom-driver
(un)official ReactDOM driver for Cycle.js
installation
npm install --save @sunny-g/cycle-reactdom-driver
usage
Basic usage with the Cycle.js counter example:
import makeReactDOMDriver from '@sunny-g/cycle-reactdom-driver';
function main(sources) {
// `sources.REACT` ... ???
const incrementReducer$ = sources.REACT
.events('increment')
.map(_ =>
({ count }) => ({ count: count + 1 })
);
const decrementReducer$ = sources.REACT
.events('decrement')
.map(_ =>
({ count }) => ({ count: count - 1 })
);
const reducer$ = xs.merge(
incrementReducer$,
decrementReducer$
);
const props$ = reducer$
.fold((state, reducer) => reducer(state), { count: initialCount });
// the sink is a stream of a rendered React elements
const vtree$ = props$
.map(({ count }) =>
<div>
<button
key="decrement"
onClick={sources.REACT.handler('decrement')}
>
Decrement
</button>
<button
key="increment"
onClick={sources.REACT.handler('increment')}
>
Increment
</button>
<p>{`Counter: ${count}`}</p>
</div>
);
return {
REACT: vtree$,
};
}
run(main, {
REACT: makeReactDOMDriver('#main-container'),
});
Alternatively, you can choose to ignore the state source and Redux store altogether and manage the state yourself (with onionify or something similar).
In this next example, we'll map our actions to reducers, and fold them into local state manually:
// same imports as before
function main(sources) {
const incrementReducer$ = sources.REDUX.action
.select(INCREMENT)
.map(({ payload }) =>
({ count }) =>
({ count: count + payload })
);
const decrementReducer$ = sources.REDUX.action
.select(DECREMENT)
.map(({ payload }) =>
({ count }) =>
({ count: count - payload })
);
// if using `onionify`, `stanga` or something similar,
// return `reducercycle-reactdom-driver
(un)official ReactDOM driver for Cycle.js
installation
npm install --save @sunny-g/cycle-reactdom-driver
usage
Basic usage with the Cycle.js counter example:
import makeReactDOMDriver from '@sunny-g/cycle-reactdom-driver';
function main(sources) {
// `sources.REACT` ... ???
const incrementReducer$ = sources.REACT
.events('increment')
.map(_ =>
({ count }) => ({ count: count + 1 })
);
const decrementReducer$ = sources.REACT
.events('decrement')
.map(_ =>
({ count }) => ({ count: count - 1 })
);
const reducer$ = xs.merge(
incrementReducer$,
decrementReducer$
);
const props$ = reducer$
.fold((state, reducer) => reducer(state), { count: initialCount });
// the sink is a stream of a rendered React elements
const vtree$ = props$
.map(({ count }) =>
<div>
<button
key="decrement"
onClick={sources.REACT.handler('decrement')}
>
Decrement
</button>
<button
key="increment"
onClick={sources.REACT.handler('increment')}
>
Increment
</button>
<p>{`Counter: ${count}`}</p>
</div>
);
return {
REACT: vtree$,
};
}
run(main, {
REACT: makeReactDOMDriver('#main-container'),
});
Alternatively, you can choose to ignore the __INLINE_CODE_0__ source and Redux store altogether and manage the state yourself (with onionify or something similar).
In this next example, we'll map our actions to reducers, and __INLINE_CODE_1__ them into local state manually:
and map your provided state source stream to `vdomcycle-reactdom-driver
(un)official ReactDOM driver for Cycle.js
installation
npm install --save @sunny-g/cycle-reactdom-driver
usage
Basic usage with the Cycle.js counter example:
import makeReactDOMDriver from '@sunny-g/cycle-reactdom-driver';
function main(sources) {
// `sources.REACT` ... ???
const incrementReducer$ = sources.REACT
.events('increment')
.map(_ =>
({ count }) => ({ count: count + 1 })
);
const decrementReducer$ = sources.REACT
.events('decrement')
.map(_ =>
({ count }) => ({ count: count - 1 })
);
const reducer$ = xs.merge(
incrementReducer$,
decrementReducer$
);
const props$ = reducer$
.fold((state, reducer) => reducer(state), { count: initialCount });
// the sink is a stream of a rendered React elements
const vtree$ = props$
.map(({ count }) =>
<div>
<button
key="decrement"
onClick={sources.REACT.handler('decrement')}
>
Decrement
</button>
<button
key="increment"
onClick={sources.REACT.handler('increment')}
>
Increment
</button>
<p>{`Counter: ${count}`}</p>
</div>
);
return {
REACT: vtree$,
};
}
run(main, {
REACT: makeReactDOMDriver('#main-container'),
});
Alternatively, you can choose to ignore the __INLINE_CODE_0__ source and Redux store altogether and manage the state yourself (with onionify or something similar).
In this next example, we'll map our actions to reducers, and __INLINE_CODE_1__ them into local state manually:
const reducer$ = incrementReducer$
.merge(decrementReducer$);
const props$ = reducer$
.fold((state, reducer) =>
reducer(state),
{ count: initialCount });
// same vdom$ as before
const vdom$ = props$
.map(({ count }) =>
div([
button('.decrement', 'Decrement'),
button('.increment', 'Increment'),
p('Counter: ' + count)
])
);
// same action$ sink as before
const action$ = xs.of({
[DECREMENT]: sources.DOM
.select('.decrement')
.events('click')
.map(_ => decrement(1)),
[INCREMENT]: sources.DOM
.select('.increment')
.events('click')
.map(_ => increment(1)),
});
return {
DOM: vdom$,
REDUX: action$,
};
}
run(main, {
DOM: makeDOMDriver('#main-container'),
REDUX: makeReduxDriver(), // not technically using Redux anymore, so pass no arguments to drivers
});
api
makeReduxDriver
parameters:
reducer?: Reducer<any>: The same Redux reducer you'd pass intocreateStoreinitialState?: any: The same Redux initial state you'd pass intocreateStoreactionsForStore?: string[]: List of action types that could result in astore's state change- every action stream is lazy by default (unless
selected within your application) - therefore, in order to preserve as much laziness as possible, we use this array to inform the driver to (eagerly) subscribe to and funnel into the
storeonly the action streams that contribute to Redux state
- every action stream is lazy by default (unless
middlewares?: Middleware[]: The same Redux middlewares you'd pass intocreateStore
NOTE: All parameters are optional in case you only want to use the action source.
Example:
run(main, {
// ... other drivers
REDUX: makeReduxDriver(
reducer,
{ count: initialCount },
[ INCREMENT, DECREMENT ],
[],
),
});
redux.action source
redux.action.select(type?: string): ActionStream | ActionSink
parameters:
type?: string: A stream that emits action objects of the specified bytype
returns:
Stream<FSA> | Stream<{ [type: string]: Stream<FSA> }>: A stream of FSA-compliant action objects- NOTE: the
metaproperty of the action object is an object with the key'$CYCLE_ACTION_SCOPE'- this key is required for Cycle.js isolation - if
typewas omitted, the stream returned is the rawActionSinkthat was passed into the driver so that you can create your own custom action streams
- NOTE: the
Example:
const incrementReducer$ = sources.REDUX.action
.select(INCREMENT)
.map(({ type, payload, error, meta }) =>
({ count }) => ({ count: count + payload })
);
redux.state source
redux.state.select(): StateStream<any>
returns:
MemoryStream<any>: A stream that emits the Redux store's currentstateevery time the state has changed
Example:
const state$ = sources.REDUX.state
.select();
redux sink: ActionSink
should return:
Stream<{ [type: string]: Stream<FSA> }>: A stream of objects, where each key is a specific actiontypeand each value is the stream that emits action objects of thattype.
Example:
// INCREMENT, DECREMENT are action type constants
// increment, decrement are action creators
return {
// ... other sinks...
REDUX: of({
[DECREMENT] : sources.DOM
.select('.decrement')
.events('click')
.map(_ => decrement(1)),
[INCREMENT] : sources.DOM
.select('.increment')
.events('click')
.map(_ => increment(1)),
}),
};
helpers
createReducer(initialState: any, reducers: { [type: string]: Reducer })
Combines a set of related reducers into a single reducer
parameters:
initialState: any: The initial state of a Redux "state machine"reducers: { [type: string]: Reducer }: An object whose keys are the actiontypes and the values are thereducers that respond to those actions and whose signature is(state: any, action: FSA) => any
returns:
- a combined
reducerof the same aformentioned signature
makeActionCreator(type: string)
Creates a shorthand function for creating action objects
parameters:
type: string: The actiontypeof the action object
returns:
actionCreator: (payload: any, error: bool = false, meta: object = {}) => FSA: A function that creates FSA-compliant action objects with the propertiestype,payload,error, andmeta
contributing
todo
- ensure typescript typings are correct and comprehensive and exported correctly
- refactor implementation to not require
reduxif not using the state source - add testing mock action and state sources
- explain contribution process
- add more tests :)
- explain why I wrote this
license
ISC