hover
hover copied to clipboard
react context
Looking at geiger I saw that context are used in an interesting fashion. https://github.com/netgusto/Geiger
There is also a blog post about react context. https://www.tildedave.com/2014/11/15/introduction-to-contexts-in-react-js.html (old)
It would be great to document how to use it similar to how the waitFor was documented.
Here is a use case: Assume you have a widget (component) that would want to share. Let us take twitter feed as an example. It is a pretty complex component so you would want to have a custom store for each instance of the component.
var twitterstore = Hoverboard(TwitterStore);
<Context twitterstore={twitterstore}>
<TwitterFeed />
</Context>
var TwitterFeed = React.createClass({
static contextTypes: {
twitterstore: React.PropTypes.object.isRequired
},
componentDidMount() {
this.twitterstore.getState(state => this.setState({ tweets: tweet }));
},
render() {
return this.state.tweets.map(t => <Tweet tweet={t} />);
}
};
var Tweet = React.createClass({
static contextTypes: {
twitterstore: React.PropTypes.object.isRequired
},
onRewteet(e) {
e.preventDefault();
this.context.twitterstore.Retweet(this.props.tweet.id);
},
render() {
return <div>{this.props.tweet.text} <input type="button" onClick={this.onRewteet} value="Retweet" /></div>
}
};
Assume that now the twitterstore is per user. I can now have multiple TwitterFeed component that works independently in the same page.
var userATwitterStore = Hoverboard(TwitterStore);
userATwitterStore.setUserId('A');
<Context twitterstore={userATwitterStore }>
<TwitterFeed />
</Context>
var userBTwitterStore = Hoverboard(TwitterStore);
userBTwitterStore.setUserId('B');
<Context twitterstore={userBTwitterStore }>
<TwitterFeed />
</Context>
That looks interesting as an alternative to passing around a store as a prop, and way better than using global variables for stores. My understanding is that contexts remain an unofficial and/or undocumented feature that will likely change in upcoming versions. I'm going to close this issue for now and maybe revisit once the feature becomes more standardized. But thanks for the explanation and pointer!
I'm going to give this another shot and come up with a React component that can be used to provide props and actions to a subset of the React render tree.
I tinkered around with context, but I think I found a nicer approach that doesn't use context. I found that being forced to use this.context contentTypes etc. was a bit awkward, where I just wanted to have props.
If you did want to use context, you could pass the data into the context of the Name component so that it'll be passed down the tree.
In my solution, I'm copying something I saw in Redux - using a function as a child of a component, so that you can take the store's state and actions and compose them onto the children element(s).
Here's an example that's working with current Hoverboard v1:
var nameActions = {
getInitialState: function () {
return { name: 'Jesse' };
},
onChangeName: function(name) {
this.setState({ name: name });
}
};
var HoverboardContext = React.createClass({
getInitialState: function () {
return {};
},
componentDidMount: function () {
var actions = Hoverboard(this.props.actions);
this._unsubscribe = actions.getState(function (state) {
this.setState({
state: state,
actions: actions
});
}.bind(this));
},
componentWillUnmount: function () {
if (this._unsubscribe) {
this._unsubscribe();
this._unsubscribe = null;
}
},
render: function() {
if (this.state.state && this.state.actions) {
return this.props.children(this.state.state, this.state.actions);
}
return null;
}
});
var Name = React.createClass({
handleClick: function () {
var name = prompt('What is your name?', this.props.name);
this.props.changeName(name);
},
render: function() {
return <div onClick={this.handleClick}>
Your name is: {this.props.name}.
(Click to edit.)
</div>;
}
});
var App = React.createClass({
render: function() {
return <HoverboardContext actions={nameActions}>{
// note that this is a function passed as child to HoverboardContext
function (state, actions) {
return <Name name={state.name} changeName={actions.changeName} />;
}
}</HoverboardContext>;
}
});
Thoughts?
I may have found a bug in React that discouraged me from using context. It seems props.children aren't re-rendered when the context (or state) changes. You have to do a forceUpdate on the parent to get the props.children to update themselves. Since React is moving to have context follow the parents and not the owners, they might want to fix this accordingly.
Ah - this is known, and is in discussion. So I think a good context-based solution will need this to be figured out first. https://github.com/facebook/react/issues/2517
Where do I specify the stores I'm interested in? Something like the redux's connect. I want to only render a component when a particular store's state changes.
@prabirshrestha In my example, you create a new store using nameActions every time you create a <HoverboardContext/>, and the contents get updated whenever that store instance's state changes.
So I guess if you wanted multiple stores you could either create a third store to combine the results of the two, or do something along the lines of:
return <HoverboardContext actions={storeA}>{
function (stateA, actionsA) {
return <HoverboardContext actions={storeB}>{
function (stateB, actionsB) {
return <Name stateA={stateA} stateB={stateB} actionsA={actionsA} />;
}
}</HoverboardContext>;
}
}</HoverboardContext>;
And I guess if you already had stores and wanted to reuse them, you could modify the <HoverboardContext/> and change this line:
var actions = Hoverboard(this.props.actions);
to this:
var actions = this.props.store;
and/or change it to accept an array of stores and iterate over them to wire up the subscription, or whatever you think makes sense.
I guess my main point is that all of this is done outside of Hoverboard so anything is possible.