Next/React lazy load not working as expected for components - reactjs

Hi all I am new in Next/react I am trying to lazy load one component. What my expectation is that component must loads when it is visible in user view port or once a page is fully rendered.
Its html code should not come in the first response. But after debugging I found that it is not working as expected.
I have tried below approaches
index.js
const ProductTabbedWidget = React.lazy(() => import('../../../components/ProductSearch/ProductTabbed/ProductTabbedWidget'));
and
const ProductTabbedWidget = dynamic(() => import('../../../components/ProductSearch/ProductTabbed/ProductTabbedWidget'),{suspense:true,});
<Suspense fallback={<div>Loading</div>}>
<ProductTabWidget vehicleType={type}></ProductTabWidget>
</Suspense>
Component: ProductTabbedWidget
const ProductTabWidget = (props) => {
const getData = () =>{
// fetch the data from api
// ex. locahost:7000/api/get-data
}
useEffect(()=>{
getData()
},[])
return (
<div></div>
)
}
The call to this api is visible in chrome when the page loads.
I am confused if react lazy is the rigt way to do this. I know this can be done using javascript but is there any way to do it ony by react or next.
I have gone through these answers but none of them works.
React suspense/lazy delay?
How to know if React lazy load component is working or not? React js
react
React lazy loading - when to use

In react side use lazy loading like this.
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
In Next JS
import dynamic from 'next/dynamic'
const DynamicComponent = dynamic(() =>
import('../components/hello').then((mod) => mod.Hello)
)
function Home() {
return (
<div>
<Header />
<DynamicComponent />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home

Related

why is my component getting rendered once but then failing on refresh

i am working on small react assignment,
following is my component code. So my component is getting rendered once but then it just fails.i'll attach the screenshots too, can some one please explain what is happening?is there an error in the code or is it because of some rate limiting in API i am using?
import React from 'react'
const Menu = ({events}) => {
console.log(events);
return (
<div>
{events.map((event)=>{
return( <div key={event.category}>
<h3>{event.category}</h3>
</div>)
})}
</div>
)
}
export default Menu
code working image
error on same code pic
parent component code
import React,{useState,useEffect} from 'react';
import './App.css';
import Menu from './components/Menu';
function App() {
const [isLoading,setISLoading] = useState(true);
const[events,setEvents] = useState()
const getEvents = async()=>{
const response = await fetch('https://allevents.s3.amazonaws.com/tests/categories.json');
const eventsData =await response.json()
setISLoading(false);
setEvents(eventsData);
}
useEffect(()=>getEvents(),[]);
return (
isLoading?<h1>Loading...</h1>:<Menu events = {events}/>
);
}
export default App;
May be the parent component of Menu which is supplying events is not using any loading state. So when the component is mounted and starts making ajax calls, events is undefined. You need to put a condition over there like this:
import React from 'react'
const Menu = ({events}) => {
console.log(events);
return events ? (
<div>
{events.map((event)=>{
return( <div key={event.category}>
<h3>{event.category}</h3>
</div>)
})}
</div>
) : null
}
export default Menu

How would I test this using Jest & React Testing library?

I have a component that I would like to test using Jest and React Testing Library. When I say test, I'm basically saying that I want to check if the content shows up on the screen. However, I'm running into a serious problem because I'm dealing with an async operation that updates the state, so the content is not appearing immediately. How would I approach this problem? A code snippet would be much appreciated.
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';
const Home = () => {
const [tv, setTv] = useState([]);
const [tvLoading, setTvLoading] = useState(true);
// Go and fetch popular TV shows
const getPopularTv = async () => {
axios.get( ... )
setTv(data);
setTvLoading(false);
};
// This will run once. As soon as the component gets rendered for the 1st time
useEffect(() => {
getPopularTv();
}, []);
let TvData, loading;
const img_path = 'https://image.tmdb.org/t/p/w500/';
// If we have TV shows, set the 'TvData' variable to a pre-defined block of JSX using it.
if (tv && tv.total_results > 0) {
TvData = (
<div className="row animated fadeIn ">
{tv.results.slice(0, 10).map((show) => {
return (
// I WANT TO TEST IF THIS DIV APPEARS ON THE SCREEN
// SO, ON THIS DIV I'M SETTING UP THE 'data-testid'
// HOWEVER THIS IS A ASYNC OPERATION AND THE CONTENT
// WON'T SHOW UP IMMEDIATELY. HOW WOULD I TEST THIS???
<div
data-testid="home-shows" // HERE'S THE ID THAT I WANT TO USE IN MY TEST
className="col s6 m6 l6"
key={show.id}
>
<Link to={'/tvs/' + show.id}>
<img
className="responsive-img z-depth-3 poster tooltipped"
data-tooltip={show.name}
data-position="top"
src={img_path + show.poster_path}
alt={show.name}
/>
</Link>
</div>
);
})}
</div>
);
}
// Set up the 'loading' screen
loading = (
<div className="progress">
<div className="indeterminate"></div>
</div>
);
return (
<div className="container">
{tvLoading ? loading : TvData}
</div>
);
};
export default Home;
I've tried a combination of act, findByTestId, waitFor, etc. But I can't get it to work properly.
For example, I tried something like this:
it('should display TV shows', async () => {
const { getByText, findByTestId } =
render(
<BrowserRouter>
<Home />
</BrowserRouter>
)
await findByTestId('home-shows')
expect(getByText('More Info')).toBeInTheDocument();
});
My thinking was, if the content appears then it should contain the text of "More Info". If that's not the case the content is not visible, so the test should fail. however, the test fails regards if the content appears or not and I'm getting an error that I should wrap my test inside of an act() callback.
Thanks to #EstusFlask I came to a breakthrough. The solution was to use waitFor.
This is how I solved the problem:
it('should display movies', async () => {
render(
<BrowserRouter>
<Home />
</BrowserRouter>
);
const data = await waitFor(() => screen.findByTestId('home-shows'));
expect(data).toBeTruthy();
});

How to lazy load a react component

Suppose I need to build a home page and I want the h1 and p to be rendered first and if the user scroll to the area of MyComponent, MyComponnet gets rendered or the async call in MyComponent does not prevent h1 or p rendering so that to have a better user experience. Is there a way I can do it?
const Home = () => {
return <div>
<h1>Home Page</h1>
<p>aaaaaaaaaa</p>
<p>aaaaaaaaaa</p>
<p>aaaaaaaaaa</p>
<MyComponent />
</div>;
}
const MyComponent = () => {
const res = await fetch('some url...');
// ... some code process the res
const data = processRes(res);
return <div>data</div>
}
React is evolving for such use cases for enhanced experience and currently it's in experimental phase.
https://reactjs.org/docs/concurrent-mode-intro.html
Having said that, yours can be achieved with minor changes.
const MyComponent = React.lazy(() => import('./MyComponent')); // load lazy
return (
<>
<h1></h1>
<p></p>
<Suspense fallback={<SplashScreen/>}>
<MyComponent/>
</Suspense>
</>);

How to suspense the whole component before the image inside is loaded?

I have the following component:
In this component, there is an image.
When I load the page, I realize the page will be loaded first then the image will be loaded later. How can I suspense the whole page before the image is loaded?
I have tried to wrap the whole div (id="container") in a Suspense component with a fallback method but it doesn't seem to work on images, and only on components.
How can I suspense the whole component before the image inside is loaded?
import React from "react";
import resume from "../assets/Joe_Rogan_Resume.jpg";
import Banner from "./Banner";
const Resume = () => (
<div id="container">
<Banner styleclass="ResumePageBanner" h1text="My Resume" />
<div className="resumePage">
<div id="resume">
<img src={resume} alt="Resume" />
</div>
</div>
</div>
);
export default Resume;
The Suspense component in React 16.x only lets you wait for some code to load, not any type of data (including images). Suspense for data fetching is still experimental. So you need to handle it manually if you don't want to use an unstable version.
import React from "react";
import resume from "../assets/Joe_Rogan_Resume.jpg";
import Banner from "./Banner";
const Resume = () => {
const [isImageLoaded, setIsImageLoaded] = React.useState(false);
React.useEffect(() => {
const image = new Image();
image.onload = () => setIsImageLoaded(true);
image.src = resume;
return () => {
image.onload = null;
};
}, []);
if (!isImageLoaded) {
return null;
}
return (
<div id="container">
<Banner styleclass="ResumePageBanner" h1text="My Resume" />
<div className="resumePage">
<div id="resume">
<img src={resume} alt="Resume" />
</div>
</div>
</div>
);
}
export default Resume;

How to isolate tests in React Testing Library?

My tests are affecting each other. I'm using the default create-react-app setup with Typescript. All tests run fine individually but when I run all tests the last one fails (both in IntelliJ and npm test). The assertion that fails finds a value that was caused by the previous test.
Now I have read articles such as Test Isolation with React but I am not sharing any values between my tests. I also read about the cleanUp function and tried adding beforeEach(cleanup) and beforeAll(cleanUp), but I didn't found a working solution yet besides putting each test in a separate file. I feel the solution should be pretty simple.
I've quickly generated a create-react-app with TypeScript to reproduce the issue in a small as possible project: https://github.com/Leejjon/breakingtests
My App.tsx
import React from 'react';
import './App.css';
import {BrowserRouter, Link, Route} from 'react-router-dom';
const About: React.FC = () => {
return (
<div>
<h1 id="pageHeader">About page</h1>
<p>This is the about page</p>
</div>
);
};
const Home: React.FC = () => {
return (
<div>
<h1 id="pageHeader">Home page</h1>
<p>This is the home page</p>
</div>
);
};
const News: React.FC = () => {
return (
<div>
<h1 id="pageHeader">News page</h1>
<p>This is the news page</p>
</div>
);
};
const App: React.FC = () => {
return (
<div className="App">
<BrowserRouter>
<Link id="linkToHome" to="/">Home</Link><br/>
<Link id="linkToNews" to="/news">News</Link><br/>
<Link id="linkToAbout" to="/about">About</Link>
<Route exact path="/" component={Home}/>
<Route exact path="/news" component={News}/>
<Route exact path="/about" component={About}/>
</BrowserRouter>
</div>
);
};
export default App;
My App.test.tsx:
import React from 'react';
import {render, fireEvent, waitForElement} from '#testing-library/react';
import App from './App';
describe('Test routing', () => {
test('Verify home page content', () => {
const {container} = render(<App/>);
const pageHeaderContent = container.querySelector("#pageHeader")
?.firstChild
?.textContent;
expect(pageHeaderContent).toMatch('Home page');
});
test('Navigate to news', async () => {
const {container} = render(<App/>);
const pageHeaderContent = container.querySelector("#pageHeader")
?.firstChild
?.textContent;
expect(pageHeaderContent).toMatch('Home page');
const linkToNewsElement: Element = (container.querySelector('#linkToNews') as Element);
fireEvent.click(linkToNewsElement);
const pageHeaderContentAfterClick = await waitForElement(() => container.querySelector('#pageHeader')?.firstChild?.textContent);
expect(pageHeaderContentAfterClick).toMatch('News page');
});
test('Navigate to about', async () => {
const {container} = render(<App/>);
const pageHeaderContent = container.querySelector("#pageHeader")
?.firstChild
?.textContent;
expect(pageHeaderContent).toMatch('Home page');
const linkToAboutElement: Element = (container.querySelector('#linkToAbout') as Element);
fireEvent.click(linkToAboutElement);
const pageHeaderContentAfterClick = await waitForElement(() => container.querySelector('#pageHeader')?.firstChild?.textContent);
expect(pageHeaderContentAfterClick).toMatch('About page');
});
});
I found out by adding console.log(document.location.href); that the location is not reset. Which makes sense.
The code below resets the url. I could enter any domain to fix my tests, for example http://blabla/ will also work.
beforeEach(() => {
delete window.location;
// #ts-ignore
window.location = new URL('http://localhost/');
});
In TypeScript this gives an error: TS2739: Type 'URL' is missing the following properties from type 'Location': ancestorOrigins, assign, reload, replace. I didn't know how to fix this so I suppressed it it for now.
EDIT:
cleanup Unmounts React trees that were mounted with render, but doesn't reset state from stores/reducers. The solution I took for this situation was to create a reset function in my store and call it at the beginning of each test.
resetStore: () => {
set(initialState);
},
and call it in your test file
beforeEach(() => {
resetStore();
});
If you're using mocha, Jest, or Jasmine, the cleanup will be done automatically, but you need to put your render in a beforeEach to recreate it for every test.
let container;
beforeEach(() => {
const app = render(<App/>);
container = app.container
});
If you use another testing framework, you'll need to cleanup manually like so
import { cleanup, render } from '#testing-library/react'
import test from 'ava'
test.afterEach(cleanup)

Resources