I have a component that takes a while to load. Actually, it's a component which loads an <iframe> of another website, which takes a while to load.
I would like the component to mount and therefore run the componentDidMount code block that loads the iframe so that when the user clicks on the 'create' tab, the user instantly sees the iframe in the correct the <main> section of the page.
Is there a way to instruct react-router to pre-load the component while retaining the same conditional rendering logic on route change and retaining the position on the page of the rendered component?
This is currently my render() statement on the root level of the app to give you some context:
render() {
return (
<div className="App">
<Nav />
<Snackbar
open={this.props.snackbar.get().open}
message={this.props.snackbar.get().message}
autoHideDuration={4000}
onRequestClose={() => this.handleSnackbarRequestClose()}
/>
<TreeViewer />
<PayloadListener/>
<main>
<ThankYouModal open={this.props.showConfirmationModal.get()} handleClose={ () => this.props.showConfirmationModal.set(false) }/>
<Switch>
<Route path="/imageservices" component={ImageServicesController} />
<Route path="/create" component={Iframe} />
<Route exact path="/account" component={Account} />
<Route exact path="/analytics" component={AnalyticsController} />
<Route path="/support" component={SupportView} />
<Route path='/login' render={ (props) => <Login { ...props } /> } />
<Route path='/logout' render={ (props) => <Logout { ...props } /> } />
</Switch>
</main>
</div>
);
}
This is the component I would like React Router to pre-load:
<Route path="/create" component={Iframe} />
How can I achieve that?
Well if you take a look at a React component lifecycle you can see that render always runs before componentDidMount. So effectively you wouldn't be able to mount a component without rendering it.
These are the lifecycle methods called while mounting:
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
You can try a few things:
Render the iframe when you need it and fade it in when it is loaded.
You can easily add an eventListener for the load event of the iframe and make the appropriate changes for it to fadeIn when the state changes.
...
componentDidMount() {
this.iframe.addEventListener("load", this.handleLoad);
}
componentWillUnmout() {
this.iframe.removeEventListener("load", this.handleLoad);
}
...
<iframe ref={ref => this.iframe = ref } />
...
I've done this when I didn't always need the iframe. It was something sporadic and it was an always one page kinda thing.
You can play around with the preload attribute
This is ideal if you know the user will stumble upon the iframe content. You can start preloading content that the user will most likely encounter effectively preventing the user from waiting.
You can add this to the head of your document:
<link rel="preload" href="your-doc-url" as="document" />
And then use your iframe normally:
<iframe src="your-doc-url"></iframe>
If your iframe url is dynamic and dependent on some info from the authenticated user and you can't put it right away in your html file remember you can always use react-helmet to render it to the head tag whenever you have the info you want.
Not via react-router, but you can use link preload as document in your index.html to ensure the document is lazy-loaded by the browser. It's intended purpose is to preload documents which can then be displayed in iframe. You also won't need to change your router mechanism.
Read more about this here - https://developer.mozilla.org/en-US/docs/Web/HTML/Preloading_content.
Basically, just add this to the head of your index.html:
<link rel='preload' href='your_iframe_url' as='document'>
Related
Here is a part of my root component code:
const App = () => {
return (
<Switch>
<Route path="/products">
<AllProducts />
</Route>
<Route path="/teams">
<AllTeams />
</Route>
<Route exact path="/">
<Home />
</Route>
</Switch>
{isVisible && (
<Overlay />
)}
);
};
I have a Home component, from where I can navigate to other pages using a router. In Home component I have a button and when clicking on it, I set isVisible variable to true and <Overlay /> component is being opened as an overlay (similar to modal) and when it's open, the / pathname does not change, because it's outside router. There may be many links and I can navigate to other routes, for example /teams and also go much more deeper away from it. And the question is, if I hit the browser back button at some point, how to show this <Overlay />, like it were in the browser history?
Add it as a query param.
A query string is a part of a uniform resource locator (URL) that assigns values to specified parameters.
I have a render function inside App.js.
return (
<div className="container">
<h1 className="text-center main-title">Activity Logger</h1>
<Router>
<NavigationBar />
<Route exact path="/">
{this.renderForm()}
</Route>
<Route path="/activitydisplay">{this.renderTable()}</Route>
</Router>
</div>
);
}
The Router is a BrowserRouter. The functions it call are
renderForm = () => {
if (this.state.formDataError) {
return "Error loading data";
}
console.log("renderForm was called");
return (
<div className="mt-3">
<ActivityForm
machinesList={this.state.machinesList}
operatorsList={this.state.operatorsList}
onFormSubmit={this.postFormData}
postSuccessCount={this.state.postSuccessCount}
loggedOperator={this.props.cookies.get("logger") || null}
/>
</div>
);
};
renderTable() {
if (this.state.tableDataError) {
return "Error loading data";
}
return (
<div className="mt-3">
<ActivityDisplay
activityData={this.state.activityData}
machines={this.state.machinesList}
operators={this.state.operatorsList}
editDataHandler={this.editData}
deleteDataHandler={this.deleteData}
/>
</div>
);
}
The components are remounted when I switch between the routes at the front end. To troubleshoot, I put logging in renderForm function and in the ActivityForm constructor. I can see the logging from renderForm only when the App component is mounted and not when I switch between routes. However, I can see the logging from ActivityForm constructor whenever I switch between the components at the front end using a navigation bar.
I lose all the states I had in the component because of this. The behavior I expect is that the component should not remount when I switch tabs. Can anyone please help?
React Router does basically update the UI according to a specific state. They handle that in a very organized manner with Browser Navigation events like Back, Refresh & so on.
As we all know, when React change the UI according to a specific updated state, it will mount & unMount relevant components accordingly. https://reacttraining.com/blog/mount-vs-render/ article explains that concept very nicely.
That same behavior will apply to React Router also. When you navigate to different path, existing components can be unMount & new components can be mount according to your code.
Route with render prop
return (
<div className="container">
<h1 className="text-center main-title">Activity Logger</h1>
<Router>
<NavigationBar />
<Router>
<Route path="/activitydisplay" render={() => this.renderTable()} />
<Route exact path="/" render={() => this.renderForm()} />
</Router>
</Router>
</div>
);
Note - It's better if you can pass the component into Route using render prop. That will make renderForm or renderTable to run before ActivityForm or ActivityDisplay mount. It will also make sure that to pass all the updated props correctly to the components where necessary.
Let me know if you need further support.
This is a basic situation: I have a Nav with links and several routes for those links in a main content area.
const App = () => (
<div id="page-container">
<Nav />
<main id="main">
<Switch>
<Route exact path="/" component={IndexPage} />
<Route exact path="/blog" component={BlogPage} />
<Route exact path="/about" component={AboutPage} />
<Route component={NotFoundPage} />
</Switch>
</main>
</div>
);
Sometimes when I click the nav links the main content area updates instantaneously, and sometimes it takes maybe 2 or 3 seconds to load the next component. I believe that's an outrageous amount of time to wait (with no indication that anything is loading).
All I seemed to find was React Router Transitions, which I tried, but it seemed like it required a static timeout for the transition animation. I only want a loading animation to appear where necessary; I do not want the routes animated every single time. React Transition Group also seems to require each route to be wrapped in a Transition component, which requires a location argument, which I don't seem able to have in my current layout.
Here's what I hoped to do:
I hoped to add a boolean state variable to my Page component to keep track of loading:
class Page extends React.Component {
constructor (props) {
super(props);
this.state = {
loading: true
};
}
componentDidMount() {
this.setState({
loading: false
});
}
render() {
return (
<article className={this.state.loading ? 'loading' : ''}>
... stuff ...
</article>
);
}
}
But this doesn't seem to work, because componentDidMount fires after render, i.e. after all of the loading time. So, basically, (1) it loads for awhile, (2) renders article with the 'loading' class for probably a few milliseconds until (3) componentDidMount sets the state and renders again, replacing the class.
So I'm guessing I need to add the loading animation on the previous page component, instead of the next page component? Or is it better to ajax all my page content? I'm really surprised how little information I've found on this, and I'm hoping someone has some helpful advice. Thanks!
Try using React Lazy and Suspense fallback
I recently reconfigured my React app, and it seems that I have broken the functionality of my parameterized routes. Rather than going too deep into how it used to work, I'll describe what I am currently stuck with and what I am aiming to achieve...
In a navigation panel on the side of my page, a user can view a list of links to resources - the urls for these links would be something like:
user/123
user/456
user/789
group/123
group/456
group/789
Clicking the first link will now render the User component in the main div on my page (rendering in {this.props.children} - see App.jsx below), and a call to componentDidMount() pulls data for the user with id 123 and populates the component. Ok, so far, so good.
However, if a user now clicks on the link for user/456, nothing happens. The url in the navbar changes, but the User component does not re-render, though clicking a link for a group will correctly clear out the User component and render the Group component instead... then, of course, I have the same problem with the groups, etc...
How can I force the re-rendering of a component when the pathname remains the same but the parameter has changed? If I've clicked the link for user/123 and then I click the link for user/456, I want that User component to re-render and pull the new data.
Other relevant code...
index.js
import { Router, Route, browserHistory } from 'react-router';
<Router history={browserHistory}>
<Route path="/" component={App}>
<Route path="/user/:id" component={User} />
<Route path="/group/:id" component={Group} />
</Route>
</Router>
App.jsx
<div className="App">
<div className="body_section_container">
<div className="body_section main">
<Nav />
{this.props.children}
</div>
<div className="body_section sidebar">
<Search searchModel={this.searchAll} user_list={this.state.user_list} group_list={this.state.group_list} organizations_list={this.state.organizations_list} />
</div>
</div>
</div>
Try plugging into componentWillReceiveProps, which should fire when the route params change. Then you can receive the new route params, submit a new query, and update state.
In the Route component, specify a key.
<Route path={YOURPATH} render={(props) => <YourComp {...props} keyProp={id} key={id}/>} />
when react see a different key, it will trigger rerender.
So I have my routes defines as follows:
<Route path="/manage" component={Manage}>
<IndexRoute component={Manage}></IndexRoute>
<Route path=":id" component={Edit}></Route>
</Route>
</Route>
Now when I click on a button in my Manage component I call following function:
handleEditClick(e) {
e.preventDefault();
let selectedId= this.state.selectedId;
this.props.router.replace("/manage/" + selectedId);
},
My browser does display me the correct link but my component is not loaded as should. It only renders me the Manage component and not the Edit component.
Am I missing something here?
UPDATE
changing the child route to <Route path="/manage/:id" component={Edit}></Route> also loads me the Manage component
UPDATE 2
if I do not use child routes but in stead create them on the same level, the Edit component does render, but I'd like to use child routes.
On your render() you need a {this.props.children} ... it's where React router knows where to put child Component
In your routes
<Route path="/manage" component={Manage}>
<IndexRoute component={Manage}></IndexRoute>
<Route path=":id" component={Edit}></Route>
</Route>
If you navigate to /manage/:id, React Router renders Manager and Edit components... but where to put Edit?
So
you need to have something like this.
class Manager extends Component {
render(){
return (
<div>
Hello
{this.props.children}
</div>
);
}
}
So React Router knows to put Edit along side when you go to /manage/:id
<div>
Hello
<Edit />
</div>