Skip to content

Commit 70c6269

Browse files
mjacksonMichael Jackson
authored andcommitted
Fixes for feedback
- Enforce <Subscriber> is rendered as descendant of <Broadcast> - Allow <Broadcast> to be rendered more than once
1 parent 181847c commit 70c6269

File tree

3 files changed

+60
-36
lines changed

3 files changed

+60
-36
lines changed

modules/__tests__/createBroadcast-test.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,19 @@ describe("A <Subscriber>", () => {
2323

2424
it("gets the initial broadcast value on the initial render", done => {
2525
const initialValue = "cupcakes"
26-
const { Subscriber } = createBroadcast(initialValue)
26+
const { Broadcast, Subscriber } = createBroadcast(initialValue)
2727

2828
let actualValue
2929

3030
ReactDOM.render(
31-
<Subscriber
32-
children={value => {
33-
actualValue = value
34-
return null
35-
}}
36-
/>,
31+
<Broadcast>
32+
<Subscriber
33+
children={value => {
34+
actualValue = value
35+
return null
36+
}}
37+
/>
38+
</Broadcast>,
3739
node,
3840
() => {
3941
expect(actualValue).toBe(initialValue)

modules/createBroadcast.js

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,29 @@
11
import React from "react"
22
import PropTypes from "prop-types"
3-
import invariant from "invariant"
3+
import warning from "warning"
44

55
function createBroadcast(initialValue) {
6-
let subscribers = []
76
let currentValue = initialValue
7+
let subscribers = []
88

99
const publish = value => {
1010
currentValue = value
11-
subscribers.forEach(subscriber => subscriber(currentValue))
11+
subscribers.forEach(s => s(currentValue))
1212
}
1313

1414
const subscribe = subscriber => {
1515
subscribers.push(subscriber)
16-
return () => (subscribers = subscribers.filter(item => item !== subscriber))
16+
17+
return () => {
18+
subscribers = subscribers.filter(s => s !== subscriber)
19+
}
1720
}
1821

19-
let broadcastInstance = null
22+
const channel = Symbol()
2023

2124
/**
2225
* A <Broadcast> is a container for a "value" that its <Subscriber>
23-
* may subscribe to. A <Broadcast> may only be rendered once.
26+
* may subscribe to.
2427
*/
2528
class Broadcast extends React.Component {
2629
/**
@@ -43,16 +46,24 @@ function createBroadcast(initialValue) {
4346
*/
4447
static initialValue = initialValue
4548

46-
componentDidMount() {
47-
invariant(
48-
broadcastInstance == null,
49-
"You cannot render the same <Broadcast> twice! There must be only one source of truth. " +
50-
"Instead of rendering another <Broadcast>, just change the `value` prop of the one " +
51-
"you already rendered."
52-
)
49+
static contextTypes = {
50+
broadcasts: PropTypes.object
51+
}
5352

54-
broadcastInstance = this
53+
static childContextTypes = {
54+
broadcasts: PropTypes.object.isRequired
55+
}
56+
57+
getChildContext() {
58+
return {
59+
broadcasts: {
60+
...this.context.broadcasts,
61+
[channel]: true
62+
}
63+
}
64+
}
5565

66+
componentDidMount() {
5667
if (this.props.value !== currentValue) {
5768
// TODO: Publish and warn about the double render
5869
// problem if there are existing subscribers? Or
@@ -66,12 +77,6 @@ function createBroadcast(initialValue) {
6677
}
6778
}
6879

69-
componentWillUnmount() {
70-
if (broadcastInstance === this) {
71-
broadcastInstance = null
72-
}
73-
}
74-
7580
render() {
7681
return this.props.children
7782
}
@@ -82,24 +87,41 @@ function createBroadcast(initialValue) {
8287
* and calls its render prop with the result.
8388
*/
8489
class Subscriber extends React.Component {
90+
static contextTypes = {
91+
broadcasts: PropTypes.object
92+
}
93+
8594
static propTypes = {
86-
children: PropTypes.func
95+
children: PropTypes.func,
96+
quiet: PropTypes.bool
97+
}
98+
99+
static defaultProps = {
100+
quiet: false
87101
}
88102

89103
state = {
90104
value: currentValue
91105
}
92106

93107
componentDidMount() {
94-
this.unsubscribe = subscribe(value => {
95-
this.setState({ value })
96-
})
108+
const broadcasts = this.context.broadcasts
109+
const inContext = broadcasts && broadcasts[channel]
110+
111+
warning(
112+
inContext || this.props.quiet,
113+
"<Subscriber> was rendered outside the context of its <Broadcast>"
114+
)
115+
116+
if (inContext) {
117+
this.unsubscribe = subscribe(value => {
118+
this.setState({ value })
119+
})
120+
}
97121
}
98122

99123
componentWillUnmount() {
100-
if (this.unsubscribe) {
101-
this.unsubscribe()
102-
}
124+
if (this.unsubscribe) this.unsubscribe()
103125
}
104126

105127
render() {

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
"react": ">=15 || ^16"
2525
},
2626
"dependencies": {
27-
"invariant": "^2.2.1",
28-
"prop-types": "^15.6.0"
27+
"prop-types": "^15.6.0",
28+
"warning": "^3.0.0"
2929
},
3030
"devDependencies": {
3131
"babel-core": "^6.17.0",

0 commit comments

Comments
 (0)