Open dumconstantin opened 8 years ago
Hello @dumconstantin. If you need to access paths that were edited you should listen to the tree's update events. They hold this information.
Hey @Yomguithereal, yeah, I tried doing that but then I don't know how to link the monkey update to the tree update because neither knows about the other.
I have a listener on a monkey (that may have other monkeys in its dependency) and a list of paths I don't want the updateFn to be triggered if the changed path is in the list.
My actual use case is like:
let tree = new Baobab({
a: {
prop1: { ... },
prop2: 123,
prop3: { ... },
prop4: 'asd'
},
b: monkey(['a'], identity)
})
tree.select('b').on('update', e => {
let exclude = ['prop1', 'prop3']
if (-1 === exclude.indexOf(e.target.actualPropChange)) {
doAction()
}
})
// This is the type of action I don't want to do anything
tree.select('a', 'prop1', 'deepProp').set(321)
// But I want to react to this
tree.select('a', 'prop2').set(321)
Would having the transaction details of the update in the cursor update event payload help you?
Yes, quite a lot actually.
Knowing exactly what change triggered the monkey to update makes reasoning about the data structure much easier.
Is there a way to achieve that?
I might add the information to the cursor's payload data then. Just need some time to think about it to see whether it's really relevant or not.
That would be great, I really need it for an app I'm currently building. Do you need for me to describe better my current setup and rationale behind this request?
Yes. This would be nice.
Sure.
This is what I have at the moment:
// using RamdaJS for the functions
let monkey = Baobab.monkey
let tree = new Baobab({
entities1: {},
entities2: {},
entity2ByEntity1: monkey(['entities2'], compose(map(indexBy(prop('id'))), groupBy(prop('entity1_id')), values)
visible: {
entity1: monkey(['entities1'], filter(propEq('visible', true)),
entity2: monkey(['visible', 'entity1'], ['entity2ByEntity1'], compose(mergeAll, values, useWith(pick, [keys, values]))
}
})
// I'm using RiotJS and I made a cursor mixin that allows
// Riot components to subscribe to the baobab state.
// dataCursor:: string (namespace) -> { propName: baobab path }
// The mixin receives an update event, sets the new state on the Riot component
// and then triggers the component to update
<entity2>{ props ... }</entity2>
<entity1>
<entity2 each={ data.entities2 }></entity2>
this.dataCursor('data', {
entities2: ['visible', 'entity2']
})
</entity1>
<entities>
<entity1 each={ data.entities1 }></entity1>
this.dataCursor('data', {
entities1: ['visible', 'entity1']
})
</entity>
As I developed the application further the state tree grew as more entities were added. However the data is hierarchical. So the above structure is a bit redundant and monkeys are mostly used to provide the following hierarchy:
entities1 {
id: {
props...
entities2: {
id: {
props...
entities3: { ... }
}
entities4: { ... }
If I try to implement the above structure then the ['visible', 'entities1']
monkey will trigger an update every time something happens to any of its children entities. This leads to a huge number of events on the Riot components which ends up refreshing the component without it needing to so.
What I would like to do is this:
<entities>
<entity1 each={ data.entities1 }></entity1>
this.dataCursor('data', {
entities1: {
path: ['visible', 'entity1'],
updateOff: ['entities2', 'entities4'] // don't update the state if any of these change
})
// or
this.dataCursor('data', {
entities1: {
path: ['visible', 'entity1'],
updateOn: ['prop1', 'prop2'] // update the state only if these change
}
})
</entity>
// and inside the dataCursor something like
tree.select(entities1.path).on('update', e => {
if (-1 !== e.data.updatedPaths.indexOf(entities.updateOn) {
tag.data = tree.select(entities1.path).get()
tag.update()
}
})
I don't want to do a comparison between the data in the riot tag and the new data because the state tree should be the single source of truth. Also I wouldn't want to do diffs on the tree on the update function because I expect that cursor events mean the same thing (I don't need to know the value that changed but I do need the path to the value that changed so I can trigger an update).
With the diff/updatePaths are available at the cursor event level I am able to better structure the data tree without any worries of overflowing the app with unnecessary updates on higher level components.
Hmm, I had a thought, maybe adding the monkeys to the tree update event would be a much better (and maybe simpler solution). The way I thought about it now is kind like:
tree.on('update', e => {
let paths = join(e.data.paths, e.data.monkeyPaths)
if (-1 !=== paths.indexOf(RiotTagDataListeningPaths) {
// trigger an update on all the riot tags according to their updateOn/updateOff rules
}
})
Diff should indeed be avoided. The whole point of this library is to avoid costly diffs and rely on paths and referential comparisons enabled by the immutability/persistence system. Which leads me to wonder if this wouldn't be possible to implement someting in RiotJS like React's shouldComponentUpdate
which seems to be your issue right now.
Yup, that's exactly what I felt about Baobab and why a big light bulb got lit when I saw it. I'm trying to move every piece of logic onto the Baobab tree, so instead of doing computation inside components (which includes the shouldComponentUpdate
) I'm moving that to monkeys - my components are 100% "dumb" in this respect :). So far its been a fantastic way to build the app, much cleaner code and the only place I need to worry about is properly setting up the data structure and the monkeys that will serve what each component needs.
I also need to push data changes to streams or third party APIs, so I can't rely on the internal mechanisms of Riot/React etc to figure out if indeed something needs refreshing. I'm looking at Baobab to provide a universal mechanism to propagate the new state according to what every listener needs.
I was thinking now of using the tree.watch
to generate a watcher by parsing what properties each component uses (or doesn't use). I'll let you know how that goes...
I also could see value in having the exact list of paths passed both to cursor changes and monkey callbacks. In our case it's wanting to know only if an item has been added/removed from an array as opposed to just a property on an item changing. Access to the paths makes knowing that easy and allow us to avoid re-computing the monkey data unless an item was added/removed.
I haven't used baobab with arrays yet but I think this is where you'd need the event details most.
As a workaround for nested object, I found using watchers with specific paths to the properties I need, instead of just on the parent object, avoids the problem of recompilation.
Constantin Dumitrescu
On 18 mar. 2016, at 01:59, Jason Rust notifications@github.com wrote:
I also could see value in having the exact list of paths passed both to cursor changes and monkey callbacks. In our case it's wanting to know only if an item has been added/removed from an array as opposed to just a property on an item changing. Access to the paths makes knowing that easy and allow us to avoid re-computing the monkey data unless an item was added/removed.
— You are receiving this because you were mentioned. Reply to this email directly or view it on GitHub
Consider the following:
What should I replace the
e.target.actualPathThatChanged
with to get['foo', 'bam']
?