Skip to content

Instantly share code, notes, and snippets.

@dmitriid
Last active September 9, 2015 12:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dmitriid/53789c56160d4d754ab5 to your computer and use it in GitHub Desktop.
Save dmitriid/53789c56160d4d754ab5 to your computer and use it in GitHub Desktop.

Trying to understand the basic flow between Cycle.js nested dialogs

Scenario

A simple component which has three states (on) <-> (transitional state) <-> (off):

  • You click on the component
  • It propagates the click up to the parent
  • The parent sets the component to a transitional state
  • The parent retrieves data
  • The parent sets the component to a final state

Imagine a checkbox in a CMS which triggers an action when you click it:

It's evident that such a component is generic enough to warrant being a separate component. However, it's not specialized enough to take care of data fetching or of state transitions. All it needs to do is to transfer clicks to the parent, get rendered with the appropriate state.

Enter Cycle.js

I will start with just an "on/off" element, just to see how it feels. It took me several tries to get this partially working, so here it is:

Create several "rows" of such checkboxes.

// main.js

/** @jsx hJSX */
import {hJSX} from '@cycle/dom';
import {run, Rx} from '@cycle/core';
import {makeDOMDriver} from '@cycle/dom';

import row from './table-row.js';

function main(sources){
    const createRow = (i) => row(sources, i);

    let arr = [];
    for(let i = 0; i <= 5; i++){ arr.push(createRow(i).DOM); }

    return {
        DOM: Rx.Observable.just(<div>{arr}</div>)
    };
}

run(main, {
    DOM: makeDOMDriver('#app')
});
// table-row.js

/** @jsx hJSX */
import {hJSX} from '@cycle/dom';
import {Rx} from '@cycle/core';

import checkbox from './checkbox.js';

const id = (name) => `div-check-${name}`;

function model(checkBox, name){
    let state = true;
    return checkBox.state$.startWith(true).map(() => {
        console.log(`props change ${name} from ${state} to ${!state}`);
        state = !state;
        console.log(`current state is ${state}`);
        return {
           state: state,
           label: state === true ? 'Checked' : 'Unchecked'
       }
    });
}

function view(model$, checkboxRender, name){
    const DOM$ = model$.map((props) => {
        console.log(`DOM change in ${name} with `, props);
        return checkboxRender(props)
    });

    return DOM$.map(
        (DOM) => {
            console.log('We got DOM', DOM);
            return <div key={id(name)}>{DOM}</div>;
        }
    ).map((DOM) => DOM);
}

export default function (sources, name){
    const check = checkbox(sources, name);
    const model$ = model(check, name);
    const view$ = view(model$, check.render, name);

    return {
        DOM: view$
    }
}
// checkbox.js

/** @jsx hJSX */
import {hJSX} from '@cycle/dom';

import {Rx} from '@cycle/core';

const id = (name) => `check-${name}`;

function intent(DOM, name){
    return DOM.select(`#${id(name)}`).events('click').map((e) => {
        e.preventDefault();
        return true;
    });
}

function view(name){
    return ({state, label}) => {
        console.log(`rendering ${name} with `, {state, label});

        return <span>
            <input type="checkbox" name={name}
                   id={id(name)}
                   className='.check'
                   key={id(name)}
                   checked={state}
                />
                &nbsp;
                <label htmlFor={id(name)}>{`${label} (${state})`}</label>
        </span>
    };
}

export default function (sources, name){
    const clickStream$ = intent(sources.DOM, name);
    return {
            render: view(name),
            state$: clickStream$
        };
}

Problems/Questions

  • I need an extra .map on the DOM$.map in table-row.js view function. Why?
    • Possibly because the result is an observable of observables
  • (a vritual-dom question) Even though the state is passed correctly to the render function in checkbox.js, the attribute checked is not propagated to the DOM
    • The property should be defaultChecked. (NOTE: there are still issues wit it no being properly rendered)

And, most importantly:

  • Is this the way to do it?
    • Answer: yes. You could also use a wrapper element on the checkbox and capture clicks to it in the parent.
@dmitriid
Copy link
Author

dmitriid commented Sep 9, 2015

Regarding the double map above. If I change the code slightly, I no longer need the second mapping. I'm baffled.

function view(model$, checkboxRender, name){
    const DOM$ = model$.map((props) => {
        console.log(`DOM change in ${name} with `, props);
        // this used to be
        // return checkboxRender(props)
        return {
            checkbox: checkboxRender(props)
        }
    });

    return DOM$.map(
        ({checkbox}) => {
            console.log('We got DOM', checkbox);
            return <div key={id(name)}>{checkbox}</div>;
        }
    );
}

@dmitriid
Copy link
Author

dmitriid commented Sep 9, 2015

This also fixed the checked issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment