I have a component, which receives a certain prop from a route - let's call it day. Then, there is a part of redux state - calendar, which I get using connect() and mapStateToProps. Now, when a date is outside of the calendar, in componentWillReceiveProps I dispatch a redux action, to recalculate calendar again to include a date in it. The action is fully synchronous, no requests are being sent. In that case, I get the invariant violation from react:
findComponentRoot(..., .0.1.0.$20160122): Unable to find element
What in what I'm doing is causing the problem? I'm not doing any tables or nested paragraphs/forms. When I dispatch action with setTimeout(..), everything works as expected, so I guess it's some concurrency issue.
This is how my component code looks like:
componentWillReceiveProps(props) {
if (!dateInArray(props.day, props.calendar)) {
props.actions.calculateCalendar(props.day);
// This works:
// setTimeout(() => props.actions.calculateCalendar(props.day), 0);
}
}
#autobind
dateToDiv(date) {
const date8 = toDate8(date);
return <Link className="daySelect" activeClassName="active" key={date8} to={`/date/${date8}`}>{toNiceDate(date)}</Link>
}
render () {
return <div>{this.props.calendar.map(this.dateToDiv)}</div>
}
And this is more or less how my routes look like
<Route path='/' component={App}>
<IndexRedirect to={`date/${today8()}`}/>
<Route path='date'>
<IndexRedirect to={`${today8()}`}/>
<Route path=":day" component={DayView}/>
</Route>
</Route>
Related
I have maintained state by using useState hook in react. I want to clean value that is getting maintain in state when route get changes.
For example - I have 4 routes declared in react based project. which are as below
<Router>
<Layout>
<Route exact path="/" component={Home}></Route>
<Route exact path="/defineFacilities" component={DefineFacilities}></Route>
**<Route exact path="/createNewModel/:id" component={ModelFormsContainer}></Route>**
<Route exact path="/viewExistingModels" component={ViewExistingModels}></Route>
<Route exact path="/importNewModel" component={ImportNewModel}></Route>
</Layout>
I have maintained state in ModelFormsContainer component. I want to clean state values when user move to other routes. Currently when I move to other route and back ModelFormsContainer component then I noticed that my state are still available.
I wasn't able to reproduce the issue you describe, but if you need to do something when the route changes you can listen for changes to the route/location via the history object.
history.listen
Starts listening for location changes and calls the given callback
with an Update when it does.
// To start listening for location changes...
let unlisten = history.listen(({ action, location }) => {
// The current location changed.
});
// Later, when you are done listening for changes...
unlisten();
In the ModelFormsContainer access the passed history prop instantiate a listener when the component mounts, pass it a callback that updates the state.
Example:
useEffect(() => {
const unlisten = history.listen(() => {
console.log("route changed!!");
// apply business logic to set any component state
});
console.log("ModelFormsContainer mounted");
return unlisten;
}, []);
If ModelFormsContainer is a class component then obviously use componentDidMount and componentWillUnmount and save unlisten as a class instance variable, i.e. this.unlisten.
I am using ReactRouter to route the application (BrowserRouter at the top level) with a Switch that includes all the routes.
In my use-case I want to be able to handle paths that include the path-parameters (bedId) and navigate between different sub-paths (i.e. /beds/:bedId/, /beds/:bedId/info/) as well asa case where the path is (/beds/-).
I also want to be able to direct user to a different "bed" while they are already on some bed, so /beds/bed1/info -> /beds/bed2, and so on...
My BedView component is responsible for routing within that /beds/:bedId path like so:
// App.jsx (fragment)
<Switch>
<Route
path="/"
exact
render={() => (<Redirect to="/beds/-"/>)}
/>
<Route
path="/beds/-"
exact
component={SomeOtherComponent}
/>
<Route
path="/beds/:bedId"
component={BedView}
/>
</Switch>
The problem occurs when I try to use a hook that relies on the current path-parameter to fetch the latest data (i.e. call to /beds/bed1 will result in a call to http://myapi.com/beds/bed1/timeseries). The useLatestData hook is called from the BedView component, which look like so:
// BedView.jsx (fragment)
export default function BedView(props) {
const {bedId} = props.match.params;
let {path, url} = useRouteMatch('/beds/:bedId');
const bedData = useLatestData({
path: `/beds/${bedId}/timeseries`,
checksumPath: `/checksum/timeseries`,
refresh: false
});```
if(!bedData){
return <Loading/>;
}
return (
<Switch>
<Route exact path={path}>
<Redirect to={`${url}/info`}/>
</Route>
<Route exact path={`${path}/info`} >
<SomeComponent info={bedData.info} />
</Route>
</Switch>
}
...and the useLatestData hook is available here.
The problem is the fact that upon redirecting from /beds/bed1/info to /beds/bed2/info, the hook does not update its props, even though the BedView component seems to be re-rendering. I have created a version of the hook that 'patches' the problem by adding an useEffect hook in my custom hook, to detect the change in path (as supplied in the function arguments) and set data to null, but then the behaviour changes on the BedView.jsx's end - making the following check fail:
if(!bedData){
return <Loading/>;
}
I'm not entirely sure which part is the culprit here and any guidance would be much appreciated! As far as I'm aware, the hook is no re-initialised because the path change still results in the same component. There is also one caveat, once I change the BrowserRouter to include the forceRefresh flag, everything works fine. Naturally, I don't want my page to refresh with every redirect though...
try this:
const {bedId} = props.match.params;
let {path, url} = props.match;
This seems so simple, but I am new to react and trying different approaches and nothing is working for me. (BTW I am a regular contributor but working on my clients machine and can't remember my so password.)
The version of react router is 4.0, and state is stored using redux.
The scenario is that we are changing the order of routing in our application and would like to redirect any users that have Urls with the old structure to the new Url structure. I have tried the following (also note that I have "scrubbed" the names of the page, function calls and variables):
There is a trigger component for the section I need to direct from, with routing set up like this:
<Route path='page/:pageGuidGuid' component={PageTrigger}> </Route>
In the trigger component, ComponentWillMount makes a request that returns a 404 if the link is from the previous component, although it does redirect to the correct route. I am checking for and triggering the redirect in getInitialState, but the component keeps going through the lifecycle and ComponentWillMount is called, resulting in a 404 and then redirects to the correct page.
const PageTrigger = connectToStores(React.createClass({
getInitialState() {
this.checkForRedirect();
return {};
},
componentWillMount() {
if (this.props.params.guid) {
this.setCaseByGuid(this.props.params.guid);
}
},
checkFrorRedirect() {
/* logic to determine if it should redirect */
browserHistory.push(redirectUrl);
}
}
I have also tried creating a custom route...
<CaseRedirectRoute path='(cases/:caseGuid)' component={CaseTrigger}>
</CaseRedirectRoute>
And in a separate module (based off a login sample)
const CaseRedirectRoute = ({ component: Component, ...rest }) => (
<Route
{...rest} render={props => (
checkForCaseRedirect(...props) ? (
<Redirect to={{
pathname: getCaseUrlRedirectUrl(...props),
state: { from: props.location }
}} />
) : (
<Component {...props} />
)
)} />
);
I have WithRouter imported from react router. When I try to run this I am getting an error inside the react framework:
Uncaught Type Error: Cannot read property 'createRouteFromReactElement' of undefined
at RouteUtils.js:68
at forEachSingleChild (ReactChildren.js:52)
at traverseAllChildrenImpl (traverseAllChildren.js:98)
at traverseAllChildrenImpl (traverseAllChildren.js:114)
at traverseAllChildren (traverseAllChildren.js:186)
at Object.forEachChildren [as forEach] (ReactChildren.js:70)
at createRoutesFromReactChildren (RouteUtils.js:65)
at Function.createRouteFromReactElement (RouteUtils.js:35)
at RouteUtils.js:69
at forEachSingleChild (ReactChildren.js:52)
I've tried redirecting from app.js and page.js, but the PageTrigger is the first component having state set. I either need to figure out how to stop execution after the redirect, or figure out why my custom route keeps blowing up. Any help would be appreciated.
I'm not sure about your setup, but I would implement this redirect as this:
<Route path='oldurl/:p1/:p2' render={
routeProps => (
<Redirect to={'/newUrlHere/'+routeProps.match.params.p1} />
)
} />
So, if the route matches (you may specify exact match if necessary), when the Redirect "renders" it stops rendering and should start the rendering from the new URL. You may build your URL in the Redirect.to property as you wish
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})
}
I want to configure react-router to wait for Firebase to login (or logout) before rendering any route.
I've seen react-router has an onEnter() hook, but it seems this is an immediate callback, and I haven't quite figured out how I would make react-router wait until firebase triggers an firebase.auth().onAuthStateChanged(...) event.
Does react-router include some functionality to handle such cases?
The onEnter hook accepts a third parameter, callback?, which if specified will block loading of the route until it is called.
An example from the react-router repo:
function requireCredentials(nextState, replace, next) {
const query = nextState.location.query
if (query.qsparam) {
serverAuth(query.qsparam)
.then(
() => next(),
() => {
replace('/error')
next()
}
)
} else {
replace('/error')
next()
}
}
and the render method:
render((
<Router history={withExampleBasename(browserHistory, __dirname)}>
<Route path="/" component={App}>
<IndexRoute component={Form} />
<Route path="page" component={Page} onEnter={requireCredentials}/>
<Route path="error" component={ErrorPage}/>
</Route>
</Router>
), document.getElementById('example'))
However, there is a warning about using this for long-running operations in the documentation:
Caution: Using the callback in an enter hook causes the transition to wait until it is called. This can lead to a non-responsive UI if you don't call it very quickly.
I fixed it using a main component in my hierarchy that prevents children to be rendered until something has happened.
Here is the implementation. Not the most elegant code, but it works in case you are desperate as I was.