Comments (18)
One alternative is to manually use isolateSource
/isolateSink
to have more control.
from cycle-onionify.
Well, in more complex example there would be more drivers, so it would mean reimplementing isolate
from cycle-onionify.
Maybe a higher order component to allow communication between the slider and the inputs? Only problem I see is that the state would be a memory stream, not ideal for a circular dependency.
from cycle-onionify.
Maybe onionify could use some prefix in isolation key? like isolate(Component, 'onion-foo')
. It would be more writing, but also would allow the cases where the component tree doesn't fully replicate the state tree
from cycle-onionify.
Maybe a higher order component to allow communication between the slider and the inputs? Only problem I see is that the state would be a memory stream, not ideal for a circular dependency.
With onionify there wouldn't be a need to make the circular dependency. Or are you thinking that would be a product of the higher order component?
Maybe onionify could use some prefix in isolation key? like isolate(Component, 'onion-foo'). It would be more writing, but also would allow the cases where the component tree doesn't fully replicate the state tree
I'd prefer not to have any magic going on. Here's it written down all manually:
function main(sources) {
const rangePickerSources = {
DOM: sources.DOM.isolateSource(sources.DOM, 'RangePicker'),
HTTP: sources.HTTP.isolateSource(sources.HTTP, 'RangePicker'),
onion: sources.onion.isolateSource(sources.onion, 'range'),
};
const rangePickerSinks = RangePicker(rangePickerSources);
const isolatedRangePickerSinks = {
DOM: sources.DOM.isolateSource(rangePickerSinks, 'RangePicker'),
HTTP: sources.HTTP.isolateSource(rangePickerSinks, 'RangePicker'),
onion: sources.onion.isolateSource(rangePickerSinks, 'range'),
};
const fromSources= {
DOM: sources.DOM.isolateSource(sources.DOM, 'From'),
HTTP: sources.HTTP.isolateSource(sources.HTTP, 'From'),
onion: sources.onion.isolateSource(sources.onion, 'range'),
};
const fromSinks = From(fromSources);
const isolatedFromSinks = {
DOM: sources.DOM.isolateSource(fromSinks, 'From'),
HTTP: sources.HTTP.isolateSource(fromSinks, 'From'),
onion: sources.onion.isolateSource(fromSinks, 'range'),
};
const toSources= {
DOM: sources.DOM.isolateSource(sources.DOM, 'To'),
HTTP: sources.HTTP.isolateSource(sources.HTTP, 'To'),
onion: sources.onion.isolateSource(sources.onion, 'range'),
};
const toSinks = To(toSources);
const isolatedToSinks = {
DOM: sources.DOM.isolateSource(toSinks, 'To'),
HTTP: sources.HTTP.isolateSource(toSinks, 'To'),
onion: sources.onion.isolateSource(toSinks, 'range'),
};
// ... combine child sinks together
return sinks;
}
And we could also enhance isolate
to support a function that runs for each sink/source type to choose what scope to use:
function main(sources) {
const chooseRangePickerScope = key =>
key === 'onion' ? 'range' : 'RangePicker';
const rangePickerSinks = isolate(RangePicker, chooseRangePickerScope)(sources);
const chooseFromScope = key =>
key === 'onion' ? 'range' : 'From';
const fromSinks = isolate(From, chooseFromScope)(sources);
const chooseToScope = key =>
key === 'onion' ? 'range' : 'To';
const toSinks = isolate(To, chooseFromScope)(sources);
// ... combine child sinks together
return sinks;
}
We need to consider it a bit more carefully. I vaguely remember @axefrog or someone else talking about "isolate only this one" type of feature.
from cycle-onionify.
Oh, actually I just found a better idea: pass state$
, not the onion StateSource
down to the children, and manually isolate their sinks (the reducer from each child):
function main(sources) {
const childrenSources = {
...sources,
state: sources.onion.isolateSource(sources.onion, 'range').state$,
};
const rangePickerSinks = isolate(RangePicker)(childrenSources);
const fromSinks = isolate(From)(childrenSources);
const toSinks = isolate(To)(childrenSources);
const childrenReducer$ = xs.merge(
sources.onion.isolateSink(rangePickerSinks.onion, 'range'),
sources.onion.isolateSink(fromSinks.onion, 'range'),
sources.onion.isolateSink(toSinks.onion, 'range')
);
// ... combine child sinks together
return sinks;
}
from cycle-onionify.
pass state$, not the onion StateSource
sounds ok
const fromSinks = isolate(From)(childrenSources);
const childrenReducer$ = xs.merge(
sources.onion.isolateSink(fromSinks.onion, 'range')
);
We have a double isolation here. Are you sure it will work as expected? I think, the children should just return reducers
sink here, which will be merged into parent's onion
sink
from cycle-onionify.
Yes it will work, because fromSinks.onion
is not isolated. sources.onion.isolateSink
function is not available for the child component, because we didn't give sources.onion
to the child.
UPDATE:
Ok, actually, we did pass sources.onion
to the children (through the object spread). In that case we could have each child return reducers under the key reducer
and not onion
. Or some variation of this idea.
from cycle-onionify.
I tried creating a webpackbin but it wouldn't let me. I created a very simple example (I like those) with two components:
- Incrementer
- Double
The components are siblings and I would like to pass output from the Incrementer into Double. Can someone explain best practices for that when using onionify?
Since webpackbin is not working I will pase code here:
import { run } from '@cycle/xstream-run';
import { button, div,
makeDOMDriver } from '@cycle/dom';
import xs from 'xstream';
import isolate from '@cycle/isolate';
import onionify from 'cycle-onionify';
function Double(sources) {
// This component should:
// 1. take data from Increment,
// 2. double it and store it as state ({ value })
// 3. displays result
// What is recommend way to do that with onionify?
const state$ = sources.onion.state$;
const fnInit$ = xs.of(() => ({ value : 0 }));
const reducer$ = xs.merge(fnInit$);
const vdom$ = state$
.map(state => div([
div('Double : ' + state.value.toString()),
]));
return {
DOM : vdom$,
onion : reducer$
}
}
function Incrementer(sources) {
const state$ = sources.onion.state$;
const action$ = sources.DOM
.select('.inc')
.events('click');
const fnInit$ = xs.of(() => ({ count : 0 }));
const fnInc$ = action$.mapTo(state => ({ count : state.count + 1 }));
const reducer$ = xs.merge(fnInit$, fnInc$);
const vdom$ = state$.map(state => div([
div(state.count.toString()),
button('.inc', '+')
]));
return {
DOM : vdom$,
onion : reducer$
}
}
function main(sources) {
const state$ = sources.onion.state$;
const fnInit$ = xs.of(() => ({ }));
const incrementer = isolate(Incrementer, 'inc')(sources);
const double = isolate(Double, 'double')(sources);
const reducer$ = xs.merge(fnInit$, incrementer.onion, double.onion);
const vdom$ = xs.combine(state$, incrementer.DOM, double.DOM)
.map(([ state, incrementer, double ]) => div([
incrementer,
double,
div(JSON.stringify(state)),
]));
return {
DOM: vdom$,
onion: reducer$,
};
}
const wrappedMain = onionify(main);
run(wrappedMain, {
DOM: makeDOMDriver('#app')
});
from cycle-onionify.
@mspoulsen I think Double
doesn't need any reducer because it's not modifying state. Then you can just pass count$
instead of the onion state source.
function main(sources) {
const state$ = sources.onion.state$;
const fnInit$ = xs.of(() => ({ }));
const incrementer = isolate(Incrementer, 'inc')(sources);
const count$ = state$.map(state => state.count);
const double = isolate(Double, 'double')({DOM: sources.DOM, count$}); // this
const reducer$ = xs.merge(fnInit$, incrementer.onion);
const vdom$ = xs.combine(state$, incrementer.DOM, double.DOM)
.map(([ state, incrementer, double ]) => div([
incrementer,
double,
div(JSON.stringify(state)),
]));
return {
DOM: vdom$,
onion: reducer$,
};
}
And Double
function Double(sources) {
const vdom$ = sources.count$
.map(count => count * 2)
.map(count => div([
div('Double : ' + count.toString()),
]));
return {
DOM : vdom$,
}
}
from cycle-onionify.
Ok, so I map state and pass that into Double. That was the pattern I was unsure about. In my real app Double will have reducers, but I will try this first and see where that gets me. Thanks a lot! :)
from cycle-onionify.
@staltz i guess it should be const incrementer = isolate(Incrementer, 'count')(sources);
from cycle-onionify.
This is a more accurate example. I would like to pass into Double the value from Incrementer, then I want to apply some logic and store some derived data as state inside Double and expose it for other components to read from.
Here I run into a loop, but I am not sure what I am doing wrong:
https://gist.github.com/mspoulsen/c5bea0f1337e3058e82f91815b1cc123
from cycle-onionify.
The loop starts here: https://gist.github.com/mspoulsen/c5bea0f1337e3058e82f91815b1cc123#file-onionify-js-L23
Here you change the state when the state changes (and remove state.Incrementer
, but that's another issue)
Just do double$ = count$.map(c => c * 2)
and use it. If you want to share it, declare it on the closest common ancestor component
from cycle-onionify.
You mean something like this: https://esnextb.in/?gist=7bc7670a8212bf1ee939f33a1122b4b4 ?
It works fine. I am not very familiar with single source of truth trees. I guess we don't store derived data in them anyway? If this is the way to go then everything is dandy :)
Update: Hmm...some issue with link. Working now.
from cycle-onionify.
Link updated. Was linking to previous example before (just for clarity): https://esnextb.in/?gist=7bc7670a8212bf1ee939f33a1122b4b4
from cycle-onionify.
I guess we don't store derived data in them anyway
Yes, it's a good idea to keep derived data away from state tree. If the deriving process is expensive, you may consider memoization
from cycle-onionify.
I believe issue #11 solves this very well now
from cycle-onionify.
Related Issues (20)
- pickCombine fails when re-adding item with same key HOT 1
- Possible to not emit until default reducer gets run? HOT 7
- Rename lens getter/setter HOT 8
- Help needed - MemoryStream.map not producing output HOT 1
- Add mock-onionify HOT 4
- Remove the .vscode folder
- Why don't provide 'pick' and 'mix' functions in xstream? HOT 4
- Shouldn't collections docs be in readme as well as release notes? HOT 2
- Add ES6 module build
- "Reducer" term is not correct HOT 1
- pickMerge throws error if child is not using sink HOT 1
- pickMerge seems to swallow events HOT 6
- type MakeScopesFn does not exist but imported HOT 1
- Shouldn't state emissions be microtask queued? HOT 6
- emitting `xs.never` with pickCombine might be wrong HOT 13
- Action stream is probably a better definition than `reducer` stream. HOT 4
- onionify typings assume "onion" as key HOT 1
- cycle-onionify when can update for rxjs@6
- cannot compile typescript examples HOT 1
- Define Omit<T, K> properly? HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from cycle-onionify.