I'm attempting to set some global variables on my app which I want to be available to all components. Lets say for example that I want a 'language' and a 'status' property to be passed to each component. This property won't be rendered to the page, instead it will be added to the props for each component, this will be so I can check for that variable when each component loads and output the appropriate styles and languages.
I was hoping it would be something simple like adding props to the router, however no matter what I try, the props come back as 'undefined' on my child components (only the main layoutWrapper component gets the props). Here is how it looks so far:
//app.js
var LayoutWrapper = React.createClass({
render: function () {
return (
<Layout status="available" />
);
}
});
ReactDOM.render(
<Router history={hashHistory}>
<Route path="/" language="en-gb" component={LayoutWrapper}>
<IndexRoute component={Index}></IndexRoute>
</Route>
</Router>,
app);
When handling global level state data, it's recommended you use some kind of state framework like Flux. I'd recommend Redux as it does a great job of reducing boilerplate code to easily pass down app state to any connected component (and subsequently subscribe these components to any changes in the state).
What you are doing fails because there's no consistent way of creating "global" props; you could use the context variable but this is an unstable feature that is not recommended for production use. Otherwise, you have to manually pass down your props from parent to child explicitly.
Related
So I've got a component that is dependent on the context of BrowserRouter to hook into
<BrowserRouter>
<...>
<.../>
<.../>
<MyRedirectComponent/>
<.../>
</...>
</BrowserRouter>
I would love to simply include BrowserRouter inside my MyRedirectComponent that way I wouldn't need to wrap it all the time.
const MyRedirectComponent = () => {
const browserRouterParentExists = // I dunno
return browserRouterParentExists ? (
<NormalStuff/>
) : (
<BrowserRouter>
<NormalStuff/>
</BrowserRouter>
)
}
Is correctly populating browserRouterParentExists possible?
Writing conditional logic that tries to access parent component is an anti-pattern in React IMO and I don't think there is a public API you can use for that purpose.
I didn't get what you are trying to achieve but BrowserRouter is defined as
A <Router> that uses the HTML5 history API (pushState, replaceState
and the popstate event) to keep your UI in sync with the URL.
and most of the time you only need one BrowserRouter in your app in a top level component like App considering its usage. So wrapping a component with it and using that component throughout your app is not quite reasonable. If you are trying to redirect user to another route based on some condition, you can use any data coming from props, state, Context API or a state management lib. like Redux etc. to implement your logic and render Redirect component together with that conditional logic.
<Provider store={this.props.store}>
<ConnectedRouter history={this.props.history}>
<AppWrap>
<Header />
<Main>
<Switch>
<Route exact path="/" component={Component1}/>
<Route path="/component2" component={Component2}/>
</Switch>
</Main>
</AppWrap>
</ConnectedRouter>
</Provider>
Inside App.componentWillMount, the Redux action this.props.requestApplesApiData is triggered. It is handled by a saga, which fetches some data from an api and returns with another action receiveApplesApiData with a payload, which is added to the redux state by the reducer.
componentWillMount(){
// triggers an action
// which is picked up by a saga,
// which is handled by the reducer
// which updates the redux state
this.props.requestApplesApiData();
}
Redux state is updated fine, when I inspect it in the chrome dev tools (w. redux extension), but I noticed that the redux state (with the recently fetched "apples") is not updated in time in Component2, while the race condition was won in Component 1. This means, that Component2 can't display the "apples", because they weren't there at the time of its render function.
A re-render would usually happen automatically, if it was in a nested tree like structure, however "apples" are located in their own root object in the redux store.
How can I have Component1 and Component2 wait for App to fetch the necessary data. Or put in other words, how can I delay/defer the render function in App, until the data is in redux?
Two options:
Conditionally render the component based on whether a certain value is present in the store
Display something intermediary before the value is updated in the Redux store
But it sounds like the main issue here though is getting the component to update based of a change in the store.
The general path is that components will update when any of their props are updated. Once the saga completes, it will update some value in the redux store. Components should have something that watches the store and updates their props from the store. This is what will tell React to re-render these components. General convention is to store all of these updates in a mapStateToProps function.
Your example is fairly stripped down, so I'm not sure how much of the built in redux functionality you're using, but here are the docs that describe how to pass updated props to your component: https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options
Summary from the docs: connect the component to the store with connect() and the first param needs to be a function specifying how the component will update its props from the store.
Here is the situation:
I am getting an unknown amount of data from a service and putting it in my Redux store
Each data-point will have its own route /:rid
The route is setup like this <Route path=':rid' component={Restaurant}/> using react-router (v3) -- see full render method below
I'd like to be able to pass the correspond data about the specific restaurant, the one whose route was navigated to, to the Restaurant component as props so it can render the component with the correct information for that restaurant
Right now my solution is to pass all the restaurants from the store into the Restaurant component as props this.props.restaurants. Then in componentWillReceiveProps I loop over all the restaurants in this.props.restaurants and check to see if the rid of each restaurant is equal to the this.props.routeParams ie :rid. If it is I then set that state to contain the data I want to show and reference this.state.name as opposed to the data being on `this.props.name'
Is there another way to do this? Right now it's not a performance issue but I can imagine looping over and arbitrarily large data set could lead to so serious load times. Also, it just seems like there should be a way for react-router to pass in this data as props so I can keep this component stateless.
Ideally, something like this would happen:
a request is made to /1234
react-router in my index.js consults/queries the redux store and finds the data for the restaurant with rid 1234 and passes it as props to the component it renders
I imagine it looking something like this <Route path=':rid' component={<Restaurant {...matchedRestaurant} />}/>
Perhaps this questioning can be asked in short like, how do I make a unknown number of routes such that when one is navigated to it is rendered with the data for that corresponding restaurant as props?
Restaurant.js:
componentWillReceiveProps(nextProps) {
this.props.restaurants.forEach((restaurant) => {
if(restaurant.rid == nextProps.routeParams.rid) this.setState({name: restaurant.name})
})
}
index.js:
render(
(
<Provider store={store}>
<Router history={hashHistory}>
<Route path='/' component={App}>
<IndexRoute component={RestaurantList} />
<Route path=':rid' component={Restaurant}/>
</Route>
</Router>
</Provider>
),
document.getElementById('root')
)
https://github.com/caseysiebel/corner-team/blob/master/src/index.js
Instead of having react-router figure this out for you, you should be using selectors (and potentially a package like reselect). Reselect even has a section on how to base your selector on props (in this case like the routerParams.rid): https://github.com/reactjs/reselect#accessing-react-props-in-selectors
For the non-Reselect solution, you could simply change the connect in your Restaurant component like so:
#connect((state, props) => {
return {
restaurants: state.restaurant.restaurants.find((restaurant) => {
return restaurant.rid == props.routeParams.rid
}),
}
})
As #Sean Kwon commented, you should also normalize your data which would make this selector trivial:
#connect((state, props) => {
return {
restaurants: state.restaurant.restaurants[props.routeParams.rid],
}
})
Assuming you have connected your action via mapDispatchToProps, you organize your store and async actions so that this can be possible.
componentDidMount() {
this.props.fetchRestaurant(this.props.params.rid)
}
The store will then update your component with the corresponding restaurant data. This way, you're calling some kind of action to get the corresponding data whilst reducing the need to use the component state, which you should try to avoid in order to keep your state centralized.
Otherwise, for a quick and dirty solution, you can just do this really quickly.
componentWillReceiveProps(nextProps) {
var name = this.props.restaurant.find(res => res.rid === nextProps.routeParams.rid)
this.setState({name: name})
}
Upgrading my application to React Router v4 I'm struggling to find a clean way to implement the old onUpdate logic which I previously used the hide a popup menu on navigation.
The only way I can see in the documentation is to take advantage of the route render method but it seems much more complicated than before - any easier solutions?
const HidePopupThenRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={matchProps => {
hidePopup();
return <Component {...matchProps}/>
}}/>
)
<HidePopupThenRoute path="/" component={MyComponent}/>
I wouldn't consider that too complicated, but I can understand it not looking right to make imperative calls inside of a render function.
One alternative would be to create an OnUpdate component that listens for location changes and calls some function when the happen.
You can see the source code for an implementation of this that I wrote. You can either use that or replicate your own component with similar functionality. Basically, all that it does is subscribe to the history object and calls whatever function you pass to it when the location changes.
const MyComponent = () => (
<div>
<OnUpdate call={hideProps} />
<h1>My Component</h1>
<p>...</p>
</div>
)
A relatively clean solution was suggested on GitHub. What it comes down to is that you read the Router's history from React's Context API, where it gets populated by React Router. You can do so by defining the contextTypes property in your component:
static contextTypes = {
router: PropTypes.object
};
That will make sure that the Router is attached to that component. You can then use that the access the router and its history from the context, and add a callback that gets executed on history changes:
this.context.router.history.listen(hidePopup)
You'll probably want to do that in componentDidMount.
After reading many questions regarding this topic I am still unsure as to which is the best way to asynchronously fetch data which later will be passed down as props to the child routes with React Router v1.0.0 and up.
My route config looks something like this:
import { render } from 'react-dom';
// more imports ...
...
render(
<Router>
<Route path="/" component={App} />
<IndexRoute component={Dashboard}/>
<Route path="userpanel" component={UserPanel}/>
</Router>,
document.getElementById('container')
)
In my App component I have code which asynchronously fetches data from the backend and will incorporate it into its state, if fetching was successful. I use componentDidMount for this within App.
The state of App will look like this contrived example:
{
user: {
name: 'Mike Smith',
email: 'mike#smith.com'
}
}
I would want to pass the user part of state as props to my IndexRoute and the userpanel route. However I am not sure how I should do this.
A few questions come to mind:
Should I place the async data request somewhere else within my code?
Should I use the React Router api (like onEnter) instead of React lifecycle methods for the data fetching?
How can I pass the state (user) of App to the Dashboard and UserPanel components as props?
I am unsure how to do this with React.cloneElement as seen in other answers.
Thanks for the help in advance.
What you are asking for is persistent data between routes and that's not the job of the router.
You should create a store (in flux terms), or a model/collection (in MVC terms) - the usual approach with react is something flux-like. I recommend redux.
In the redux docs it has an example of fetching a reddit user:
componentDidMount() {
const { dispatch, selectedReddit } = this.props
dispatch(fetchPostsIfNeeded(selectedReddit))
}
Personally I don't think flux/redux is the easiest approach to implement, but it scales well. The essential concept is even if you decide to use something else:
You are correct, as Facebook suggests, async fetching goes best in componentDidMount.
If you want to integrate with other JavaScript frameworks, set timers using setTimeout or setInterval, or send AJAX requests, perform those operations in this method.
Next you need to set this data in a store/model which can be accessed from other components.
The nice thing about redux (with react-redux) is that for each component you can say "Here are the actions this component is interested in" and then that component can simply call the action like UserActions.fetchUserIfNeeded() and the action will figure out whether it already has the user or if it should be fetched, and afterwards it will re-render and the prop will be available.
Answer to Q4: What are you trying to clone and why? If it's a child see this answer.
You can do one thing when your application start at that time you will call the API and fetch the data and register your Route like
my index.js is entry file then
here I have used React-Router 0.13.3 you can change the syntax as per new Router
fetchData(config.url+'/Tasks.json?TenantId='+config.TenantId).then(function(items)
{
var TaskData=JSON.parse(JSON.stringify(items.json.Tasks));
var Data=[];
Object.keys(TaskData).map(function(task){
if(TaskData[task].PageName !=='' && TaskData[task].PageUrl !=='')
{
Data.push({PageName:TaskData[task].PageName,path:TaskData[task].PageName+'/?:RelationId',PageUrl:TaskData[task].PageUrl});
}
});
Data.push({PageName:'ContainerPage',path:'/ContainerPage/?:RelationId',PageUrl:'./pages/ContainerPage'});
var routes=require('./routes')(Data);
$("#root").empty();
Router.run(routes,function(Handler){
React.render(<Handler />,document.getElementById('root'));
});
React.render(<UserRoles />, document.getElementById("userrole"));
}).catch(function(response)
{
showError(response);
});
I have pass the data to routes.js file like var routes=require('./routes')(Data); and my routes.js file look like
export default (data =>
<Route name="App" path="/" handler={App}>
<NotFoundRoute handler={require('./pages/PageNotFound')} />
<Route handler={TaskList} data={data} >
</Route>
{ data.map(task =>
<Route name={task.PageName} path={task.path} handler={require(task.PageUrl)}>
</Route>
) }
</Route>
);
I am not entirely sure I understand the question, but I just recently passed properties to the children of my routes as well. Pardon me if this is not the best way of doing it, but you'll have to clone your children and edit them and then pass down the copies not the children. I'm not sure why react and the react router make you do this, but try this:
let children (or whatever you want to name it) = React.Children.map(this.props.children, (child) => {
return React.cloneElement(child, {name of property: property value});
});
Afterwards, you should be able to access those properties in this.props in the sub routes. Please ask if you have any questions because this is pretty confusing.