Skip to content

Instantly share code, notes, and snippets.

@Vermeille
Last active March 5, 2018 11:11
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Vermeille/6660f2371bd272ce85872c3e7012e4fb to your computer and use it in GitHub Desktop.
Save Vermeille/6660f2371bd272ce85872c3e7012e4fb to your computer and use it in GitHub Desktop.
React thoughts

The Action Components

I know about Container Components. They usually go like that:

class ListContainer extends React.Component {
    componentDidMount() {
        fetch('/api/data')
            .then(b => b.json())
            .then(b => this.setState({data: b}));
    }

    render() { return <List data={this.state.data} />; }
}

const List = ({data}) => (
    <ul>
        {data.map(x => <li>{x}</li>)}
    </ul>
);

// Use in UI:
<ListContainer />

The first thing I have to note about that is that List is actually something: it's a list of items. But ListContainer is nothing. Instead, it does things. List is a value while ListContainer is a function (and I'm talking on a semantic level. The funny thing is that List is actually a javascript function whereas ListContainer is a class, that is, a value. Quite the irony). So, let's use that observation and go deeper. We have Value Components, like List, and Action Component, like the misnamed ListContainer. A function, on a semantic level, is something that takes values and produces another values.

With Function Children Props to the rescue, we can make that very apparent and meaningful.

class Fetch extends React.Component {
    componentDidMount() {
        fetch('/api/data')
            .then(b => b.json())
            .then(b => this.setState({data: b}));
    }

    render() { return this.props.children(this.state.data); }
}

const List = ({data}) => (
    <ul>
        {data.map(x => <li>{x}</li>)}
    </ul>
);

// Use in UI:
<Fetch>
    {data => <List data={data} />}
</Fetch>

And Fetch is now much more reusable, it doesn't know about List because it never had to, and the semantic action of "producing a value used by the children" is more obvious than ever.

But wait. I claimed I wanted reusability. The fetched endpoint can't be specified. Let's fix that.

class Fetch extends React.Component {
    componentDidMount() {
        fetch(this.props.endpoint)
            .then(b => b.json())
            .then(b => this.setState({data: b}));
    }

    render() { return this.props.children(this.state.data); }
}

Better? Not quite. And I saw this kind of code in a lot of places. What's wrong? If props.endpoint changes, the fetching won't happen again. Sure, I could also fetch on componentWillReceiveProps(), but abusing the lifecycle is not going to make things better. Instead, let's consider render() as our entry point and write that:

class Fetch extends React.Component {
    doFetch(endpoint) {
        if (this.state.endpoint === endpoint) {
            return this.state.data;
        }

        fetch(endpoint)
            .then(b => b.json())
            .then(b => this.setState({data: b, endpoint: endpoint}));
    }

    render() { return this.props.children(this.doFetch(this.props.endpoint)); }
}

And now, props changes are reflected seemlessly. Results are cached, the component is super easy to maintain and understand, and doesn't use the complex lifecycles at all.

<Fetch endpoint="/api/data">
    {data => <List data={data} />}
</Fetch>

Why limit ourselves though? Data fetching is one of many ways to produce or transform values. See a conceptual heavy use of Action Components here:

<Every seconds={10}>
    {() =>
        <Fetch endpoint="/api/data">
            {values =>
                <div>
                    Data: {values.join(', ')}
                    <ComputeMean data={values}>
                        {mean => `Mean is ${mean}`}
                    </ComputeMean>
                    <ComputeVariance data={values}>
                        {variance => `Variance is ${variance}`}
                    </ComputeVariance>
                    <ComputeHistogram data={values}>
                        {bins => bins.map((x, c) => `${x} appears ${c} times`)}
                    </ComputeHistogram>
                </div>
            }
        </Fetch>
    }
</Every>

That looks nice to me.

I hate props drilling (my background)

I hate props drilling. I despise it. Is spent the last years writing C++ and fighting hard with my code habits to have a code that is not just working, but also semantically correct. Having code that makes sense is hard, but has all the possible qualities good code can have: since the concerns are well delimited, each class / component / module tends to be independant, reusable, and easily testable. Your code just makes sense. It's also simpler, because the design is just good. The maintainability is greater. And all sorts of things.

And then I moved to Python and was even more amazed by the tools this language would provide to express my semantic intents. I'm not a Python expert at all, but I feel the potential, and I like it. (I could argue that static typing is a semantic weapon, but static typing enforces objects to be something, whereas dynamic typing enforces objects to behave a certain way. None is inherently better, and both seem to be fairly expressive in their own way, despite dynamic typing being obviously more flexible).

Then I moved to js / React because, well, I needed some frontend. No especially web, but having dabbled with Qt in the past and hating it, I prefered not to continue in that path. My goal was to have a technology to quickly build up UIs that doesn't make me want to shoot myself. An ideal technology for that, in my opinion, would require little knowledge of its working, wouldn't need me to remember a fuckton of names and function and all that, and would have a simple logic. React seems to be all that, and I started to do a bit of React, here and there, to solve basic needs. And, well, despise hating frontend and UI, I did not hate React. Very good win.

But as my needs evolved, my use of React had to become a little less simplistic. And remember where I come from: C++ with good design in mind, a love for Haskell and functional programming, and clean Python design. And, well, I found out about props drilling. And God knows I hate that shit (I'm actually a convinced antitheist, but this expression serves my need of emphasis). It's all I tried to fight all those years. It breaks the semantics, it breaks the design, it shows flaws.

So here I am, I know little about React, I challenge props drilling that seems to be widely accepted. That might have two outcomes: either create horse shit, since I don't know about the issues and solutions that React encountered along its life (I don't know the ecosystem and MobX / Redux / context are empty words); either ignorance is bliss and since I'm free of preconceptions, I can create original and actually novel pieces of code (or, third option, I'm just gonna rediscover solutions that have been known for long).

Passing messages among your components

The general idea is to share a pub / sub object alongside your components.

My first need for such a system arose first when I needed a great deal of images to be overlayed with some metadata fetched from a json file.

Props drilling solution

Fetch the file on one of your top component, and fucking pass it down through all your damn app until it reaches the images. And make all the damn intermediary components aware of that json metadata store. You want to do that? Over my dead body.

Non props drilling bad solution

Put that data in a global variable. That would have been okay if that data didn't have to be fetched from a server. Because it means our global variable will have two successive states: unfetched, then fetched. And somehow our components would need to know when the fetching happens.

let imgData = {};
fetch('img_metadata.json').then(d => d.json()).then(d => imgData = d);

class ImgWithMetadata extends React.Component {
    render() {
        // imgData is initially an empty object. And it never knows when the
        // file is fetched so that the rendering can really happen.
        return (
            <div>
                <img src={this.props.src} />
                {imgData[this.props.src]} // just display the metadata
            </div
        );
    }
}

Non props drilling good solution

Obviously, with the complexity of the problem grows the complexity of the solution. So, this solution is a bit more involved. But it works. Fine. Without props drilling. And correct propagation of concerns. Maybe that's all what MobX is about. I don't know.

class FetchableData {
    // Fetch the file. When it's fetched, call all the subscribers with the
    // data, and forget about them.
    constructor(file) {
        this.subs = [];
        fetch(file)
            .then(b => b.json())
            .then(d => {
                this.data = d;
                for (let s of this.subs) {
                    s(this.data);
                }
                this.subs = [];
            }
        );
    }

    // While the fetching happen, the components needing it can subscribe.
    // They'll be called back when the data arrives. It the data is already
    // there, subscribing just immediately calls the callback
    subscribe(s) {
        if ('data' in this) {
            s(this.data);
        } else {
            this.subs.push(s);
        }
    }
}

let imgData = new FetchableData('img_metadata.json');

class ImgWithMetadata extends React.Component {
    componentDidMount() {
        imgData.subscribe(d => this.setState({ metadata: d}));
    }

    render() {
        // imgData is initially an empty object. And it never knows when the
        // file is fetched so that the rendering can really happen.
        return (
            (this.state.metadata) ?
                <div>
                    <img src={this.props.src} />
                    {this.state.metadata[this.props.src]}
                </div
            ) : (
                "The metadata aren't loaded yet"
            )
        );
    }
}

I'm sure this could easily be turned into a powerful message passing but I'm figuring out the details.

But hey, this ImgWithMetadata component is not perfect. Because it takes care of fetching the data and displaying it. Welcome the Action Components.

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