Skip to content

Commit c395051

Browse files
committed
Updated README examples
1 parent 88e7e22 commit c395051

File tree

1 file changed

+109
-64
lines changed
  • packages/create-component-with-subscriptions

1 file changed

+109
-64
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,126 @@
11
# create-component-with-subscriptions
22

3-
Below is an example showing how the container can be used:
3+
[Async-safe subscriptions are hard to get right.](https://gist.github.com/bvaughn/d569177d70b50b58bff69c3c4a5353f3)
4+
5+
This complexity is acceptible for libraries like Redux/Relay/MobX, but it's not ideal to have mixed in with application code. `create-component-with-subscriptions` provides an interface to easily manage subscriptions in an async-safe way.
6+
7+
## Installation
8+
9+
```sh
10+
# Yarn
11+
yarn add create-component-with-subscriptions
12+
13+
# NPM
14+
npm install create-component-with-subscriptions --save
15+
```
16+
17+
# API
18+
19+
Creating a subscription component requires a configuration object and a React component. The configuration object must have four properties:
20+
* **subscribablePropertiesMap** `{[subscribableProperty: string]: string}` - Maps property names of incoming subscribable sources (e.g. "eventDispatcher") to property names for their values (e.g. "value").
21+
* **getDataFor** `(subscribable: any, propertyName: string) => any` - Synchronously returns the value of the specified subscribable property. If your component has multiple subscriptions,the second 'propertyName' parameter can be used to distinguish between them.
22+
* **subscribeTo** `(
23+
valueChangedCallback: (value: any) => void,
24+
subscribable: any,
25+
propertyName: string,
26+
) => any` - Subscribes to the specified subscribable and call the `valueChangedCallback` parameter whenever a subscription changes. If your component has multiple subscriptions, the third 'propertyName' parameter can be used to distinguish between them.
27+
* **unsubscribeFrom** `(
28+
subscribable: any,
29+
propertyName: string,
30+
subscription: any,
31+
) => void` - Unsubscribes from the specified subscribable. If your component has multiple subscriptions, the second `propertyName` parameter can be used to distinguish between them. The value returned by `subscribeTo()` is the third `subscription` parameter.
32+
33+
# Examples
34+
35+
## Subscribing to event dispatchers
36+
37+
Below is an example showing how `create-component-with-subscriptions` can be used to subscribe to event dispatchers such as DOM elements or Flux stores.
438

539
```js
6-
// This is an example functional component that subscribes to some values.
7-
function ExampleComponent({
8-
examplePassThroughProperty,
9-
friendsList,
10-
userProfile
11-
}) {
12-
// The rendered output of this component is not very important.
13-
// It just exists to show how the observed values are provided.
14-
// Properties not related to subscriptions are passed through as-is,
15-
// (e.g. examplePassThroughProperty).
16-
}
40+
import React from "react";
41+
import createComponent from "create-component-with-subscriptions";
1742

18-
// In the below example, "friendsList" mimics an RxJS BehaviorSubject,
19-
// and "userProfile" mimics an event dispatcher (like a DOM element).
20-
function getDataFor(subscribable, propertyName) {
21-
switch (propertyName) {
22-
case "friendsListSubject":
23-
return subscribable.getValue();
24-
case "userProfile":
25-
return subscribable.value;
26-
default:
27-
throw Error(`Invalid subscribable, "${propertyName}", specified.`);
28-
}
43+
// Start with a simple functional (or class-based) component.
44+
function InnerComponent({ followerCount, username }) {
45+
return (
46+
<div>
47+
{username} has {followerCount} follower
48+
</div>
49+
);
2950
}
3051

31-
function subscribeTo(valueChangedCallback, subscribable, propertyName) {
32-
switch (propertyName) {
33-
case "friendsListSubject":
34-
// Return the subscription in this case; it's necessary to unsubscribe.
35-
return subscribable.subscribe(valueChangedCallback);
36-
case "userProfile":
37-
const onChange = () => valueChangedCallback(subscribable.value);
52+
// Wrap the functional component with a subscriber HOC.
53+
// This HOC will manage subscriptions and pass values to the decorated component.
54+
// It will add and remove subscriptions in an async-safe way when props change.
55+
const FollowerCountComponent = createComponent(
56+
{
57+
subscribablePropertiesMap: { followerStore: "followerCount" },
58+
getDataFor: (subscribable, propertyName) => subscribable.value,
59+
subscribeTo: (valueChangedCallback, subscribable, propertyName) => {
60+
const onChange = event => valueChangedCallback(subscribable.value);
3861
subscribable.addEventListener(onChange);
39-
// Return the event handling callback, since it's required to unsubscribe.
4062
return onChange;
41-
default:
42-
throw Error(`Invalid subscribable, "${propertyName}", specified.`);
43-
}
44-
}
45-
46-
function unsubscribeFrom(subscribable, propertyName, subscription) {
47-
switch (propertyName) {
48-
case "friendsListSubject":
49-
// Unsubscribe using the subscription rather than the subscribable.
50-
subscription.unsubscribe();
51-
case "userProfile":
52-
// In this case, 'subscription', is the event handler/function.
63+
},
64+
unsubscribeFrom: (subscribable, propertyName, subscription) => {
65+
// `subscription` is the value returned from subscribeTo, our event handler.
5366
subscribable.removeEventListener(subscription);
54-
break;
55-
default:
56-
throw Error(`Invalid subscribable, "${propertyName}", specified.`);
57-
}
67+
}
68+
},
69+
InnerComponent
70+
);
71+
72+
// Your component can now be used as shown below.
73+
// (In this example, `followerStore` represents a generic event dispatcher.)
74+
<FollowerCountComponent followerStore={followerStore} username="Brian" />;
75+
```
76+
77+
## Subscribing to observables
78+
79+
Below is an example showing how `create-component-with-subscriptions` can be used to subscribe to certain types of observables (e.g. RxJS `BehaviorSubject` and `ReplaySubject`).
80+
81+
**Note** that it is not possible to support all observable types (e.g. RxJS `Subject` or `Observable`) because some provide no way to read the "current" value after it has been emitted.
82+
83+
```js
84+
import React from "react";
85+
import createComponent from "create-component-with-subscriptions";
86+
87+
function InnerComponent({ behaviorValue, replayValue }) {
88+
// Render ...
5889
}
5990

60-
// Map incoming subscriptions property names (e.g. friendsListSubject)
61-
// to property names expected by our functional component (e.g. friendsList).
62-
const subscribablePropertiesMap = {
63-
friendsListSubject: "friendsList",
64-
userProfile: "userProfile"
65-
};
66-
67-
// Decorate our functional component with a subscriber component.
68-
// This HOC will automatically manage subscriptions to the incoming props,
69-
// and map them to subscribed values to be passed to the inner component.
70-
// All other props will be passed through as-is.
71-
export default createSubscribable(
91+
const SubscribedComponent = createComponent(
7292
{
73-
getDataFor,
74-
subscribablePropertiesMap,
75-
subscribeTo,
76-
unsubscribeFrom
93+
subscribablePropertiesMap: {
94+
behaviorSubject: "behaviorValue",
95+
replaySubject: "replayValue"
96+
},
97+
getDataFor: (subscribable, propertyName) => {
98+
switch (propertyName) {
99+
case "behaviorSubject":
100+
return subscribable.getValue();
101+
case "replaySubject":
102+
let currentValue;
103+
// ReplaySubject does not have a sync data getter,
104+
// So we need to temporarily subscribe to retrieve the most recent value.
105+
const temporarySubscription = subscribable.subscribe(value => {
106+
currentValue = value;
107+
});
108+
temporarySubscription.unsubscribe();
109+
return currentValue;
110+
}
111+
},
112+
subscribeTo: (valueChangedCallback, subscribable, propertyName) =>
113+
subscribable.subscribe(valueChangedCallback),
114+
unsubscribeFrom: (subscribable, propertyName, subscription) =>
115+
subscription.unsubscribe()
77116
},
78-
ExampleComponent
117+
InnerComponent
79118
);
80119

120+
// Your component can now be used as shown below.
121+
// In this example, both properties below represent RxJS types with the same name.
122+
<SubscribedComponent
123+
behaviorSubject={behaviorSubject}
124+
replaySubject={replaySubject}
125+
/>;
81126
```

0 commit comments

Comments
 (0)