I've followed a couple of examples in an attempt to get access to a parameter from a Route in the React component that handles it. However the result of console.log on this.props from inside the render or componentDidMount is always {} when I'd expect it to contain the gameId from the gamesView route.
client.js which starts the Router:
// HTML5 History API fix for local
if (config.environment === 'dev') {
var router = Router.create({ routes: routes });
} else {
var router = Router.create({ routes: routes, location: Router.HistoryLocation });
}
router.run(function(Handler) {
React.render(
<Handler />,
document.getElementById('app')
);
});
routes.js with some routes removed for simplicity:
var routes = (
<Route name='home' path='/' handler={app}>
<DefaultRoute handler={home} location="/" />
<Route name='gamesView' path='/games/:gameId' handler={gamesView} />
</Route>
);
module.exports = routes;
...and app.js which wraps the other routes, I've tried it both with and without {...this.props} in the RouteHandler. If I console.log(this.props) from inside the render function here is also returns {}:
var App = React.createClass({
render: function() {
return (
<div className='container'>
<div className='row'>
<RouteHandler {...this.props} />
</div>
</div>
);
}
});
module.exports = App;
Finally the gamesView React component that I expect to see the props object. Here this.props is also {} and the following results in the error TypeError: $__0 is undefined var $__0= this.props.params,gameId=$__0.gameId;:
var GamesView = React.createClass({
render: function() {
var { gameId } = this.props.params;
return (
<div>
<h1>Game Name</h1>
<p>{gameId}</p>
</div>
);
}
});
module.exports = GamesView;
Anybody have any ideas?
You won't see those params until you are at the component defined in your router. App won't know anything about them. If you put the console.log(this.props.params) in your gamesView component, however, you should see them.
After discussing on the React Router (RR) Github it turned out this was due to me using an older version of RR (v0.11.6).
Looking at the example in the docs for that release it showed that I needed to use a Router.State mixin and then get the expected param via var gameId = this.getParams().gameId;.
Without upgrading RR the working version of my original example for GamesView becomes:
var React = require('react');
var Router = require('react-router');
var { Route, RouteHandler, Link } = Router;
var GamesView = React.createClass({
mixins: [ Router.State ],
render: function() {
var gameId = this.getParams().gameId;
return (
<div>
<h1>Game Name</h1>
<p>{gameId}</p>
</div>
);
}
});
module.exports = GamesView;
Related
I have a simple setup to test the use of the useContext hook, when you want to change the context value in child components.
A simple Context is defined in its own file like such:
import React from 'react'
const DataContext = React.createContext({})
export const DataProvider = DataContext.Provider
export default DataContext
Then I wrap my router in a provider in a component that exposes its state to use as a reference for the ContextProvider, as such:
import { DataProvider } from './dataContext.js'
export default function App(props) {
const [data, setData] = useState("Hello!")
const value = { data, setData }
const hist = createBrowserHistory();
return (
<DataProvider value={value}>
<Router history={hist}>
<Switch>
<Route path="/admin" component={Admin} />
<Redirect from="/" to="/admin/services" />
</Switch>
</Router>
</DataProvider>
)
}
Finally I have two Views that I am able to navigate between initially, one of them showcasing the context value, as well as containing a button to change it:
export default function EndpointView(props) {
const { data, setData } = useContext(DataContext)
return (
<div>
<h1>{data}!</h1>
<Button onClick={() => setData(Math.random())}>Update context state</Button>
</div>
)
}
The functionality seems to work, as the showcases text is updated.
The problem is, when I have clicked the button, I can no longer navigate in my navbar, even though the url is changing. Any ideas as to why?
This is showcased in this picture, where the url is corresponding to the top-most item in the side bar, even though we are stuck in the "endpoint view"-component.
Edit:
So the routing works by including a switch in the Admin layout:
const switchRoutes = (
<Switch>
{routes.map((prop, key) => {
if (prop.layout === "/admin") {
return (
<Route
path={prop.layout + prop.path}
component={prop.component}
key={key}
/>
);
}
return null;
})}
<Redirect from="/admin" to="/admin/services" />
</Switch>
);
Where the routes (which we .map) are fetched from another file that looks like this:
const dashboardRoutes = [
{
path: "/services",
name: "Services view",
icon: AccountBalance,
component: ServicesView,
layout: "/admin"
},
{
path: "/endpoint",
name: "Endpoint view",
icon: FlashOn,
component: EndpointView,
layout: "/admin"
}
];
export default dashboardRoutes;
I was able to solve this issue.
I suspect the problem was that updating the state reloaded the root router component which caused some issues.
Instead I moved the DataProvider tag one step down the tree, to wrap the switch in the Admin component.
I have two problems. I've read some SO posts and browsed through various docs, but I run into different explanations.
This is what I've got:
var Router = window.ReactRouter.Router;
var useRouterHistory = window.ReactRouter.useRouterHistory;
var Route = window.ReactRouter.Route;
var Link = window.ReactRouter.Link;
const createHistory = window.History.createHistory;
const appHistory = useRouterHistory(createHistory)({ queryKey: false });
var MyTable = React.createClass({
getInitialState: function() {
...
history.push({
pathname: window.location.href.split('?')[0],
search: querystring,
state: { 'data': initial_data }
});
...
},
...
});
ReactDOM.render((
<Router history={appHistory}>
<Route path={window.page_url} component={MyTable}>
</Route>
</Router>
), document.getElementById('my-table-wrapper'));
Problem number 1 is: history.push is not a function.
Problem number 2 is: if I use, for example, window.history.pushState, I do get nice URLs that change on browser's back and forward buttons and I saw that I can perform actions onpopstate. What is the equivalent of that for the history that I'm trying to use above?
Thanks.
I am having a hard time figuring out how to mount components inside a nested components with react router v1.0. I have an App component that loads a Layout component. The Layout component then loads two components, Menu and Content. I want to load different components inside the Content component based on the route.
Below is my sample code.
var App = React.createClass({
render : function(){
return <div><Layout/></div>
}
});
var Layout = React.createClass({
render : function(){
return(
<div>
<Menu/>
<Content/>
</div>
)
}
});
var Content = React.createClass({
render : function(){
return <div>This is where i want to mount my components</div>
}
});
var List = React.createClass({
render : function(){
return <div>some list goes here</div>
}
});
var Graph = React.createClass({
render : function(){
return <div>some graph goes here</div>
}
});
<Router>
<Route path='/' component={App}>
<Route path='/list' component={List}/>
<Route path='/graph' component={Graph}/>
</Route>
</Router>
Any help/pointers will be highly appreciated.
Thanks
It's all the same as basic React components. When you nest them, they're available on this.props.children. So you would end up with something like this:
var App = React.createClass({
render : function(){
return <div><Layout>{this.props.children}</Layout></div>
}
});
var Layout = React.createClass({
render : function(){
return(
<div>
<Menu/>
<Content>{this.props.children}</Content>
</div>
)
}
});
var Content = React.createClass({
render : function(){
return <div>{this.props.children}</div>
}
});
I'm trying to get the react-router up and running, but somehow React is not defined within react-router and because of that fails with the error
Uncaught TypeError: Cannot read property 'createClass' of undefined
I'm also getting this error later:
Uncaught Error: Load timeout for modules: jsx!testapp_unnormalized2,jsx!testapp
Any idea of what I'm doing wrong here? I simply want to get the router working without concatenating all the files.
This is how the app looks:
index.html
<!-- ... -->
<script data-main="../resources/js/init" src="../resources/bower_components/requirejs/require.js"></script>
</body>
<!-- ... -->
init.js
require.config({
paths: {
react: "/resources/bower_components/react/react-with-addons",
JSXTransformer: "/resources/bower_components/jsx-requirejs-plugin/js/JSXTransformer",
jsx: "/resources/bower_components/jsx-requirejs-plugin/js/jsx",
jquery : "/resources/bower_components/jquery/dist/jquery",
'react-router' : "/resources/bower_components/react-router/dist/react-router",
'react-router-shim': 'react-router-shim'
},
shim : {
'react-router': {
deps: ['react'],
exports: 'Router'
}
},
});
require(['jsx!testapp'], function(App){
var app = new App();
app.init();
});
react-router-shim.js
define(['react'], function(React) {
"use strict";
window.React = React;
});
testapp.js
define(function(require){
var React = require('react');
var Router = require('react-router');
var Route = Router.Route;
var NotFoundRoute = Router.NotFoundRoute;
var DefaultRoute = Router.DefaultRoute;
var Link = Router.Link;
var RouteHandler = Router.RouteHandler;
var routes = (
<Route handler={Home} path="/">
<DefaultRoute handler={Home} />
</Route>
);
var Home = React.createClass({
render: function() {
return(
<p>This is the mathias page</p>
);
}
});
var App;
App.init = function () {
Router.run(routes, Router.HistoryLocation, function (Handler) {
React.render(<Handler/>, document.getElementById('content'));
});
};
return App;
});
react-router doesn't support AMD and thus won't load the React dependency by default. Instead, you should load React first and set it as a global (window.React = React), or use the RequireJS shim configuration to load React as a global.
See react-router#171 for more discussion around react-router and AMD.
I'm trying to pass some props down to my handled component however react-router fails to do so.
var Objects = React.createClass({
getInitialState: function() {
return {
selected: "All"
}
},
select: function(opt) {
this.setState({
selected: opt
});
},
render: function() {
return (
<div>
<LeftNav select={this.select} />
<this.props.activeRouteHandler selected={this.state.selected} />
</div>
);
}
});
var routes = (
<Routes>
<DefaultRoute name="objects" handler={objecctHandler}/>
</Routes>
);
The router loads fine as I can see '#/' in the url now. The Left nav renders fine and updates the state as well. however there is not props.selected in the handeded component namely objectHandler. Am I missing something here? Thanks.
I'm using react-router 0.7.0
Try making the DefaultRoute for 'objectHandler' a child of another route that defines 'Objects' as the handler. Such as:
var routes = (
<Routes>
<Route handler={Objects}>
<DefaultRoute name="objects" handler={objectHandler}/>
</Route>
</Routes>
);
jsfiddle: http://jsfiddle.net/gq1uym5y/1/
What I'm using right now is something like this.
One top level route that just routes to the App component:
React.renderComponent(
<Routes>
<Route handler={App}>
<Route path="/todos" handler={TodoList} />
</Route>
</Routes>
, mountNode);
The App component looks like this. I pass a Container to all subroutes (ie to the TodoList route). This container contains the list of todos (and anything else I need in the app, including methods for adding/persisting new Todos). This helps with keeping state at the top level and decouple subcomponents. Since the App component is used for every route, it never unmounts, so it doesn't loose its state.
var Container = function(app) {
return {
getTodos: function() {
return app.state.todos;
}
};
};
var App = React.createClass({
getInitialState: function() {
return {
todos: ['Buy milk', 'Call Mike']
};
},
componentWillMount: function() {
this.container = Container(this);
},
render: function() {
return <this.props.activeRouteHandler container={this.container} />;
}
});
Here's what the TodoList looks like. It's actually two components: TodoList and TodoListInner. The TodoListInner remains clean and testable. The TodoList itself is not so easily testable, but since it just wraps around the inner component, this shouldn't be much of a problem.
var TodoListInner = React.createClass({
render: function() {
<ul>
{this.props.todos.map(function(todo) {
return <li>{todo}</li>;
})}
</ul>
}
})
var TodoList = React.createClass({
render: function() {
<TodoListInner todos={this.props.container.getTodos()} />
}
});
It's a little more complex than jsmiff's solution but does the job with some added benefits.