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.
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 use react/redux with rails project. So i want my Listing Component to be pretender(server sider render) and Other component just show the detail when mouse over on listing item.
Mouse Hover event
My Question is How can i get listing data on Detail Component when mouse over on each listing item
Simple Example
My Code on rails view
= react_component('Listing', { data: #listings }, prerender: true )
= react_component('Detail', { }, prerender: false )
My Code on JS
export default class Listings extends Component {
render() {
return (
<Provider store={store}>
<ListingsWidget />
</Provider>
);
}
}
My Code for Detail
export default class ListingDetail extends Component {
render() {
return (
<Provider store={store}>
< ListingDetail Widget />
</Provider>
);
}
}
You have some pseudo code there, but you'll have 3 components: Listings, ListingsItem, and ListingsItemDetail. You'll have a React onMouseOver attribute on an element in your ListingsItem that will call your event handler to set state. Assuming your ListingsItemDetail component is within ListingsItem, you'll check state to see if you should show ListingsItemDetail. If ListingsItemDetail is somewhere else, then you'll either call an event handler passed in as a prop or use Redux or something to set the id for the ListingsItemDetail that should be displayed.
Edit - added a partial example:
const ListItem = React.createClass({
getInitialState() {
return {showDescription: false}
},
handleMouseOver() {
this.setState({showDescription: true})
},
handleMouseOut() {
this.setState({showDescription: false})
},
renderDescription() {
if (this.state.showDescription) {
return (
<ListItemDescription description={this.props.item.description} />
)
}
},
render() {
return (
<div onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
List item title: {this.props.item.title}
{this.renderDescription}
</div>
)
}
})
I'm trying to test a connected react component that needs a props.params.id to call action creators. When I try to test that the component is connected to the store I get "Uncaught TypeError: Cannot read property 'id' of undefined"
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<Route path="/" component={App}>
<IndexRoute component={PostsIndex}/>
<Route path="posts/:id" component={ShowPost}/>
</Route>
</Router>
</Provider>
, document.querySelector('.container'));
describe('ConnectedShowPost', ()=> {
let initialState = {
posts: { postsList: postsListData, post: postData, error: '' },
comments: {showComments: false},
auth: {authenticated: true}
};
store = mockStore(initialState);
connectedShowPost = TestUtils.renderIntoDocument(<Provider store={store}><ConnectedShowPost/></Provider>);
showPost = TestUtils.scryRenderedComponentsWithType(connectedShowPost, ShowPost)[0];
expect(showPost.props.posts.post).toEqual(postData);
})
I've tried including params in the store but that doesn't work since params is not hooked up to the store when used inside the component.
I've also tried passing it in as an ownProps argument and that didn't work either.
Passing it in to the ConnectedShowPost component as a props causes all other state items in the store to be undefined..
Also tried to directly set showPost.props.params = {id: '123} which didnt work either..
Any ideas on how to get this test to work?
If your connected component receives a param like this:
function mapStateToProps(state, ownProps)
{
const { match: { params } } = ownProps;
console.log(params.id);
return {
......
};
}
Then, In your test it's possible to inject that param as traditional props. Check out the next example:
wrapper = mount(
<MemoryRouter>
<ConnectedContainer
store={store}
match={
{
params: {
id: 1
}
}
}
/>
</MemoryRouter>
);
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'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;