My server is a bit slow and when displaying even small size images, you can see them loading on screen, and I would like them to be displayed right away.
Thankfully I have a landing page which then after pressing a button, redirects to the main page which contains the images.
This routing is controlled in my app.js which is like the root file. This file routes to the landing page and to the main page. I thought that I could just load the images there and then pass them as props via the router, to the main page:
import projectImage1 from './assets/1.png';
import projectImage2 from './assets/2.png';
import projectImage3 from './assets/3.png';
class App extends Component {
render() {
return (
<Route render={({location}) => (
<TransitionGroup>
<CSSTransition
key={location.pathname === '/' ? location.key : null} // Use transition only if on landing page
timeout={800}
classNames="fade"
>
<Switch location={location}>
<Route path="/" exact component={Landingpage} />
<Route path="/app/:name" exact render={(props) => <MainApp
projectImage1={projectImage1}
projectImage2={projectImage2}
projectImage3={projectImage3}
/>} />
</Switch>
</CSSTransition>
</TransitionGroup>
)} />
);
}
}
As you can see my MainApp.js received the 3 images as props. This is technically working without errors but I'm still seeing the images loading on screen. Why is it so? How can I load them in advance so that they can be displayed smoothly?
Thanks
The browser doesn't load resources before it needs by default. For images this is when the image itself is visible. This is due to performance, but there is a way for you to load these images beforehand.
If you do this in your componentDidMount lifecycle method in your App component, the browser will load the images:
class App extends Component {
componentDidMount() {
const imagesToBePreloaded = [projectImage1, projectImage2, projectImage3]
imagesToBePreloaded.forEach(image => { new Image().src = image })
}
render() {
// [...] Removed for abbreviation.
}
}
Related
I have a Menu, I need that when I click on an item the Menu disappears. I have seen a solution in this platform to create a Pages folder and there to put all the pages, but it does not allow me to do import Menu from '. / Pages', I get an error: cant resolve'. / Pages. I am working with react-router-dom. Link de posible solution: ReactJS How do you switch between pages in React?
Link sandbox: https://codesandbox.io/s/jovial-bartik-jsjj9 line:12
With the way you've structured your code, You'll have to do as you did for the 404 page:
import MainMenu from "./pages/MainMenu";
alternatively you can add an index.js file in the pages folder and export your components out if you want to keep the ./pages style
i.e
index.js
export * from './MainMenu.js'
export * from './404.js'
...etc
In your MainMenu.jsx file, you need to import React => import React from "react";
Then in your index.js file import the files as =>
import MainMenu from "./pages/MainMenu";
import NotFountPage from "./pages/404";
import Clients from "./pages/Clients";
Edit the index.js file and add the following code segment:
<Router>
<Switch>
<Route exact path="/">
<MainMenu/>
</Route>
<Route path="/404">
<NotFountPage />
</Route>
<Route path="/clients">
<Clients />
</Route>
<Redirect to="/404" />
</Switch>
</Router>
If you are just trying to make a item disappear I will show you how.
import Nav from "./nav";
consturctor(props) {
super(props);
this.state ={
showNav: true
}
}
render() {
const show = this.state.showNav; // make a var to hold current state for this value
return(
{show ? <Nav />: null} // if show then show this component else show null
)
then make a function to change that state
dontShowNav = () => {
this.setState({showNav: false}); // set show to false hide component
}
}
I have my app.js file which looks like this:
class App extends Component {
render() {
const {classes} = this.props;
return (
<React.Fragment>
<AppBar/>
<BrowserRouter>
<Route render={({location}) => (
<TransitionGroup>
<CSSTransition
key={location.key}
timeout={100}
classNames="someanimation"
>
<Switch location={location}>
<Route exact path="/" component={HomePage} />
<Route exact path="/contact" component={ContactPage} />
<Route exact path="/customer/:id" component={CustomerPage} />
<Route component={ErrorPage} />
</Switch>
</CSSTransition>
</TransitionGroup>
)} />
</BrowserRouter>
</React.Fragment>
);
}
}
This component has an appbar and a router with routes underneath. The point of the appbar being here is that the application then only has one appbar at all times throughout the application. Only the pages underneath change.
Inside my contact page I have a button that goes to a custom page with a param passed:
<Button component={Link} to={'/customer/' + customerID[99]}>
When the application goes to this customer page I want the appbar to show a back button. So I have to somehow notify the appbar to show this button and then also know which page to go back to (it should be the last page). I have googled some examples but I can't find one that fits this case.
React router contains withRouter higher-order component that can provide relevant props to application bar component.
React router is powered by history.js library which is an abstraction over browser history API. While it can be used to navigate over browser history with history.goBack(), it won't work well with navigating over application alone because window history may contain other websites.
The component can look like this (a demo) and should be a child of router component to get router props:
#withRouter
class AppBar extends Component {
state = {
locations: [this.props.location]
};
componentDidMount() {
this.props.history.listen((location, action) => {
if (action === 'REPLACE')
return;
this.setState({
locations: [location, ...this.state.locations]
})
});
}
back = () => {
const [location, ...locations] = this.state.locations;
this.setState({ locations });
this.props.history.replace(location);
}
render() {
return (
<>
{this.state.locations.length > 1 && <button onClick={this.back}>Back</button>}
</>
);
}
}
It keeps track on location changes and navigates through them. It would be more complex task to keep it in sync with browser history navigation buttons (back and forward).
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 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'>
I have a react multi step form with a flux store like this:
// MultiForm.js
....
import LoadFile from './components/LoadFile';
import LoadPeople from './components/LoadPeople';
import Confirmation from './components/Confirmation';
class MultiForm extends Component {
.
.
.
nextPage() {
// Update page number with a flux action
MultiStepActions.nextPage();
}
previousPage() {
// Update page number with a flux action
MultiStepActions.previousPage();
}
render() {
const {pagina} = this.state;
<div className="container">
{page === 1 && <LoadFile
handleChange={this.handleChange}
file={this.state.file}
nextPage={this.nextPage}
data1={this.state.data1}
data2={this.state.data2}
data3={this.state.data3}/>
}
{page === 2 && <LoadPeople
listPeople={this.state.listPeople}
nextPage={this.nextPage}
previousPage={this.previousPage}
handleChangePeople={this.handleChangePeople}/>
}
{page === 3 && <Confirmation
listData={this.state.listData}
nextPage={this.nextPage}
previousPage={this.previousPage}/>
}
</div>
}
}
// Index.js with react-router
import Menu from './components/Menu';
class Index extends Component {
class App extends Component {
render() {
return (
<div>
<Menu />
{this.props.children}
</div>
)
}
}
render((
<Router history={browserHistory}>
<Route path="/" component={App}>
<Route path="/MultiForm" component={MultiForm}>
</Route>
</Route>
</Router>
), document.getElementById('app'));
}
It is a summary of the main component (MultiForm) and a basic react router scenario.
In the component I am using a flux store to set and get the actual number of page of the multi step form and then render the component according to the actual page.
In the components (LoadFile, LoadPeople, Confirmation) I have a button to navigate to the next and previos page (via nextPage and previousPage functions) and all wokr ok.
Now I want achieve the same result using the back and previous buttons of the browser and I suppose with react-router. So, how I must configure react router or what I need to do in order to getting the browser buttons to work equals to my next and previous buttons?
You cannot override a browser's navigation buttons. You can however, hook up the pages states defined in react-router, and as the user browses around using the navigation buttons, your application state remains synced to the browser state.
What i would suggest for you here is to create a parameter for your MultiForm route:
<Route path="/Multiform/:page" component={MultiForm} />
This would then allow you to get that page parameter in this.props.params.page
Even better, you can hook up each of the child components as routes in themselves:
<Route path="/Multiform/" component={MultiForm}>
<Route path="1" component={LoadFile} />
<Route path="2" component={LoadPeople} />
<Route path="3" component={Confirmation} />
</Route>
class Multiform extends Component {
//...
render() {
return (
<div className="container">
{ this.props.children }
</div>
)
}
}