Skip to content
This repository has been archived by the owner on Jun 16, 2020. It is now read-only.

How to attach state to renderer? #218

Open
KoderFPV opened this issue Apr 27, 2017 · 10 comments
Open

How to attach state to renderer? #218

KoderFPV opened this issue Apr 27, 2017 · 10 comments

Comments

@KoderFPV
Copy link

KoderFPV commented Apr 27, 2017

I have part table component, this component will use part_row component so lets imagine:

Part_row component:

State:

export default (part) => {
    return hg.state({
        part_number: hg.value(part.part_number),
        part_type: hg.value(part.part_type),
        description: hg.value(part.description),
        quantity: hg.value(part.quantity),
        suggested_quantity: hg.value(part.suggested_quantity),
        unit_price: hg.value(part.unit_price),
        total_list_price: hg.value(part.total_list_price)
    })
}

Renderer:

export default (state) => {
    return h('div.row.part', [
        h('div.col-xs-2.part_number', state.part_number),
        h('div.col-xs-1.part_type', state.part_type),
        h('div.col-xs-2.description', state.description),
        h('div.col-xs-1.quantity', h('input', { type: 'number', value: state.quantity })),
        h('div.col-xs-2.suggested_quantity', state.suggested_quantity),
        h('div.col-xs-2.unit_price', state.unit_price),
        h('div.col-xs-2.total_list_price', state.total_list_price)
    ]);
}

Table component

State:

export default (opts) => {
    return hg.state({
        parts_list: opts.parts
    })
}

And finally renderer:

export const part_table_renderer = (state) => {
    return h('div#part_table', [
        h('div.row.header', [
            h('div.col-xs-2', 'PART NUMBER'),
            h('div.col-xs-1', 'PART TYPE'),
            h('div.col-xs-2', 'DESCRIPTION'),
            h('div.col-xs-1', 'QUANTITY'),
            h('div.col-xs-2', 'SUGGESTED QUANTITY'),
            h('div.col-xs-2', 'UNIT PRICE (USD)'),
            h('div.col-xs-2', 'TOTAL LIST PRICE (USD)')
        ]),
        h('div.body', [
            state.parts_list.map((part) => part_component_renderer(part));
        ])
}

Sooooo how to "connect" or "attach" in this pattern part state? I don understand that. Why I should create state for part_row component if I pass state to renderer. The only stupid solution what I can imagine would be

Table component
State:

export default (opts) => {
    return hg.state({
        part1: opts.parts[0]
        part2: opts.parts[1]
        part3: opts.parts[2]
        part4: opts.parts[3]
        part5: opts.parts[4]
    })
}

Thanks for help :)
Regards

@crabmusket
Copy link
Contributor

crabmusket commented Apr 28, 2017

I'm not sure exactly what your question is. Are you already running a mercury app that these components will be put inside?

It looks like you want parts_list in the table component to be an array of objects like the ones created by the part_row component, right? In that case I think you could just import the part_row function and do something like:

export default (opts) => {
    return hg.state({
        parts_list: opts.parts.map(part_row_state),
    })
}

@KoderFPV
Copy link
Author

KoderFPV commented Apr 28, 2017

@crabmusket
Thanks it is nearly what I need. But I had to do this:

     parts: Store.parts.map((part) => { 
            return part_model(part)**()**
        })

And now when I have in part_row component :

    channels: {
            changeQuantity: changeQuantity
        }

function changeQuantity(state, value) {
    const quantity = parseInt(value.quantity)
    state.quantity.set(quantity)
    state.total_list_price.set(quantity * state.unit_price())
}

changeQuantity is triggered on event, it works, but it does not re render part_row.
Do you know why?

Here is repo: https://github.com/Tarvald/bom
Here part row state: https://github.com/Tarvald/bom/blob/master/src/components/parts_list/part_table/part/part.model.js
Here part table state: https://github.com/Tarvald/bom/blob/master/src/components/parts_list/part_table/part_table.model.js

@crabmusket
Copy link
Contributor

Hmm that doesn't look right - part_model(part) will return a hg.state, right? Then calling part_model(part)() will get the value of the state as a plain JS object. But don't you want a tree of hg.state objects? I imagine part_row is not re-rendered for that reason? It's been too long since I worked with mercury 😅

@KoderFPV
Copy link
Author

@crabmusket
I thought that hg.state return ready-to-ready object with values. I don't understand why I should not get values of state as a plain JS object. And why should I want hg.state object?
And I would like to re render part_row, that why I am doing this channels, and states tricks.

Sorry I'm quite new in FRP, mercury, states etc.

@KoderFPV
Copy link
Author

So even I did
parts_list: opts.parts.map(part_row_state),

In the render I had to do state.part_number() instead of just state.part_number
And still row does not re render

@crabmusket
Copy link
Contributor

Maybe I'm misremembering, and you can't nest hg.state objects. The application state should be a tree of observable objects, and then when rendering happens, you turn the observ tree into a non-observ object (which is what all the render functions get passed).

@ashnur
Copy link
Collaborator

ashnur commented Apr 30, 2017

Ok, so please take the following as a very subjective opinion:
I personally found that it becomes very hard to manage observ-* trees when they become large, so at first I tried to fix it by throwing code at it (https://github.com/ashnur/observ-change, https://www.npmjs.com/package/observ-confined)

However this still was not quite good enough. So I went back and thought a lot about this, what is the best way to deal with state on the frontend when it becomes large and complicated, and since then I am using a mercury where I replaced the observ-* tree with https://github.com/typeetfunc/datascript-mori

Now, this has it's own issues, mainly that writing datalog in js and using mori functions is still a pita, but at least now state is easy. It's in one place, it's fast, and I can ask anything from it with a cool query language. So writing new views although is very verbose and takes longer than it should, it also gives a very flat structure that's easy to understand and grasp once you know what's happening.

Again, this fits my needs so far, when it doesn't, I tend to not use mercury but go for react/redux.

@gcallaghan
Copy link
Contributor

What I did to compose trees of state is write a base component that binds the state to a render function. I have the module here, npm here.

This allows me to have child components as part of the state tree, and call render on those child components directly without worrying about what their immediate render function is.

@crabmusket
Copy link
Contributor

crabmusket commented May 9, 2017

See also this section of the FAQ. Here Raynos seems to use struct instead of state for subcomponent states... but calling state should return a struct anyway so I'm not sure what the difference is :/

EDIT: oh hey. Back in my original comment I made a mistake. @Tarvald, maybe try this:

export default (opts) => {
    return hg.state({
        parts_list: hg.array(opts.parts.map(part_row_state)),
    })
}

E.g. you need to convert the result of map to an observable hg.array before using it in the state. Maybe that will have some effect.

@ashnur
Copy link
Collaborator

ashnur commented May 9, 2017

@crabmusket this kind of trial and error that I got bored with eventually and decided to read up on state management and ended up here: https://www.infoq.com/presentations/Value-Values :)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants