React Native - Call Function of child from NavigatorIOS - reactjs

I am trying to call a child function from the right button on the parent navigator.
A basic code example of what I need is as follows:
Main.js
<NavigatorIOS
style={styles.container}
initialRoute={{
title: 'Test',
component: Test,
rightButtonTitle: 'Change String',
onRightButtonPress: () => ***I Want to call miscFunction from here*** ,
passProps: {
FBId: this.state.fbidProp,
favsPage: true
}
}}/>
Test.js
class Test extends React.Component{
constructor(props){
super(props);
this.state = {
variable: 'some string'
};
}
miscFunction(){
this.setState({
variable: 'new string'
};
}
render(){
return(
<Text> {variable} </Text>
)
}
}

This is covered in the following github issue:
https://github.com/facebook/react-native/issues/31
Eric Vicenti comments to describe how Facebook solves this internally:
Currently the best way to do that is to create an EventEmitter in the owner of the NavigatorIOS, then you can pass it down to children using route.passProps. The child can mix in Subscribable.Mixin and then in componentDidMount, you can
this.addListenerOn(this.props.events, 'myRightBtnEvent', this._handleRightBtnPress);
It is clear that this API needs improvement. We are actively working the routing API in Relay, and hopefully react-router, but we want NavigatorIOS to be usable independently. Maybe we should add an event emitter inside the navigator object, so child components can subscribe to various navigator activity:
this.addListenerOn(this.props.navigator.events, 'rightButtonPress', this._handleRightBtnPress);
Here's how this looks in a practical example:
'use strict';
var React = require('react-native');
var EventEmitter = require('EventEmitter');
var Subscribable = require('Subscribable');
var {
AppRegistry,
StyleSheet,
Text,
View,
NavigatorIOS
} = React;
First we pull in all of our requirements including the EventEmitter and Subscribable.
var App = React.createClass({
componentWillMount: function() {
this.eventEmitter = new EventEmitter();
},
onRightButtonPress: function() {
this.eventEmitter.emit('myRightBtnEvent', { someArg: 'argValue' });
},
render: function() {
return <NavigatorIOS
style={styles.container}
initialRoute={{
title: 'Test',
component: Test,
rightButtonTitle: 'Change String',
onRightButtonPress: this.onRightButtonPress,
passProps: {
events: this.eventEmitter
}
}}/>
}
});
In our main top-level component, we create a new EventEmitter (in componentWillMount) to be available across the component, and then use passProps to pass it down to the Test component we specify for the navigator.
We also define a handler for the right button press, which emits a myRightBtnEvent with some dummy arguments when that button is pressed. Now, in the Test component:
var Test = React.createClass({
mixins: [Subscribable.Mixin],
getInitialState: function() {
return {
variable: 'original string'
};
},
componentDidMount: function() {
this.addListenerOn(this.props.events, 'myRightBtnEvent', this.miscFunction);
},
miscFunction: function(args){
this.setState({
variable: args.someArg
});
},
render: function(){
return(
<View style={styles.scene}><Text>{this.state.variable}</Text></View>
)
}
});
We add the Subscribable mixin, and the only other thing we need to do is listen out for the myRightBtnEvent being fired from the App component and hook miscFunction up to it. The miscFunction will be passed the dummy arguments from the App press handler so we can use those to set state or perform other actions.
You can see a working version of this on RNPlay:
https://rnplay.org/apps/H5mMNQ

A. In initial component
this.props.navigator.push({
title: 'title',
component: MyComponent,
rightButtonTitle: 'rightButton',
passProps: {
ref: (component) => {this.pushedComponent = component},
},
onRightButtonPress: () => {
// call func
this.pushedComponent && this.pushedComponent.myFunc();
},
});
B. In pushed component
replace onRightButtonPress func in pushed component.
componentDidMount: function() {
// get current route
var route = this.props.navigator.navigationContext.currentRoute;
// update onRightButtonPress func
route.onRightButtonPress = () => {
// call func in pushed component
this.myFunc();
};
// component will not rerender
this.props.navigator.replace(route);
},

You can use the flux, here is a demo: https://github.com/backslash112/react-native_flux_demo

Related

How to use setState in ReactJS?

In the follow code, I thought the text should update to the new one after 3 seconds:
https://jsfiddle.net/smrfcr9x/1/
var Component = React.createClass({
render: function() {
return React.DOM.span(null, "hello " + this.props.text);
}
});
var aComponent = React.render(
React.createElement(Component, {
text: "abcd"
}),
document.getElementById("app")
);
setTimeout(function() {
console.log("now setting state");
aComponent.setState({
text: "lmno"
});
}, 3000);
How is it actually done?
You should use setProps to replace setState.
Please notice the warning in console from react.js:
Warning: setProps(...) and replaceProps(...) are deprecated. Instead, call render again at the top level.
You may follow that warning.
EDIT:
Sorry for haven't said it clearly just now.
Using setProps to replace setState can do correctly what you want. But the method setProps has been deprecated and will show the warning above. So I think you should follow the waring, once you want to change the props, rerender the component.
You read the text from props not state in the component, so it won't work if you set the state.
EDIT2:
var Component = React.createClass({
render: function() {
return React.DOM.span(null, "hello " + this.props.text);
}
});
var renderComponent = function(text){
React.render(
React.createElement(Component, {
text: text
}),
document.getElementById("app")
);
}
renderComponent('abcd')
setTimeout(function() {
console.log("now setting state");
renderComponent('lmno')
}, 3000);
I'm sorry for not knowing how to share code in jsfiddle. This code work fine in it.
Ok, I think I get it. The code to needs to set a state, and in render, use this.state instead of this.props:
https://jsfiddle.net/smrfcr9x/7/
var Component = React.createClass({
getInitialState: function() {
return {
text: this.props.text,
};
},
render: function() {
return React.DOM.span(null, "hello " + this.state.text);
}
});

ReactJS Server Side rendering click event won't fire

I have this code which is compiled server side with node-jsx but click events do not fire. Being novice I can't figure out what I missed
/** #jsx React.DOM */
var React = require('react/addons')
var mui = require('material-ui');
var ThemeManager = new mui.Styles.ThemeManager();
var injectTapEventPlugin = require("react-tap-event-plugin");
var UnyDentApp = React.createClass({
childContextTypes: {
muiTheme: React.PropTypes.object
},
getChildContext: function() {
return {
muiTheme: ThemeManager.getCurrentTheme()
};
},
componentDidMount: function () {
},
render: function () {
var menuItems = [
{ route: 'home', text: 'Home' },
{ route: 'about', text: 'About' },
];
return (
<div id="uny-dent">
<mui.LeftNav
ref='leftNav'
menuItems={menuItems}
docked={false} />
<mui.AppBar
title="UnyDent" onMenuIconButtonTouchTap={ this._handleClick }/>
</div>
)
},
_handleClick: function()
{
alert('ok');
},
toggleNav: function(e){
e.preventDefault();
alert();
this.refs.leftNav.toggle();
}
});
/* Module.exports instead of normal dom mounting */
module.exports = UnyDentApp;
It looks like you're attempting to do what is commonly referred to as "Isomorphic React". This differs from simply rendering a react template server-side (resulting in rendered static page) in that it also supports "mounting" the react component(s) client-side (resulting in a rendered dynamic page).
Right now you're simply doing the former, and therefore React isn't actually running client-side, so the click-event isn't actually "wired up". There are several different solutions to isomorphic react. Here's one in particular from the Paypal team: https://github.com/paypal/react-engine

How do I set state of sibling components easily in React?

I have got the beginnings of a clickable list component that will serve to drive a select element. As you can see from the below, onClick of the ListItem, I'm passing the state of a child element (ListItem in this case) to the parents (SelectableList, and CustomSelect component). This is working fine. However, what I would also like to do is change the state of the sibling components (the other ListItems) so that I can toggle their selected states when one of the ListItems is clicked.
At the moment, I'm simply using document.querySelectorAll('ul.cs-select li) to grab the elements and change the class to selected when it doesn't match the index of the clicked ListItem. This works - to an extent. However, after a few clicks, the state of the component has not been updated by React (only by client side JS), and things start to break down. What I would like to do is change the this.state.isSelected of the sibling list items, and use this state to refresh the SelectableList component. Could anyone offer a better alternative to what I've written below?
var React = require('react');
var SelectBox = require('./select-box');
var ListItem = React.createClass({
getInitialState: function() {
return {
isSelected: false
};
},
toggleSelected: function () {
if (this.state.isSelected == true) {
this.setState({
isSelected: false
})
} else {
this.setState({
isSelected: true
})
}
},
handleClick: function(listItem) {
this.toggleSelected();
this.props.onListItemChange(listItem.props.value);
var unboundForEach = Array.prototype.forEach,
forEach = Function.prototype.call.bind(unboundForEach);
forEach(document.querySelectorAll('ul.cs-select li'), function (el) {
// below is trying to
// make sure that when a user clicks on a list
// item in the SelectableList, then all the *other*
// list items get class="selected" removed.
// this works for the first time that you move through the
// list clicking the other items, but then, on the second
// pass through, starts to fail, requiring *two clicks* before the
// list item is selected again.
// maybe there's a better more "reactive" method of doing this?
if (el.dataset.index != listItem.props.index && el.classList.contains('selected') ) {
el.classList.remove('selected');
}
});
},
render: function() {
return (
<li ref={"listSel"+this.props.key}
data-value={this.props.value}
data-index={this.props.index}
className={this.state.isSelected == true ? 'selected' : '' }
onClick={this.handleClick.bind(null, this)}>
{this.props.content}
</li>
);
}
});
var SelectableList = React.createClass({
render: function() {
var listItems = this.props.options.map(function(opt, index) {
return <ListItem key={index} index={index}
value={opt.value} content={opt.label}
onListItemChange={this.props.onListItemChange.bind(null, index)} />;
}, this);
return <ul className="cs-select">{ listItems }</ul>;
}
})
var CustomSelect = React.createClass({
getInitialState: function () {
return {
selectedOption: ''
}
},
handleListItemChange: function(listIndex, listItem) {
this.setState({
selectedOption: listItem.props.value
})
},
render: function () {
var options = [{value:"One", label: "One"},{value:"Two", label: "Two"},{value:"Three", label: "Three"}];
return (
<div className="group">
<div className="cs-select">
<SelectableList options={options}
onListItemChange={this.handleListItemChange} />
<SelectBox className="cs-select"
initialValue={this.state.selectedOption}
fieldName="custom-select" options={options}/>
</div>
</div>
)
}
})
module.exports = CustomSelect;
The parent component should pass a callback to the children, and each child would trigger that callback when its state changes. You could actually hold all of the state in the parent, using it as a single point of truth, and pass the "selected" value down to each child as a prop.
In that case, the child could look like this:
var Child = React.createClass({
onToggle: function() {
this.props.onToggle(this.props.id, !this.props.selected);
},
render: function() {
return <button onClick={this.onToggle}>Toggle {this.props.label} - {this.props.selected ? 'Selected!' : ''}!</button>;
}
});
It has no state, it just fires an onToggle callback when clicked. The parent would look like this:
var Parent = React.createClass({
getInitialState: function() {
return {
selections: []
};
},
onChildToggle: function(id, selected) {
var selections = this.state.selections;
selections[id] = selected;
this.setState({
selections: selections
});
},
buildChildren: function(dataItem) {
return <Child
id={dataItem.id}
label={dataItem.label}
selected={this.state.selections[dataItem.id]}
onToggle={this.onChildToggle} />
},
render: function() {
return <div>{this.props.data.map(this.buildChildren)}</div>
}
});
It holds an array of selections in state and when it handles the callback from a child, it uses setState to re-render the children by passing its state down in the selected prop to each child.
You can see a working example of this here:
https://jsfiddle.net/fth25erj/
Another strategy for sibling-sibling communication is to use observer pattern.
The Observer Pattern is a software design pattern in which an object can send messages to multiple other objects.
No sibling or parent-child relationship is required to use this strategy.
Within the context of React, this would mean some components subscribe to receive particular messages and other components publish messages to those subscribers.
Components would typically subscribe in the componentDidMount method and unsubscribe in the componentWillUnmount method.
Here are 4 libraries that implement the Observer Pattern. The differences between them are subtle - EventEmitter is the most popular.
PubSubJS: "a topic-based publish/subscribe library written in JavaScript."
EventEmitter: "Evented JavaScript for the browser." It's actually an implementation of a library that already exists as part of nodejs core, but for the browser.
MicroEvent.js: "event emitter microlibrary - 20lines - for node and browser"
mobx: "Simple, scalable state management."
Taken from: 8 no-Flux strategies for React component communication which also is a great read in general.
The following code helps me to setup communication between two siblings. The setup is done in their parent during render() and componentDidMount() calls.
class App extends React.Component<IAppProps, IAppState> {
private _navigationPanel: NavigationPanel;
private _mapPanel: MapPanel;
constructor() {
super();
this.state = {};
}
// `componentDidMount()` is called by ReactJS after `render()`
componentDidMount() {
// Pass _mapPanel to _navigationPanel
// It will allow _navigationPanel to call _mapPanel directly
this._navigationPanel.setMapPanel(this._mapPanel);
}
render() {
return (
<div id="appDiv" style={divStyle}>
// `ref=` helps to get reference to a child during rendering
<NavigationPanel ref={(child) => { this._navigationPanel = child; }} />
<MapPanel ref={(child) => { this._mapPanel = child; }} />
</div>
);
}
}

ReactJS and Reflux listening to a store before component mounts

I have this doubt that I haven't been able to google out yet but I have this react component that I want to update it's state using a reflux store using componentWillMount() method.
I am able to update the state in the store but using this.trigger to update it's state from the store didn't give me the updated state of the data which got me confused. How can I get the updated state of the data.
Here is what my component is like at the moment
var Challenges = React.createClass({
contextTypes: {
router: React.PropTypes.func
},
mixins: [Reflux.connect(ChallengeStore,'challenges')],
getInitialState: function() {
return {
challenges: []
}
}
componentDidMount: function() {
var trackId = this.props.params.trackId; // the url
ChallengeActions.GetChallenges(trackId);
console.log(this.state);
},
render: function () {
return(
<div>
<h1>{ this.state.challenges.title }</h1> <List challenges={ this.state.challenges } />
</div>
);
}
});
And my store here
var ChallengeStore = Reflux.createStore({
listenables: ChallengeActions,
onGetChallenges: function(url) {
var items = ChallengeService.getChallenges(url);
this.trigger({
challenges: items
});
}
});
Ran into this while figuring out Reflux this week.
The issue is Reflux.connect only connects a getInitialState() in the store which your store seems is missing.
As per the docs:
The Reflux.connect() mixin will check the store for a getInitialState
method. If found it will set the components getInitialState
Unless your store's initial state is consistent across all it's listeners, I find it's better to just use Reflux.listenTo():
var Status = React.createClass({
mixins: [Reflux.listenTo(statusStore,"onStatusChange")],
onStatusChange: function(status) {
this.setState({
currentStatus: status
});
},
render: function() {
// render using `this.state.currentStatus`
}
});

Sending events between components

Consider the ToDo App example on the React homepage. For posterity, here's a fiddle and the code is at the end of this post.
Now say we decide to upgrade this app with w simple features:
Each todo item will have not only text, but also a "done" attribute. You can click on an item and it will toggle the "done" state, and perhaps add strikethrough styling when it is done.
At the very bottom, there will be text indicating the number of "Done", eg, "2 items done, 3 left to do"
The problem is that the state of the items is maintained in TodoApp, not in TodoList. So we'd like to add an onClick={something} to the <li> element in TodoList's render method. But we want that click event to be handled by TodoApp, which would then change the state of the item, and cause everything to re-render. If we wanted to approach it like this, how could we do it?
We could also create a TodoItem component to be called by TodoList, and push the statefulness down into that. This would allow the click to be handled by the TodoItem, but now we would need a way to share the TodoItems' states with the component indicating the number of items done and still todo.
In a nutshell, I'd like to know how components can send events to each other, because I think just knowing that would allow solutions to both problems.
React ToDo App
/** #jsx React.DOM */
var TodoList = React.createClass({
render: function() {
var createItem = function(itemText) {
return <li>{itemText}</li>;
};
return <ul>{this.props.items.map(createItem)}</ul>;
}
});
var TodoApp = React.createClass({
getInitialState: function() {
return {items: [], text: ''};
},
onChange: function(e) {
this.setState({text: e.target.value});
},
handleSubmit: function(e) {
e.preventDefault();
var nextItems = this.state.items.concat([this.state.text]);
var nextText = '';
this.setState({items: nextItems, text: nextText});
},
render: function() {
return (
<div>
<h3>TODO</h3>
<TodoList items={this.state.items} />
<form onSubmit={this.handleSubmit}>
<input onChange={this.onChange} value={this.state.text} />
<button>{'Add #' + (this.state.items.length + 1)}</button>
</form>
</div>
);
}
});
React.renderComponent(<TodoApp />, document.body);
The idiomatic way to do this is to pass a callback down to TodoList:
Live demo: http://jsbin.com/zeqizene/1/edit
I've changed TodoList to look like this:
var TodoList = React.createClass({
handleDoneToggle: function(i) {
this.props.onDoneToggle(i);
},
render: function() {
var createItem = function(item, i) {
return <li onClick={this.handleDoneToggle.bind(null, i)}>
{item.text}
{item.done && " (done)"}
</li>;
};
return <ul>{this.props.items.map(createItem, this)}</ul>;
}
});
When an item is clicked, TodoList will call its own onDoneToggle function, so TodoApp can modify the state appropriately.
See also Editing a rich data structure in React.js.

Resources