From adf2d57214c052c025e5d220dff07079628deaed Mon Sep 17 00:00:00 2001
From: Sean Matheson
Date: Mon, 11 Apr 2016 14:24:00 +0100
Subject: [PATCH] perf(api): Massive simplification to API, along with guards
and 100% test cov.
The API has been refocused to allow for the injection of Components of any type
(i.e. class/stateless/createClass). The Provider has been renamed to make it
more explicit, and the Injector API has been greatly simplified. A lot more
invariant based checks have been added to the project and we have achieved
full test coverage.
closes #2
closes #3
BREAKING CHANGE: Provider renamed to InjectableProvider.
BREAKING CHANGE: The Injector helper has been greatly simplified. Instead of
wrapping a component that will host the injection, it rather produces an
injection component which you can render into any component that you would
like to initiate an injection. This produces a much cleaner API and allows
you to easily pass down specific props to the component that will be injected.
Please look at the updated readme for example usage.
---
README.md | 123 +++++----
examples/router/package.json | 11 +-
examples/router/src/InjectableHeader.js | 8 +-
examples/router/src/PageOne.js | 34 ++-
examples/router/src/PageTwo.js | 30 ++-
examples/router/src/index.js | 2 +-
package.json | 27 +-
src/Injectable.js | 21 +-
src/{Provider.js => InjectablesProvider.js} | 123 +++++----
src/Injector.js | 58 ++--
src/index.js | 2 +-
test/Injectable.test.js | 139 ++++++++++
test/InjectablesProvider.test.js | 279 ++++++++++++++++++++
test/Injector.test.js | 268 +++++++++++++++++++
test/Provider.test.js | 178 -------------
test/integration.test.js | 83 +++---
16 files changed, 964 insertions(+), 422 deletions(-)
rename src/{Provider.js => InjectablesProvider.js} (50%)
create mode 100644 test/Injectable.test.js
create mode 100644 test/InjectablesProvider.test.js
create mode 100644 test/Injector.test.js
delete mode 100644 test/Provider.test.js
diff --git a/README.md b/README.md
index ea95764..b6c3b22 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-
Explicitly inject Elements to any part of your React render tree
+
Explicitly inject Components to any part of your React render tree
[![Travis](https://img.shields.io/travis/ctrlplusb/react-injectables.svg?style=flat-square)](https://travis-ci.org/ctrlplusb/react-injectables)
@@ -9,15 +9,11 @@
[![Codecov](https://img.shields.io/codecov/c/github/ctrlplusb/react-injectables.svg?style=flat-square)](https://codecov.io/github/ctrlplusb/react-injectables)
[![Maintenance](https://img.shields.io/maintenance/yes/2016.svg?style=flat-square)]()
-Tiny, and the only dependency is a peer dependency of React.
-
-### Warning
-
-I am actively developing this project whilst applying it to a production use-case. This is exposing the limitations of the API pretty fast requiring that I do quick updates. Expect the versions to bump excessively over the next couple of weeks, but I will stabilise it as quickly as I can. I'm not a fan of 0.x.x beta releases and don't mind big numbers. I like that each version I release clearly indicates breaking changes. This message will disappear as soon as I believe the API will remain stable for an extended period of time.
+This is a teeny tiny React library - it's almost unnoticable when gzipped.
## What is this for?
-Placeholders, Modals, etc etc.
+Placeholders, Modals, etc.
## Overview
@@ -44,26 +40,34 @@ One option would be to use react-routers native capability to pass down multiple
Enter Injectables.
-Injectables aims to provide you with a mechanism to explicity define an Injectable target and Injector source component(s).
+Injectables aims to provide you with a mechanism to explicity define `Injectable` and `Injector` Components. An `Injector` produces a Component that gets injected into an `Injectable`.
-With Injectables you can easily inject into `Sidebar` from your `ProductPage` doing something similar to the following:
+With Injectables you can easily inject a Component into the `Sidebar` when your `ProductPage` Component gets mounted. Here is a partial example of this:
```javascript
-import { Injector } from 'react-injectables';
+const MyBasketSidebarInjection = Injector({
+ into: InjectableSidebar
+})(MyBasket)
class ProductsPage extends Component {
...
+
+ render() {
+ return (
+
+
+
+
Product Page
+ ...
+
+ );
+ }
}
-
-export default Injector({
- to: Sidebar,
- inject: function () { return (); }
-})(ProductsPage);
```
-Now every time `ProductPage` is rendered onto the page the `MyBasketSummary` will be injected into `Sidebar`. Ok, there is a tiny bit of additional setup. But it's quick sticks to get going.
+Now every time the `ProductPage` Component is mounted the `MyBasket` Component will be injected into `Sidebar` Component. Ok, there is a bit of additional setup required, but the above is a basic overview of how easy it is to define your injections after the initial configuration.
-Fairy dust is involved, but we attempt to keep things as un-magical as possible, keeping things explict, whilst also easy to use.
+Yep, some fairy dust is involved, but we attempt to keep things as un-magical as possible, pushing for explictness whilst keeping it easy to use.
## Usage
@@ -76,9 +80,9 @@ First install the library.
To get going there are only 3 steps:
- 1. Wrap your application with our `Provider`.
- 2. Wrap a component you would like to _receive_ content with our `Injectable`. e.g. `Injectable(Sidebar)`
- 4. Wrap a component you would like to _produce_ content with our `Injector`. e.g.: `Injector({ to: InjectableSidebar, inject: () => )(ProductPage)`
+ 1. Wrap your application with our `InjectablesProvider`.
+ 2. Wrap a Component you would like to _receive_ injected content with our `Injectable` helper. e.g. `Injectable(MainScreen)`
+ 4. Wrap a Component you would like to _inject_ with our `Injector` helper. e.g.: `Injector({ into: MainScreen })(MyModal)`
### Full Tutorial
@@ -86,38 +90,35 @@ Ok, here's a more detailed run through with example code.
__Step 1__
-Somewhere very low down in your application wrap your component tree with our `Provider`. This is the engine that will do all the heavy lifting for you. For example:
+Somewhere very low down in your application wrap your component tree with our `InjectablesProvider`. This is the engine that will do all the heavy lifting for you. For example:
```javascript
import React from 'react';
import ReactDOM from 'react-dom';
-import { Provider } from 'react-injectables';
+import { InjectablesProvider } from 'react-injectables';
ReactDOM.render((
-
+
...
-
+
), document.getElementById('app'));
```
-_Note:_ If you already have another component with the name of `Provider` in your app (think redux), then you can rename the import, like so:
-`import { Provider as InjectablesProvider } from 'react-injectables';`
-
__Step 2__
-Now you need to create an Injectable Component. Let's say that you would like to create a Sidebar component that you could inject in to. You would do the following:
+Now you need to create an `Injectable` Component. Let's say that you would like to create a `Sidebar` component that you could inject in to. You would do the following:
```javascript
import React, { PropTypes } from 'react';
import { Injectable } from 'react-injectables';
-// Note the 'injected' prop. This will contain any injected elements.
-function Sidebar({ injected }) {
+// Note the 'injections' prop. This will contain any injected elements.
+function Sidebar({ injections }) {
return (
- {injected}
+ {injections}
);
}
@@ -132,45 +133,43 @@ __Step 3__
Ok, so you have an `InjectableSidebar` now, but you need to declare the components that will inject content in to it.
-You need to make use of our `Injector`, wrapping your component, whilst also providing the target injectable component and the elements that you wish to inject.
+You need to make use of our `Injector`, wrapping the Component you would like to inject, whilst also providing the target `Injectable` Component you created.
```javascript
import React from 'react';
import { Injector } from 'react-injectables';
import InjectableSidebar from './InjectableSidebar';
-import BasketViewPage from './BasketViewPage';
+import MyBasketView from './MyBasketView';
+
+// This sets up our injection.
+const MyBasketViewSidebarInjection = Injector({
+ into: InjectableSidebar // The target Injectable Component. The actual Component - explicit. :)
+})(MyBasketView); // The Component you would like to be injected.
class ProductsPage extends Component {
....
+
+ render() {
+ return (
+
+ {/* The injection! Nothing actually gets rendered here, it gets sent to
+ our target Injectable. In this case it means that MyBasketView will
+ be injected into the Sidebar.
+ Notice how you can also pass down props into your injected component too. */}
+
+
+
Products Page
+
+ );
+ }
}
-// We are using this stateless component as our inject resolver.
-// It will receive any props that will be passed into ProductsPage, including updated ones.
-function Inject(props) {
- return ();
-}
-
-export default Injector({
- to: InjectableSidebar, // You have to specify the actual Injectable Component. Explicit.
- inject: Inject // The inject function or a React Element.
-})(ProductsPage);
+export default ProductsPage;
```
-The `inject` property argument for the `Injector` accepts either of two things:
-
- * A function with a `props` argument that when executed will return a React Element. i.e. a stateless functional component. It will recieve the same props that will be passed into the component being wrapped by the `Injector`. If you wrap your component with multiple higher order functions/components then please make sure that `Injector` is the first of the functions used to wrap your component. For e.g.
- ```javascript
- export default compose( // executes last to first.
- Connect(stateM, propsM),
- Injector(args)
- )(ProductsPage);
- ```
-
- * A React element. This works, however the element is created up front before it is even needed. Also you lose the capability to interogate any props that may have been passed into your component.
-
---
-And that's it. Any time the `ProductsPage` is rendered it will inject it's content into the `Sidebar`. When it unmounts, it's injected elements will be removed from the `Sidebar`.
+And that's it. Any time the `ProductsPage` is mounted it will inject the `MyBasketView` Component into the `Sidebar`. When it unmounts, the respective Component will be removed from the `Sidebar`.
As you can see it's all explicit, so you can follow the import references to find out any relationships.
@@ -180,17 +179,17 @@ You will need to repeat steps 2 and 3 for any set of Injectable targets and Inje
Here are a few basic properties you should be aware of:
- * All injection happens within the initial render cycle by react-dom. Injection does not cause a double render to occur. This is a result of us trying to intentionally keep injection as "uni-directional" / "input -> output" as possible.
+ * All injection happens within the initial render cycle by react-dom. Injection does not cause a double render to occur on your `Injectable`s. This is a result of us trying to intentionally keep injection as "input to output" as possible.
- * You can have multiple instances of an Injectable rendered in your app. They will all recieve the same injected content from their respective Injectors.
+ * You can have multiple instances of an `Injectable` rendered in your app. They will all recieve the same injected content from their respective `Injector`s.
- * You can create multiple Injectors components targetting the same Injectable component. The rendered Injectors shall have their injected elements collected and passed through to the target Injectable. For example, you may want to pass in action buttons from different components into an InjectableActions component.
+ * You can create multiple `Injector`s Components targetting the same `Injectable` component. For example, you may want to pass in action buttons from different components into an InjectableActions component.
- * If an Injector is removed from the tree then it's injected elements will automatically be removed from the Injectable target.
+ * If an Component that is hosting `Injector` is unmounted then the injected Components will automatically be removed from the `Injectable` target.
- * Any new Injectors that are rendered into the tree will automatically have their injected content rendered within any rendered Injectable target.
+ * Any new `Injector`s that are rendered into the tree will automatically have their injected Components passed into any existing `Injectable` targets. i.e. a props update.
- * Injectors are allowed to be rendered before any Injectables. Once their target Injectable Component is rendered then any content from existing the Injectors will automatically be rendered within the newly rendered Injectable.
+ * `Injector`s are allowed to be mounted before any `Injectable`s. Once their target `Injectable` Component is mounted then any Components from existing `Injector`s will automatically be passed into the newly mounted `Injectable`.
## Examples
diff --git a/examples/router/package.json b/examples/router/package.json
index 6f90afa..7957d28 100644
--- a/examples/router/package.json
+++ b/examples/router/package.json
@@ -9,19 +9,18 @@
"author": "",
"license": "MIT",
"dependencies": {
- "babel-cli": "6.6.5",
- "babel-core": "6.7.4",
+ "babel-cli": "6.7.5",
+ "babel-core": "6.7.6",
"babel-loader": "6.2.4",
"babel-preset-es2015": "6.6.0",
"babel-preset-react": "6.5.0",
"babel-preset-stage-1": "6.5.0",
"babel-register": "6.7.2",
"express": "4.13.4",
- "react": "0.14.8",
- "react-dom": "0.14.8",
- "react-injectables": "1.0.0",
+ "react": "15.0.1",
+ "react-dom": "15.0.1",
"react-router": "2.0.1",
- "webpack": "1.12.14",
+ "webpack": "1.12.15",
"webpack-dev-middleware": "1.6.1",
"webpack-hot-middleware": "2.10.0"
}
diff --git a/examples/router/src/InjectableHeader.js b/examples/router/src/InjectableHeader.js
index 0a07d15..0cd4f29 100644
--- a/examples/router/src/InjectableHeader.js
+++ b/examples/router/src/InjectableHeader.js
@@ -2,17 +2,17 @@ import React, { PropTypes } from 'react';
import { Injectable } from '../../../src/index.js';
// Prep a header component which we intend to make injectable.
-// Note the prop named 'injected'. This will contain any injected elements.
-const Header = ({ injected }) => (
+// Note the prop named 'injections'. This will contain any injected elements.
+const Header = ({ injections }) => (
INJECTABLE HEADER
- {injected.length > 0 ? injected :
Nothing has been injected
}
+ {injections.length > 0 ? injections :
Nothing has been injected
}
);
Header.propTypes = {
- injected: PropTypes.arrayOf(PropTypes.element)
+ injections: PropTypes.arrayOf(PropTypes.element)
};
// Convert our header into an injectable!
diff --git a/examples/router/src/PageOne.js b/examples/router/src/PageOne.js
index 215c230..13b78d0 100644
--- a/examples/router/src/PageOne.js
+++ b/examples/router/src/PageOne.js
@@ -2,34 +2,24 @@ import React, { Component, PropTypes } from 'react';
import { Injector } from '../../../src/index.js';
import InjectableHeader from './InjectableHeader';
-const Content = ({ active }) => (
-
-
I am page one.
-
My State is {active ? `active` : `inactive`}
-
-);
-Content.propTypes = {
- active: PropTypes.bool.isRequired
-};
-
-const Inject = ({ active }) => (
+// Our component that we will inject.
+const Injection = ({ active }) => (
Injection from Page One
The active prop value is: {active ? `active` : `inactive`}
);
-Inject.propTypes = {
+Injection.propTypes = {
active: PropTypes.bool.isRequired
};
-const InjectingContent = Injector({
- to: InjectableHeader,
- inject: Inject
-})(Content);
+// Our Injector instance configured to inject into the InjectableHeader.
+const HeaderInjection = Injector({
+ into: InjectableHeader
+})(Injection);
/**
- * We wrap our injecting content with a class based component so we can track
- * state and pass down props, thereby adding behaviour.
+ * This is the component that when rendered will cause the injection to occur.
*/
class PageOne extends Component {
state = {
@@ -45,7 +35,13 @@ class PageOne extends Component {
return (
);
-export default Injector({
- to: InjectableHeader,
- inject: Inject
-})(PageTwo);
+// Our Injector instance configured to inject into the InjectableHeader.
+const HeaderInjection = Injector({
+ into: InjectableHeader
+})(Inject);
+
+/**
+ * This is the component that when rendered will cause the injection to occur.
+ */
+const PageTwo = () => (
+
+ {/* The injection! Nothing actually gets rendered here, it gets sent to
+ our target Injectable. */}
+
+
+ I am page two.
+