npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

reactdi

v0.1.2

Published

Dependency injection for ReactJS

Downloads

8

Readme

reactdi

Dependency injection for React components.

WARNING: This project is experimental!

Why?

In ReactJS, the data is normally passed directly from parent to child as props. However, when working with large component trees, forwarding props to descendants can become cumbersome and couple components unnecessarily. React's solution to this is to use React.withContext:

var Grandparent = React.createClass({
    render: function () {
        return React.withContext({greeting: 'hello'}, function () {
            return Parent();
        });
    }
});

var Parent = React.createClass({
    render: function () {
        return Child();
    }
});

var Child = React.createClass({
    render: function () {
        return div(null, this.context.greeting);
    }
});

In this example, Child will render the "hello" greeting even though it wasn't passed via props.

While this approach works, your components now need to know what data they get from props and what comes from context. If you change your mind about how data is given to the component, you need to rewrite your component. Components that want to support both will have to be written to do so.

reactdi uses an alternative method for getting data to deeply nested components: Instead of adding a special property (like context), you simply inject props.

Example

The usage for reactdi should be very familiar. Here are our previous components rewritten to use reactdi:

var Grandparent = React.createClass({
    render: function () {
        var di = reactdi().mapValues({greeting: 'hello'});
        return di(function () {
            return Parent();
        });
    }
});

var Parent = React.createClass({
    render: function () {
        return Child();
    }
});

var Child = React.createClass({
    render: function () {
        return div(null, this.props.greeting);
    }
});

As you can see, there are only a few differences from before: 1) we create a reactdi injector and map properties to values, 2) we use that in the render() method in place of React.withContext, and 3) our Child gets its data from its props object instead of context. If a prop isn't provided, the component will continue to use its default value (from getDefaultProps). If you want to override an injected value, you can just pass a value from the parent as usual.

Note: we don't have to create the di instance in the render() method—if the values are constant, it would probably be a good idea to do that in componentWillMount instead.

This approach has two big benefits:

  1. props are king. They're how data comes into your component—regardless of where it's coming from.
  2. Components don't need to be rewritten to take advantage of dependency injection. Just wire them up at a higher level.

Isolate Injectors

There's no rule that says you can only have one injector for your hierarchy—feel free to create as many as you want. Normally, components will receive props from all of the injectors above them in the tree:

var Grandparent = React.createClass({
    render: function () {
        var di = reactdi().mapValues({greeting: 'hello'});
        return di(function () {
            return Parent();
        });
    }
});

var Parent = React.createClass({
    render: function () {
        var di = reactdi().mapValues({subject: 'world'});
        return di(function () {
            return Child();
        });
    }
});

var Child = React.createClass({
    render: function () {
        return div(null, this.props.greeting + ', ' + this.props.subject);
    }
});

Here, Child will get props.subject from the injector in Parent and props.greeting from the injector in Grandparent.

Sometimes, though, you may not want properties to be be passed down the hierarchy forever. In those cases, you can create isolated injectors:

var Grandparent = React.createClass({
    render: function () {
        var di = reactdi().mapValues({greeting: 'hello'});
        return di(function () {
            return Parent();
        });
    }
});

var Parent = React.createClass({
    render: function () {
        var di = reactdi({isolate: true}).mapValues({subject: 'world'});
        return di(function () {
            return Child();
        });
    }
});

var Child = React.createClass({
    getDefaultProps: function () {
        return {
            greeting: 'hey',
            subject: 'you'
        }
    },
    render: function () {
        return div(null, this.props.greeting + ', ' + this.props.subject);
    }
});

In this case, the Child component will get props.subject from the injector in Parent but (since the Parent uses an isolate injector) it won't be injected with Grandparent's greeting prop. That means it'll render the string "hey world".

Limiting Injection Scope

Another drawback of React's withContext is that it doesn't allow for very targeted injection. With reactdi, you can specify that only specific component types be injected using either the class:

var di = reactdi()
    .mapValues(MyWidget, {greeting: 'hello'});

…or the displayName:

var di = reactdi()
    .mapValues('mywidget', {greeting: 'hello'});

For cases when this isn't enough, you can provide your own test function, which will be passed the component instance and its current props:

var di = reactdi()
    .mapValues({greeting: 'hello'}, function (component, props) {
        if (someCondition) {
            return true;
        }
        return false;
    });

Events

By default, reactdi will only inject props that aren't set explicitly by the parent component. You can force it to override those values by passing {override: true} as an option when configuring your mappings. However, there's one common case where you don't want either of these behaviors: event handling.

In ReactJS, event handling is done by passing callbacks as props. This works out fine as long as only one thing (the parent component) needs to listen. But with reactdi, you now have access to deeply nested components from other locations. In order to let you hook into component events without overriding the handlers their parents have set, reactdi provides the on method:

var Grandparent = React.createClass({
    render: function () {
        var di = reactdi()
            .on('change', function () { console.log('something changed!') });
        return di(function () {
          return Parent();
        });
    }
});

var Parent = React.createClass({
    handleChildChange: function () {
        console.log('my kid changed!');
    },
    render: function () {
      return Child({onChange: this.handleChildChange});
    }
});

var Child = React.createClass({
    handleClick: function () {
        this.props.onChange();
    },
    render: function () {
        return div({onClick: this.handleClick});
    }
});

Now the Grandparent can know when one of its descendants has changed. Notice that the Parent is blissfully unaware that this is happening; its callback will execute normally.

Like the map* functions, on can be easily scoped to particular component types:

var Grandparent = React.createClass({
    render: function () {
        var di = reactdi()
            .on(Child, 'change', function () { console.log('a Child changed!') });
        return di(function () {
          return Parent();
        });
    }
});

API

Calling reactdi will return an injector. Below is the API for an injector ("di"):

        <code><pre>reactdi()
.mapValue(Widget, 'someProp', 5)
.on(Widget, 'change', handleChange)
.inject(function () {
    return Button();
});</pre></code>
    </td>
</tr>
<tr>
    <td><code>di.mapValue(<i>componentType</i>?, <i>propName</i>, <i>value</i>, <i>options</i>?, <i>test</i>?)</code></td>
    <td>
        Maps a particular prop value for injection.
        <code>componentType</code> may be either a React class or a
        displayName. If provided, the mapping will only be used for the
        matching components. For more fine-grained control, pass a
        <code>test</code> function that returns a boolean.
    </td>
</tr>
<tr>
    <td><code>di.mapValue(<i>componentType</i>?, <i>props</i>, <i>options</i>?, <i>test</i>?)</code></td>
    <td>
        Like <code>di.mapValue</code>, but maps several values at once.
        <code>props</code> is an object whose keys are prop names.
    </td>
</tr>
<tr>
    <td><code>di.mapFactory(<i>componentType</i>?, <i>propName</i>, <i>factory</i>, <i>options</i>?, <i>test</i>?)</code></td>
    <td>
        Like <code>di.mapValue</code>, but maps a factory function. This
        function will be invoked and the result used for the prop value.
        The factory function is passed the component and current props.
    </td>
</tr>
<tr>
    <td><code>di.on(<i>componentType</i>?, <i>eventName</i>, <i>listener</i>, <i>options</i>?, <i>test</i>?)</code></td>
    <td>
        Adds the provided listener as a callback for the given event.
        Callback names are derived from the <code>eventName</code> by
        prefixing "on" and capitalizing the first letter, so passing the
        event name `"change"` will result in a binding to the `onChange`
        prop.
    </td>
</tr>