Skip to content

Commit c7e282c

Browse files
committed
perf(api): Injector API updated to inject via an 'inject' prop.
The Injector no longer supports the 'elements' prop, it now has an 'inject' prop which allows you to pass through either a stateless component (that will recieve the same props as the component being wrapped) or an element. Using the stateless component method is preferable as it will only be executed when needed, whereas passing an element requires you to actually create the element up front. closes #1 BREAKING CHANGE: The Injector no longer supports the 'elements' prop, it now has an 'inject' prop which allows you to pass through either a stateless component (that will recieve the same props as the component being wrapped) or an element. Using the stateless component method is preferable as it will only be executed when needed, whereas passing an element requires you to acutally create the element up front.
1 parent 49316f8 commit c7e282c

File tree

11 files changed

+145
-89
lines changed

11 files changed

+145
-89
lines changed

examples/router/src/InjectableHeader.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ const Header = ({ injected }) => (
77
<div style={{ backgroundColor: `red`, color: `white` }}>
88
<h1>INJECTABLE HEADER</h1>
99
<div>
10-
<strong>INJECTED ITEMS:</strong>
1110
{injected.length > 0 ? injected : <div>Nothing has been injected</div>}
1211
</div>
1312
</div>

examples/router/src/PageOne.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ const PageOne = () => (
99
);
1010
export default Injector({
1111
to: InjectableHeader,
12-
elements: [<div>Injection from Page One</div>]
12+
inject: () => <div>Injection from Page One</div>
1313
})(PageOne);

examples/router/src/PageTwo.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@ const PageTwo = () => (
88
</div>
99
);
1010

11+
const Inject = (props) => (
12+
<div>
13+
Injection from Page Two.<br />
14+
I also recieved these props:<br />
15+
{Object.keys(props).join(`, `)}
16+
</div>
17+
);
18+
1119
export default Injector({
1220
to: InjectableHeader,
13-
elements: [<div>Injection from Page Two</div>]
21+
inject: Inject
1422
})(PageTwo);

examples/router/src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ ReactDOM.render((
1616
<Route path="/" component={App}>
1717
<IndexRoute component={Home} />
1818
<Route path="pageOne" component={PageOne} />
19-
<Route path="pageTwo" component={PageTwo} />
19+
<Route path="pageTwo" component={() => <PageTwo foo bar baz />} />
2020
</Route>
2121
</Router>
2222
</InjectablesProvider>

lib/react-injectables.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Injectable.js

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,36 @@
11
import React, { PropTypes, Component } from 'react';
22
import { containsUniq, keyedElements } from './utils';
33

4-
let injectionIndex = 0;
4+
let injectionIdIndex = 0;
55

66
const Injectable = (WrappedComponent) => {
7-
injectionIndex++;
8-
const injectionId = `injection_${injectionIndex}`;
7+
injectionIdIndex++;
8+
const injectionId = `injectionId_${injectionIdIndex}`;
99

1010
class InjectableComponent extends Component {
1111
static injectionId = injectionId;
1212

1313
static contextTypes = {
14-
consumeElements: PropTypes.func.isRequired,
15-
stopConsumingElements: PropTypes.func.isRequired
14+
registerInjectable: PropTypes.func.isRequired,
15+
removeInjectable: PropTypes.func.isRequired
1616
};
1717

1818
state = {
1919
injected: []
2020
}
2121

2222
componentWillMount() {
23-
this.context.consumeElements({
24-
injectionId: InjectableComponent.injectionId,
23+
this.context.registerInjectable({
24+
injectionId,
2525
injectable: this
2626
});
2727
}
2828

2929
componentWillUnmount() {
30-
this.context.stopConsumingElements({ listener: this });
30+
this.context.removeInjectable({
31+
injectionId,
32+
injectable: this
33+
});
3134
}
3235

3336
consume = (elements) => {

src/Injector.js

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,44 @@
11
import React, { PropTypes, Component } from 'react';
22

3-
const Injector = (args: { to: Object, elements : Array<Object> }) => {
4-
const { to, elements } = args;
3+
type Inject = Object | (props: Object) => Object;
4+
5+
let injectorIndex = 0;
6+
7+
const Injector = (args: { to: Object, inject: Inject }) => {
8+
const { to, inject } = args;
9+
10+
injectorIndex++;
11+
const injectorId = `injector_${injectorIndex}`;
512

613
return function WrapComponent(WrappedComponent) {
714
class InjectorComponent extends Component {
815
static contextTypes = {
9-
produceElements: PropTypes.func.isRequired,
10-
removeProducer: PropTypes.func.isRequired
16+
registerInjector: PropTypes.func.isRequired,
17+
removeInjector: PropTypes.func.isRequired
1118
};
1219

1320
componentWillMount() {
14-
this.context.produceElements({
21+
this.context.registerInjector({
1522
injectionId: to.injectionId,
16-
injector: this,
17-
elements
23+
injectorId,
24+
injector: this
1825
});
1926
}
2027

2128
componentWillUnmount() {
22-
this.context.removeProducer({
23-
injectionId: to.injectionId, injector: this
29+
this.context.removeInjector({
30+
injectionId: to.injectionId,
31+
injector: this
2432
});
2533
}
2634

35+
getInjectElement = () => {
36+
if (typeof inject === `function`) {
37+
return inject(this.props);
38+
}
39+
return inject;
40+
}
41+
2742
render() {
2843
return (<WrappedComponent {...this.props} />);
2944
}

src/Provider.js

Lines changed: 44 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { Children, Component, PropTypes } from 'react';
2-
import { compose, concatAll, find, map, uniq, without, withoutAll } from './utils';
2+
import { compose, find, map, uniqBy, without, withoutAll } from './utils';
33

44
class InjectablesProvider extends Component {
55
static childContextTypes = {
6-
produceElements: PropTypes.func.isRequired,
7-
removeProducer: PropTypes.func.isRequired,
8-
consumeElements: PropTypes.func.isRequired,
9-
stopConsumingElements: PropTypes.func.isRequired,
6+
registerInjector: PropTypes.func.isRequired,
7+
removeInjector: PropTypes.func.isRequired,
8+
registerInjectable: PropTypes.func.isRequired,
9+
removeInjectable: PropTypes.func.isRequired,
1010
};
1111

1212
static propTypes = {
@@ -20,13 +20,13 @@ class InjectablesProvider extends Component {
2020

2121
getChildContext() {
2222
return {
23-
produceElements: (args) => this.produceElements(args),
23+
registerInjector: (args) => this.registerInjector(args),
2424

25-
removeProducer: (args) => this.removeProducer(args),
25+
removeInjector: (args) => this.removeInjector(args),
2626

27-
consumeElements: (args) => this.consumeElements(args),
27+
registerInjectable: (args) => this.registerInjectable(args),
2828

29-
stopConsumingElements: (args) => this.stopConsumingElements(args)
29+
removeInjectable: (args) => this.removeInjectable(args)
3030
};
3131
}
3232

@@ -38,11 +38,10 @@ class InjectablesProvider extends Component {
3838
)(this.registrations);
3939

4040
if (!registration) {
41-
// Need to create the registration.
4241
registration = {
4342
injectionId,
4443
injectables: [],
45-
injectors: []
44+
injections: []
4645
};
4746

4847
this.registrations.push(registration);
@@ -51,15 +50,14 @@ class InjectablesProvider extends Component {
5150
return registration;
5251
}
5352

54-
notifyConsumers(args: { registration: Object }) {
53+
runInjections(args: { registration: Object }) {
5554
const { registration } = args;
56-
const { injectables, injectors } = registration;
55+
const { injectables, injections } = registration;
5756

5857
const elements = compose(
59-
uniq,
60-
concatAll,
61-
map(x => x.elements)
62-
)(injectors);
58+
map(x => x.injector.getInjectElement()),
59+
uniqBy(`injectorId`)
60+
)(injections);
6361

6462
injectables.forEach(injectable => {
6563
injectable.consume(elements);
@@ -71,56 +69,66 @@ class InjectablesProvider extends Component {
7169
this.registrations = without(registration)(this.registrations);
7270
}
7371

74-
consumeElements(args: { injectionId: string, injectable: Object}) {
72+
registerInjectable(args: { injectionId: string, injectable: Object}) {
7573
const { injectionId, injectable } = args;
7674
const registration = this.getRegistration({ injectionId });
7775

7876
if (withoutAll(registration.injectables)([injectable]).length > 0) {
7977
registration.injectables = [...registration.injectables, injectable];
80-
this.notifyConsumers({ registration }); // First time consumption.
78+
this.runInjections({ registration }); // First time consumption.
8179
}
8280
}
8381

84-
stopConsumingElements(args: { injectionId: string, injectable: Object }) {
82+
removeInjectable(args: { injectionId: string, injectable: Object }) {
8583
const { injectionId, injectable } = args;
8684
const registration = this.getRegistration({ injectionId });
8785

8886
const injectables = without(injectable)(registration.injectables);
8987

90-
if (injectables.length === 0 && registration.injectors.length === 0) {
88+
if (injectables.length === 0 && registration.injections.length === 0) {
9189
this.removeRegistration({ registration });
9290
} else {
9391
registration.injectables = injectables;
9492
}
9593
}
9694

97-
findProducer({ registration, injector }) {
98-
return find(x => Object.is(x.injector, injector))(registration.injectors);
95+
findInjection({ registration, injector }) {
96+
return find(x => Object.is(x.injector, injector))(registration.injections);
9997
}
10098

101-
produceElements(args: { injectionId: string, injector: Object, elements: Array<Object> }) {
102-
const { injectionId, injector, elements } = args;
99+
registerInjector(args: { injectionId: string, injectorId: string, injector: Object }) {
100+
const { injectionId, injectorId, injector } = args;
103101
const registration = this.getRegistration({ injectionId });
104-
const existingProducer = this.findProducer({ registration, injector });
102+
const existingInjection = this.findInjection({ registration, injector });
105103

106-
if (existingProducer) {
104+
if (existingInjection) {
107105
return;
108106
}
109107

110-
const newInjector = { injector, elements };
111-
registration.injectors = [
112-
...registration.injectors,
113-
newInjector
108+
const newInjection = { injector, injectorId };
109+
registration.injections = [
110+
...registration.injections,
111+
newInjection
114112
];
115-
this.notifyConsumers({ registration });
113+
114+
this.runInjections({ registration });
116115
}
117116

118-
removeProducer(args: { injectionId: string, injector: Object }) {
117+
removeInjector(args: { injectionId: string, injector: Object }) {
119118
const { injectionId, injector } = args;
120119
const registration = this.getRegistration({ injectionId });
121-
const existingInjector = this.findProducer({ registration, injector });
122-
registration.injectors = without(existingInjector)(registration.injectors);
123-
this.notifyConsumers({ registration });
120+
const injection = this.findInjection({ registration, injector });
121+
122+
if (injection) {
123+
registration.injections = without(injection)(registration.injections);
124+
this.runInjections({ registration });
125+
} else {
126+
/* istanbul ignore next */
127+
if (process.env.NODE_ENV === `development`) {
128+
throw new Error(
129+
`Trying to remove an injector which has not been registered`);
130+
}
131+
}
124132
}
125133

126134
render() {
Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import React, { Children } from 'react';
1414
*/
1515
export function compose(...funcs) {
1616
return (...args) => {
17+
/* istanbul ignore next */
1718
if (funcs.length === 0) {
1819
return args[0];
1920
}
@@ -47,8 +48,21 @@ export const withoutAll = (toRemove) => (point) =>
4748
(x) => all(y => !Object.is(x, y))(toRemove)
4849
)(point);
4950

50-
// :: [a] -> [a]
51-
export const uniq = y => Array.from(new Set(y));
51+
// :: a -> [b]
52+
export const uniqBy = x => y => {
53+
const checked = new Set();
54+
const result = [];
55+
56+
y.forEach(a => {
57+
const prop = a[x];
58+
if (!checked.has(prop)) {
59+
checked.add(prop);
60+
result.push(a);
61+
}
62+
});
63+
64+
return result;
65+
};
5266

5367
/**
5468
* :: [a] -> [a] -> boolean

0 commit comments

Comments
 (0)