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
Related
I'm trying to call a component function on a child component RouteHandler.
var Bar = React.createClass({
baz: function() {
console.log('something');
},
render: function() {
return <div />;
},
});
var Foo = React.createClass({
baz: function() {
this.refs['REF'].baz();
},
render: function() {
return <RouteHandler ref="REF" />;
},
);
Where RouteHandler is a Bar but this.refs['REF'].baz is undefined.
See https://facebook.github.io/react/tips/expose-component-functions.html for more information on component functions.
I don't believe react-router currently supports exposing component functions on RouteHandlers and the current hacky workaround is to do:
this.refs['REF'].refs['__routeHandler__'].baz();
See https://github.com/rackt/react-router/issues/1597
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
I'm trying to test a React component that requires the react-router in separately from app.js.
I have a component that does a redirect using the mixin Router.Navigation like so:
var React = require('react'),
Router = require('react-router');
var Searchbar = React.createClass({
mixins : [Router.Navigation],
searchTerm: function(e) {
if (e.keyCode !== 13) {
return;
}
this.context.router.transitionTo('/someRoute/search?term=' + e.currentTarget.value)
},
render: function() {
return (
<div className="searchbar-container">
<input type="search" placeholder="Search..." onKeyDown={this.searchTerm} />
</div>
)
}
});
module.exports = Searchbar;
I tried to write a test for this but ran into a wall. Apart from the fact that I'm unable to test that transitionTo works as expected, I've also encountered this error message in my Jest tests:
Warning: Failed Context Types: Required context router was not specified in Searchbar.
Does anyone know how I can get rid of the warning and bonus question, how I can test that the transition works as expected?
I've done research into this and this conversation on Github here: https://github.com/rackt/react-router/issues/400 is the closest I've found to the problem. It looks like I need to export the router separately but that seems like a lot of overhead to just run component tests without the warning a la https://github.com/rackt/react-router/blob/master/docs/guides/testing.md
Is that really the way to go?
In version 0.13 of React Router, the mixins Navigation and State were deprecated. Instead, the methods they provide exist on the object this.context.router. The methods are no longer deprecated, but if you're using this.context.router explicitly you don't need the mixin (but you need to declare the contextTypes directly); or, you can use the mixin, but don't need to use this.context.router directly. The mixin methods will access it for you.
In either case, unless you render your component via React Router (via Router#run), the router object is not supplied to the context, and of course you cannot call the transition method. That's what the warning is telling you—your component expects the router to be passed to it, but it can't find it.
To test this in isolation (without creating a router object or running the component through Router#run), you could place a mocked router object on the component's context in the correct place, and test that you call transitionTo on it with the correct value.
Because the router relies heavily on the lesser known context feature of React you need to stub it like described here
var stubRouterContext = (Component, props, stubs) => {
return React.createClass({
childContextTypes: {
makePath: func,
makeHref: func,
transitionTo: func,
replaceWith: func,
goBack: func,
getCurrentPath: func,
getCurrentRoutes: func,
getCurrentPathname: func,
getCurrentParams: func,
getCurrentQuery: func,
isActive: func,
},
getChildContext () {
return Object.assign({
makePath () {},
makeHref () {},
transitionTo () {},
replaceWith () {},
goBack () {},
getCurrentPath () {},
getCurrentRoutes () {},
getCurrentPathname () {},
getCurrentParams () {},
getCurrentQuery () {},
isActive () {},
}, stubs);
},
render () {
return <Component {...props} />
}
});
};
And use like:
var stubRouterContext = require('./stubRouterContext');
var IndividualComponent = require('./IndividualComponent');
var Subject = stubRouterContext(IndividualComponent, {someProp: 'foo'});
React.render(<Subject/>, testElement);
Here is my Jest file for a complete answer to this question. BinaryMuse’s last paragraph got me on the right track but I find code examples always the most helpful, so here it is for future reference.
jest.dontMock('./searchbar');
describe('Searchbar', function() {
var React = require('react/addons'),
Searchbar = require('../../components/header/searchbar'),
TestUtils = React.addons.TestUtils;
describe('render', function() {
var searchbar;
beforeEach(function() {
Searchbar.contextTypes = {
router: function() {
return {
transitionTo: jest.genMockFunction()
};
}
};
searchbar = TestUtils.renderIntoDocument(
<Searchbar />
);
});
it('should render the searchbar input', function() {
var searchbarContainer = TestUtils.findRenderedDOMComponentWithClass(searchbar, 'searchbar-container');
expect(searchbarContainer).toBeDefined();
expect(searchbarContainer.props.children.type).toEqual('input');
});
});
});
Hope this helps someone else in the future.
My answer is not Jest-specific but it might help people coming across the same problem.
I created a class to wrap router context.
Then in your test just add
<ContextWrapper><YourComponent/></ContextWrapper>
It can be useful to wrap other things like ReactIntl.
Note that you will lose the possibility to use shallow rendering but that's already the case with ReactIntl.
Hope that helps someone.
ContextWrapper.js
import React from 'react';
export default React.createClass({
childContextTypes: {
router: React.PropTypes.object
},
getChildContext () {
return {
router: {}
};
},
render () {
return this.props.children;
}
});
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`
}
});
How can I update a ReactJS component based on URL / path when using React-Router?
The code below works, but is this the correct way to do this? Seems like a lot of code to make a simple update. I was hoping there would be a stateful API call in the router to automatically take care of this scenario.
var MyHomeView = React.createClass({
componentDidMount: function() {
this.props.updateHeader();
},
render: function() {
return (
<div>
<h2>Home</h2>
</div>
);
}
});
var MyAboutView = React.createClass({
componentDidMount: function() {
this.props.updateHeader();
},
render: function() {
return (
<div className="my-page-text">
<h2>About</h2>
</div>
);
}
});
var MyHeader = React.createClass({
mixins: [ CurrentPath ],
getInitialState: function() {
return {
myPath: "about",
classes: "ion-ios7-information"
};
},
updateHeader: function() {
// Classnames refer to www.ionicons.com
if (this.getCurrentPath() === "/") {
this.setState( {myPath: "about" } );
this.setState( {classes: "ion-ios7-information" } );
} else {
this.setState( {myPath: "/" } );
this.setState( {classes: "ion-ios7-rewind" } );
}
},
render: function() {
return (
<Link to={this.state.myPath}>
<i className={this.state.classes} />
</Link>
);
}
});
var App = React.createClass({
updateHeader: function() {
this.refs.header.updateHeader();
},
render: function() {
return (
<div>
<MyHeader ref="header" />
<this.props.activeRouteHandler updateHeader={this.updateHeader} />
</div>
);
}
});
React.renderComponent((
<Routes>
<Route path="/" handler={App}>
<DefaultRoute handler={MyHomeView} />
<Route name="about" handler={MyAboutView} />
</Route>
</Routes>
), document.body);
In react-router 2.0.0 you can use the hashHistory or browserHistory:
browserHistory.listen(function(ev) {
console.log('listen', ev.pathname);
});
<Router history={browserHistory}>{routes}</Router>
This has been updated if you are working with the react-router > v11.0.
You can read the details here
TLDR:
// v0.11.x
var Something = React.createClass({
mixins: [ Router.State ],
render: function () {
var path = this.getPath();
}
});
For the full State API: https://github.com/rackt/react-router/blob/master/doc/04%20Mixins/State.md
This question has been open awhile, and it seems like there should be a more straightforward solution, but I'll take a stab and share what we do in our application.
We are using the Flux architecture, which has the notion of Stores that inform components when their state is updated. In react-router, they have a PathStore that fits into this model nicely, since when the URL changes it can then notify components that care and need to be updated.
The StoreListener we use in our application is publicly available here: https://github.com/odysseyscience/react-flux-suppprt. I should've published to NPM, but haven't yet. I'll do that soon if you think it would be helpful.
Here is how we use our StoreListener to listen for changes on the PathStore from react-router, and how you would use it in your MyHeader:
var StoreListener = require('path/to/StoreListener');
var PathStore = require ('react-router/modules/stores/PathStore');
var MyHeader = React.createClass({
mixins: [
StoreListener(PathStore)
],
getStateFromStores: function() {
return {
path: PathStore.getCurrentPath()
};
},
render: function() {
var path = this.state.path;
var myPath;
var classes;
if (this.state.path === "/") {
myPath = 'about';
classes = 'ion-ios7-information';
} else {
myPath = '/';
classes = 'ion-ios7-rewind';
}
return (
<Link to={myPath}>
<i className={classes} />
</Link>
);
}
});
This starts with a mixin that says "I care about changes to the PathStore". Whenever this store (or any store being listened to) changes, getStateFromStores() is called on your component in order to retrieve the data out of the store that you want available on this.state. If/When that data changes, render() is called again, and you re-read this.state, and apply your changes.
This is exactly our pattern for applying certain "active" CSS classes on header tabs, or anywhere else in the application that depends on the current URL.
Note that we use webpack to bundle our CommonJS modules, so there may be some assumptions about the environment that may/may not work for you.
I didn't answer this question a month ago when I first saw it because I assumed there was a better way, but perhaps our solution can help others with the same issue.