Skip to content

Commit 18c193f

Browse files
committed
Updated README examples
1 parent 88e7e22 commit 18c193f

File tree

1 file changed

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

1 file changed

+99
-64
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,116 @@
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+
# API
8+
9+
Creating a subscription component requires a configuration object and a React component. The configuration object must have four properties:
10+
* **subscribablePropertiesMap** `{[subscribableProperty: string]: string}` - Maps property names of incoming subscribable sources (e.g. "eventDispatcher") to property names for their values (e.g. "value").
11+
* **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.
12+
* **subscribeTo** `(
13+
valueChangedCallback: (value: any) => void,
14+
subscribable: any,
15+
propertyName: string,
16+
) => 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.
17+
* **unsubscribeFrom** `(
18+
subscribable: any,
19+
propertyName: string,
20+
subscription: any,
21+
) => 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.
22+
23+
# Examples
24+
25+
## Subscribing to event dispatchers
26+
27+
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.
428

529
```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-
}
30+
import React from "react";
31+
import createComponent from "create-component-with-subscriptions";
1732

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-
}
33+
// Start with a simple functional (or class-based) component.
34+
function InnerComponent({ followerCount, username }) {
35+
return (
36+
<div>
37+
{username} has {followerCount} follower
38+
</div>
39+
);
2940
}
3041

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);
42+
// Wrap the functional component with a subscriber HOC.
43+
// This HOC will manage subscriptions and pass values to the decorated component.
44+
// It will add and remove subscriptions in an async-safe way when props change.
45+
const FollowerCountComponent = createComponent(
46+
{
47+
subscribablePropertiesMap: { followerStore: "followerCount" },
48+
getDataFor: (subscribable, propertyName) => subscribable.value,
49+
subscribeTo: (valueChangedCallback, subscribable, propertyName) => {
50+
const onChange = event => valueChangedCallback(subscribable.value);
3851
subscribable.addEventListener(onChange);
39-
// Return the event handling callback, since it's required to unsubscribe.
4052
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.
53+
},
54+
unsubscribeFrom: (subscribable, propertyName, subscription) => {
55+
// `subscription` is the value returned from subscribeTo, our event handler.
5356
subscribable.removeEventListener(subscription);
54-
break;
55-
default:
56-
throw Error(`Invalid subscribable, "${propertyName}", specified.`);
57-
}
57+
}
58+
},
59+
InnerComponent
60+
);
61+
62+
// Your component can now be used as shown below.
63+
// (In this example, `followerStore` represents a generic event dispatcher.)
64+
<FollowerCountComponent followerStore={followerStore} username="Brian" />;
65+
```
66+
67+
## Subscribing to observables
68+
69+
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`).
70+
71+
**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.
72+
73+
```js
74+
import React from "react";
75+
import createComponent from "create-component-with-subscriptions";
76+
77+
function InnerComponent({ behaviorValue, replayValue }) {
78+
// Render ...
5879
}
5980

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(
81+
const SubscribedComponent = createComponent(
7282
{
73-
getDataFor,
74-
subscribablePropertiesMap,
75-
subscribeTo,
76-
unsubscribeFrom
83+
subscribablePropertiesMap: {
84+
behaviorSubject: "behaviorValue",
85+
replaySubject: "replayValue"
86+
},
87+
getDataFor: (subscribable, propertyName) => {
88+
switch (propertyName) {
89+
case "behaviorSubject":
90+
return subscribable.getValue();
91+
case "replaySubject":
92+
let currentValue;
93+
// ReplaySubject does not have a sync data getter,
94+
// So we need to temporarily subscribe to retrieve the most recent value.
95+
const temporarySubscription = subscribable.subscribe(value => {
96+
currentValue = value;
97+
});
98+
temporarySubscription.unsubscribe();
99+
return currentValue;
100+
}
101+
},
102+
subscribeTo: (valueChangedCallback, subscribable, propertyName) =>
103+
subscribable.subscribe(valueChangedCallback),
104+
unsubscribeFrom: (subscribable, propertyName, subscription) =>
105+
subscription.unsubscribe()
77106
},
78-
ExampleComponent
107+
InnerComponent
79108
);
80109

110+
// Your component can now be used as shown below.
111+
// In this example, both properties below represent RxJS types with the same name.
112+
<SubscribedComponent
113+
behaviorSubject={behaviorSubject}
114+
replaySubject={replaySubject}
115+
/>;
81116
```

0 commit comments

Comments
 (0)