How to lazy load a react component - reactjs

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>
</>);

Related

Next/React lazy load not working as expected for components

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

React - easy solution for global state or antipattern?

In my React app I have main component that contians the whole state of the application and also functions used to modify the state.
In order to get access to the state and the functions in all subcomponents I'm passing this from the main component through subcomponent attributes (app):
class App extends React.Component {
state = DefaultState;
funcs = getController(this);
render() {
return (
<Header app={this} />
<Page app={this} />
<Footer app={this} />
)
}
}
function getController(app: App) {
return {
switchPage: (page: string) => {
app.setState({ page: page });
},
}
}
Therefore in subcomponents I can access the state variables and modify them like this:
const HeaderComponent: React.FC<CompProps> = (props) => {
return (
<div>
<h1>{props.app.state.currentPage}</h1>
<button onClick={(e) => {props.app.funcs.switchPage('home')}}>Home</button>
</div>
)
}
This is the quickest/simplest solution I've found for global state. However I've never seen this in any tutorial or so. I guess main problem is the whole app will rerender when a single value is changed in global state, but the same goes for React Context API.
Question is that are there any disadvantages of this approach or a reason to not use this?
You can save func to an exported let variable and utilize the most recent version of func without re-rendering, but as this isn't a common occurrence, you won't find much information about it. Since it's simply javascript, any known hack will work. Also, the part of your question regarding react-context re-rendering is correct although you must consider that it will re-render and it will be more pruned for optimization of unmodified siblings.
You may alternatively supply a simple ref (useRef) to those components, which will allow them to access the most recent version of func, but because the ref reference itself does not change when the page is re-rendered, they will not be updated for function change.
I'm using react functional component but the class base may be so similar
export let funcs = null
const App = () => {
funcs = getController();
render() {
return (
<Header />
<Page />
<Footer />
)
}
}
// header component
import { funcs as appFuncs } from '~/app.js'
const HeaderComponent: React.FC<CompProps> = (props) => {
return (
<div>
{/* same thing can be happened for the state */}
<button onClick={(e) => {appFuncs.switchPage('home')}}>Home</button>
</div>
)
}
Hooks version
const App = () => {
const ref = useRef();
funcs = getController();
ref.current = {state, funcs};
// note ref.current changes not the ref itself
render() {
return (
<Header app={ref} />
<Page app={ref} />
<Footer app={ref} />
)
}
}
// header
const HeaderComponent: React.FC<CompProps> = (props) => {
return (
<div>
<h1>{props.app.current.state.currentPage}</h1>
<button onClick={(e) => {props.app.current.func.switchPage('home')}}>Home</button>
</div>
)
}
Any suggestions or other techniques will be much appreciated.

When using a functional component, why do parent state changes cause child to re-render even if context value doesn't change

I'm switching a project from class components over to functional components and hit an issue when using a context.
I have a parent layout that contains a nav menu that opens and closes (via state change in parent). This layout component also contains a user property which I pass via context.
Everything worked great before switching to the functional components and hooks.
The problem I am seeing now is that when nav menu is opened from the layout component (parent), it is causing the child component to re-render. I would expect this behavior if the context changed, but it hasnt. This is also only happening when adding useContext into the child.
I'm exporting the children with memo and also tried to wrap a container with the children with memo but it did not help (actually it seemed to cause even more renders).
Here is an outline of the code:
AppContext.tsx
export interface IAppContext {
user?: IUser;
}
export const AppContext = React.createContext<IAppContext>({});
routes.tsx
...
export const routes = <Layout>
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/metrics' component={Metrics} />
<Redirect to="/" />
</Switch>
</Layout>;
Layout.tsx
...
const NavItems: any[] = [
{ route: "/metrics", name: "Metrics" }
];
export function Layout({ children }) {
const aborter = new AbortController();
const history = useHistory();
const [user, setUser] = React.useState<IUser>(null);
const [navOpen, setNavOpen] = React.useState<boolean>(false);
const [locationPath, setLocationPath] = React.useState<string>(location.pathname);
const contextValue = {
user
};
const closeNav = () => {
if (navOpen)
setNavOpen(false);
};
const cycleNav = () => {
setNavOpen(prev => !prev);
};
React.useEffect(() => {
Fetch.get("/api/GetUser", "json", aborter.signal)
.then((user) => !aborter.signal.aborted && !!user && setUser(user))
.catch(err => err.name !== 'AbortError' && console.error('Error: ', err));
return () => {
aborter.abort();
}
}, []);
React.useEffect(() => {
return history.listen((location) => {
if (location.pathname != locationPath)
setLocationPath(location.pathname);
})
}, [history]);
const navLinks = NavItems.map((nav, i) => <li key={i}><Link to={nav.route} onClick={closeNav}>{nav.name}</Link></li>);
return (
<div className="main-wrapper layout-grid">
<header>
<div className="header-bar">
<div className="header-content">
<div className="mobile-links-wrapper">
<ul>
<li>
<div className="mobile-nav-bars" onClick={cycleNav}>
<Icon iconName="GlobalNavButton" />
</div>
</li>
</ul>
</div>
</div>
</div>
<Collapse className="mobile-nav" isOpen={navOpen}>
<ul>
{navLinks}
</ul>
</Collapse>
</header>
<AppContext.Provider value={contextValue} >
<main role="main">
{children}
</main>
</AppContext.Provider>
<a target="_blank" id="hidden-download" style={{ display: "none" }}></a>
</div>
);
}
Metrics.tsx
...
function Metrics() {
//Adding this causes re-renders, regardless if I use it
const { user } = React.useContext(AppContext);
...
}
export default React.memo(Metrics);
Is there something I am missing? How can I get the metrics component to stop rendering when the navOpen state in the layout component changes?
Ive tried memo with the switch in the router and around the block. I've also tried moving the contextprovider with no luck.
Every time your Layout component renders, it creates a new object for the contextValue:
const contextValue = {
user
};
Since the Layout component re-renders when you change the navigation state, this causes the context value to change to the newly created object and triggers any components depending on that context to re-render.
To resolve this, you could memoize the contextValue based on the user changing via a useMemo hook and that should eliminate the rendering in Metrics when the nav state changes:
const contextValue = React.useMemo(() => ({
user
}), [user]);
Alternatively, if you don't really need the object, you could simply pass the user as the context value directly:
<AppContext.Provider value={user}>
And then access it like:
const user = React.useContext(AppContext);
That should accomplish the same thing from an unnecessary re-rendering point of view without the need for useMemo.

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();
});

Creating a parent 'workspace' component in ReactJS

Using ReactJS, I am trying to create a common workspace component that will have toolbar buttons and a navigation menu. The idea I have is to re-use this component to wrap all other dynamic components that I render in the app.
Currently, I've created a Toolbar and MenuBar components that I then add to each component in the app as such:
<Toolbar/>
<MenuBar/>
<Vendors/>
This does not feel right, since my aim is to have just one component which would be something like:
<Workspace>
<Vendor/>
</Workspace>
However, I am not sure of how to achieve this and whether this is the right approach.
As to whether or not it is the right approach is subjective, but I can provide insight into one way to make a "wrapper" type component:
// Your workspace wrapper component
class Workspace {
render() {
return (
<div className="workspace">
<div className="workspace__toolbar">
Toolbar goes here
</div>
<div className="workspace__nav">
Navgoes here
</div>
<div className="workspace__content">
{this.props.children}
</div>
</div>
)
}
}
// Using the component to define another one
class MyComponent {
render() {
return (
<Workspace>
This is my workspace content.
</Workspace>
)
}
}
You can also look at HOC's or Higher Order Components to wrap things.
React offer two traditional ways to make your component re useable
1- High-order Components
you can separate the logic in withWorkspace and then give it a component to apply that logic into it.
function withWorkSpace(WrappedComponent, selectData) {
// ...and returns another component...
return class extends React.Component {
render() {
// ... and renders the wrapped component with the fresh data!
// Notice that we pass through any additional props
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
}
const Component = () => {
const Content = withWorkSpace(<SomeOtherComponent />)
return <Content />
}
2- Render Props
or you can use function props then give the parent state as arguments, just in case you need the parent state in child component.
const Workspace = () => {
state = {}
render() {
return (
<div className="workspace">
<div className="workspace__toolbar">
{this.props.renderTollbar(this.state)}
</div>
<div className="workspace__nav">
{this.props.renderNavigation(this.state)}
</div>
<div className="workspace__content">
{this.props.children(this.state)}
</div>
</div>
)
}
}
const Toolbar = (props) => {
return <div>Toolbar</div>
}
const Navigation = (props) => {
return <div>Toolbar</div>
}
class Component = () => {
return (
<Workspace
renderNavigation={(WorkspaceState) => <Navigation WorkspaceState={WorkspaceState} />}
renderTollbar={(WorkspaceState) => <Toolbar {...WorkspaceState} />}
>
{(WorkspaceState) => <SomeComponentForContent />}
</Workspace>
)
}

Resources