-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This is an API overhaul to more closely match the API currently being proposed in reactjs/rfcs#2 The main goals of this work are: - Conform more closely to the upcoming context API, to make it easier for people to migrate off react-broadcast when that API eventually lands - Remove reliance on context entirely, since eventually it'll be gone - Remove ambiguity around "channel"s The new API looks like: const { Broadcast, Subscriber } = createBroadcast(initialValue) <Broadcast value="anything-you-want-here"> <Subscriber children={value => ( // ... )} /> </Broadcast> Instead of providing pre-built <Broadcast> and <Subscriber> components, we provide a createBroadcast function that may be used to create them. See the README for further usage instructions.
- Loading branch information
Showing
4 changed files
with
228 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import React from "react" | ||
import ReactDOM from "react-dom" | ||
import { Simulate } from "react-dom/test-utils" | ||
import createBroadcast from "../createBroadcast" | ||
|
||
describe("createBroadcast", () => { | ||
it("creates a Broadcast component", () => { | ||
const { Broadcast } = createBroadcast() | ||
expect(typeof Broadcast).toBe("function") | ||
}) | ||
|
||
it("creates a Subscriber component", () => { | ||
const { Subscriber } = createBroadcast() | ||
expect(typeof Subscriber).toBe("function") | ||
}) | ||
}) | ||
|
||
describe("A <Subscriber>", () => { | ||
let node | ||
beforeEach(() => { | ||
node = document.createElement("div") | ||
}) | ||
|
||
it("gets the initial broadcast value on the initial render", done => { | ||
const initialValue = "cupcakes" | ||
const { Subscriber } = createBroadcast(initialValue) | ||
|
||
let actualValue | ||
|
||
ReactDOM.render( | ||
<Subscriber | ||
children={value => { | ||
actualValue = value | ||
return null | ||
}} | ||
/>, | ||
node, | ||
() => { | ||
expect(actualValue).toBe(initialValue) | ||
done() | ||
} | ||
) | ||
}) | ||
|
||
it("gets the updated broadcast value as it changes", done => { | ||
const { Broadcast, Subscriber } = createBroadcast("cupcakes") | ||
|
||
class Parent extends React.Component { | ||
state = { | ||
value: Broadcast.initialValue | ||
} | ||
|
||
render() { | ||
return ( | ||
<Broadcast value={this.state.value}> | ||
<button | ||
onClick={() => this.setState({ value: "bubblegum" })} | ||
ref={node => (this.button = node)} | ||
/> | ||
<Child /> | ||
</Broadcast> | ||
) | ||
} | ||
} | ||
|
||
let childDidRender = false | ||
|
||
class Child extends React.Component { | ||
// Make sure we can bypass a sCU=false! | ||
shouldComponentUpdate() { | ||
return false | ||
} | ||
|
||
render() { | ||
return ( | ||
<Subscriber | ||
children={value => { | ||
if (childDidRender) { | ||
expect(value).toBe("bubblegum") | ||
done() | ||
} else { | ||
expect(value).toBe(Broadcast.initialValue) | ||
} | ||
|
||
childDidRender = true | ||
|
||
return null | ||
}} | ||
/> | ||
) | ||
} | ||
} | ||
|
||
ReactDOM.render(<Parent />, node, function() { | ||
Simulate.click(this.button) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import React from "react" | ||
import PropTypes from "prop-types" | ||
import invariant from "invariant" | ||
|
||
function createBroadcast(initialValue) { | ||
let subscribers = [] | ||
let currentValue = initialValue | ||
|
||
const publish = value => { | ||
currentValue = value | ||
subscribers.forEach(subscriber => subscriber(currentValue)) | ||
} | ||
|
||
const subscribe = subscriber => { | ||
subscribers.push(subscriber) | ||
return () => (subscribers = subscribers.filter(item => item !== subscriber)) | ||
} | ||
|
||
let broadcastInstance = null | ||
|
||
/** | ||
* A <Broadcast> is a container for a "value" that its <Subscriber> | ||
* may subscribe to. A <Broadcast> may only be rendered once. | ||
*/ | ||
class Broadcast extends React.Component { | ||
/** | ||
* For convenience when setting up a component that tracks this <Broadcast>'s | ||
* value in state. | ||
* | ||
* const { Broadcast, Subscriber } = createBroadcast("value") | ||
* | ||
* class MyComponent { | ||
* state = { | ||
* broadcastValue: Broadcast.initialValue | ||
* } | ||
* | ||
* // ... | ||
* | ||
* render() { | ||
* return <Broadcast value={this.state.broadcastValue}/> | ||
* } | ||
* } | ||
*/ | ||
static initialValue = initialValue | ||
|
||
componentDidMount() { | ||
invariant( | ||
broadcastInstance == null, | ||
"You cannot render the same <Broadcast> twice! There must be only one source of truth. " + | ||
"Instead of rendering another <Broadcast>, just change the `value` prop of the one " + | ||
"you already rendered." | ||
) | ||
|
||
broadcastInstance = this | ||
|
||
if (this.props.value !== currentValue) { | ||
// TODO: Publish and warn about the double render | ||
// problem if there are existing subscribers? Or | ||
// just ignore the discrepancy? | ||
} | ||
} | ||
|
||
componentWillReceiveProps(nextProps) { | ||
if (this.props.value !== nextProps.value) { | ||
publish(nextProps.value) | ||
} | ||
} | ||
|
||
componentWillUnmount() { | ||
if (broadcastInstance === this) { | ||
broadcastInstance = null | ||
} | ||
} | ||
|
||
render() { | ||
return this.props.children | ||
} | ||
} | ||
|
||
/** | ||
* A <Subscriber> sets state whenever its <Broadcast value> changes | ||
* and calls its render prop with the result. | ||
*/ | ||
class Subscriber extends React.Component { | ||
static propTypes = { | ||
children: PropTypes.func | ||
} | ||
|
||
state = { | ||
value: currentValue | ||
} | ||
|
||
componentDidMount() { | ||
this.unsubscribe = subscribe(value => { | ||
this.setState({ value }) | ||
}) | ||
} | ||
|
||
componentWillUnmount() { | ||
if (this.unsubscribe) { | ||
this.unsubscribe() | ||
} | ||
} | ||
|
||
render() { | ||
const { children } = this.props | ||
return children ? children(this.state.value) : null | ||
} | ||
} | ||
|
||
return { | ||
Broadcast, | ||
Subscriber | ||
} | ||
} | ||
|
||
export default createBroadcast |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
export Broadcast from "./Broadcast"; | ||
export Subscriber from "./Subscriber"; | ||
|
||
export createBroadcast from "./createBroadcast"; |