React Lazy Load useEffect - reactjs

I have a problem getting clientWidth from useEffect.
There is a lazyLoad which loads a page with users.
import {Suspense, lazy} from 'react';
const UsersContainer = lazy (() => import ('./ users / users-container'));
const Index = (props) => {
return (
<Suspense fallback = {<div id = {'loading'} />}>
<UsersContainer />
</Suspense>
)
}
export default Index;
UsersContainer has a child component dataTooltip, which displays the full User name if it does not fit into the block.
import React, {useEffect, useRef} from 'react';
import '../common.scss';
const DataTooltip = ({title, ... props}) => {
let ref = useRef ();
useEffect (() => {
if (ref.current.scrollWidth> ref.current.clientWidth) {
ref.current.setAttribute ('data-tooltip', title)
}
});
return (
<div ref = {ref} className = {'tooltip'}>
{
React.cloneElement (props.children, {ref: ref})
}
</div>
)
}
export default DataTooltip;
What's the problem?
After the UsersContainer is loaded and rendered in the DOM, it has 'display: none' and at the same moment useEffect in DataTooltip is triggered asynchronously.
As a result, DataTooltip says that clientWidth = 0, due to the fact that the parent has 'display: none'.
How to make useEffect work after lazyLoad removed 'display: none'.
PS: useLayoutEffect works the same, clientWidth = 0
Solved the problem this way:
<Suspense fallback={<div id={'loading'}/>}>
<Main/>
<Acquaintance/>
<UsersContainer/>
<RegisterContainer/>
</Suspense>
to
<Suspense fallback={<div id={'loading'}/>}>
<Main/>
</Suspense>
<Suspense fallback={<div id={'loading'}/>}>
<Acquaintance/>
</Suspense>
<Suspense fallback={<div id={'loading'}/>}>
<UsersContainer/>
</Suspense>
<Suspense fallback={<div id={'loading'}/>}>
<RegisterContainer/>
</Suspense>

I don't know if this solves your issue - but one thing I notice immediately is that you're missing the dependency array from your useEffect hook.
import React, {useEffect, useRef} from 'react';
...
const DataTooltip = ({title, ... props}) => {
let ref = useRef ();
useEffect (() => {
if (ref.current.scrollWidth> ref.current.clientWidth) {
ref.current.setAttribute ('data-tooltip', title)
}
});
return (...)
}
export default DataTooltip;
should be:
import React, {useEffect, useRef} from 'react';
...
const DataTooltip = ({title, ... props}) => {
let ref = useRef ();
useEffect (() => {
if (ref.current.scrollWidth> ref.current.clientWidth) {
ref.current.setAttribute ('data-tooltip', title)
}
}, [ref]);
return (...)
}
export default DataTooltip;
Also keep in mind that this will cause the component to re-render whenever ref changes, per the documentation of the useEffect hook you should always declare any variables from the upper scope used in the useEffect hook as part of the dependency array, and if you dont dont use any such variables you should pass an empty array still to prevent running an infinite loop.

Related

why components state data are gone in conditional rendering

I am new to react and come from a background of functional component only.
In my react project,
When I conditionally rendering , ie from false to true, the data inside child component will be gone.
Then I wonder why is that.
Then I heard a concept called unmounting. It means, when my condition change from true to false, the component will get unmounting. And in unmounting, the state inside will gone.
But then, it doesn't add up.
Q: Whenever we re-render any other components, just like the normal situation, we will also unmount component in order to do re-rendering. And our state value would not be gone.
Why this problem was happened especially on having conditional statement in react?
Edit:
My emphsis is not on how to avoid state loss. My question is that why data will be gone in conditional rendering. And why unmounts will cause such problem, but re rendering would not cause such ( both also involves unmounting)
Here is my code
In parent:
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import Child1 from "./child";
import "./styles.css";
function Parent() {
const [message, setMessage] = useState("initial text");
const [showChild,setShowChild] = useState(true);
useEffect(() => {
console.log("useeffect in parent");
});
return (
<div className="App">
<button onClick={() => setShowChild(!showChild)}>show child</button>
{showChild?
<Child1 />
:
null
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Parent />, rootElement);
In child:
import React, { useEffect, useState } from "react";
function Child1() {
useEffect(() => {
console.log("useeffect in child");
console.log("newMessage: " + newMessage);
});
const [newMessage, setNewMessage] = useState("");
return (
<div>
<input onChange={(event) => setNewMessage(event.target.value)} />
</div>
);
}
export default Child1;
Add some picture to illurste what I mean by data lose in conidtional rendering
enter
https://i.stack.imgur.com/UrIhT.png
click to not show it
https://i.stack.imgur.com/0OC87.png
click to show again
https://i.stack.imgur.com/4zlWk.png
Try moving all state management to the parent component and leave the child component 'dumb'. You can pass the setMessage and any other state variables to the child as props.
Parent:
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import Child1 from "./child";
import "./styles.css";
function Parent() {
const [message, setMessage] = useState("initial text");
const [showChild,setShowChild] = useState(true);
useEffect(() => {
console.log("useeffect in parent");
});
return (
<div className="App">
<button onClick={() => setShowChild(!showChild)}>show child</button>
{showChild?
<Child1 setMessage={setMessage}/>
:
null
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Parent />, rootElement);
Child:
import React from "react";
function Child1({setMessage}) {
return (
<div>
<input onChange={(event) => setMessage(event.target.value)} />
</div>
);
}
export default Child1;
The answer for your question is very simple, While unmounting you are removing the component itself from react-dom. The state, props and all the data's handled inside your component will be active only If the component is inside the react-dom. If the particular component is unmounted, all the states and props that was created and processed will also be removed from react-dom along with the component. And the fresh component, state and props will be mounted in the react-dom if you conditionally render the component again.

useEffect() keeps re-rendering, how to stop it from re-rendering

import React from "react";
import { ThemeProvider } from "styled-components";
import { theme } from "./theme-default";
import { HashRouter as Router, Route, Switch } from "react-router-dom";
import { GlobalStyle } from "./themes";
import { Security } from '#okta/okta-react';
import { OktaAuth } from "#okta/okta-auth-js";
import { FeeSitePageHeader } from "./app/fee--site-page-header";
import { FeeSitePageFooter } from "./app/fee-site-page-footer";
import Dashboard from "./app/Dashboard/Dashboard";
import Logout from "./app/Logout/Logout";
import Login from "./app/Login/Login";
function App() {
const config = {
issuer: 'https://dev-95779092.okta.com/',
clientId: '***',
redirectUri: window.location.origin + '/?redirect_url=/login/callback'
};
const authClient = new OktaAuth(config);
function restoreOriginalUri() {
console.log(restoreOriginalUri);
};
return (
<ThemeProvider theme={theme}>
<GlobalStyle />
<Router >
<FeeSitePageHeader />
<Security oktaAuth={authClient} restoreOriginalUri={restoreOriginalUri}>
<Switch>
<Route path="/" exact>
<Dashboard />
</Route>
</Switch>
</Security>
<FeeSitePageFooter />
</Router>
</ThemeProvider>
);
}
export default App;
This is the dashboard component.
import { useOktaAuth } from '#okta/okta-react';
import React from 'react';
import * as Styled from "./Dashboard.styled";
function Dashboard(){
const { authState, oktaAuth } = useOktaAuth();
React.useEffect(() => {
oktaAuth.signInWithRedirect();
if (window.location.search?.includes('redirect_url=/login/callback')) {
console.log("Check if the browser contains redirect");
}
}, [])
return(
<>
<Styled.CardsWrapper>Apple</Styled.CardsWrapper>
<Styled.CardsWrapper>Apple</Styled.CardsWrapper>
<Styled.CardsWrapper>Apple</Styled.CardsWrapper>
<Styled.CardsWrapper>Apple</Styled.CardsWrapper>
<Styled.CardsWrapper>Apple</Styled.CardsWrapper>
<Styled.CardsWrapper>Apple</Styled.CardsWrapper>
</>
);
}
export default Dashboard;
When the application starts, call goes to OKTA, authenticate logic does its part and url changes to "http://localhost:8080/#/dashboard?redirect_url=/login/callback". After this I want useEffects to get called, Problem is Dashboard keeps re-rendering itself how to stop re-rendering here?
See the docs when you are using the functional component you shouldn't recreate the octaAuth every time.
https://github.com/okta/okta-react#creating-react-router-routes-with-function-based-components
Then you call signInWithRedirect in useEffect every time when component mounts. It should be wrapped with authState.isAuthenticated. Example in docs https://github.com/okta/okta-react#show-login-and-logout-buttons-function-based
When you change the URL you "reload" the page hence - useEffect runs again.
Pass the parameter inside the useEffect hook
React.useEffect(() => {
oktaAuth.signInWithRedirect();
if (window.location.search?.includes('redirect_url=/login/callback')) {
console.log("Check if the browser contains redirect");
}
}, [authState])

How to use react-router-dom with Context API V6?

I am changing the value in PC component but it is not reflected in the BR1 component. If I don't use react-router-dom, everything works fine, but I need the routes.
App.js code
import React, { createContext, useState } from 'react';
import './App.css';
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import BR1 from './Components/BR1';
import PC from './Components/P_c1'
import BR from './Components/BR';
export const BRcontext = createContext();
function App() {
const [value, setValue] = useState(false)
return (
<div>
<BRcontext.Provider value={{value, setValue}}>
<Router>
<Routes>
<Route path='/PC' element={<PC/>} />
<Route path='/BR1' element={<BR1/>} />
<Route path='/BR' element={<BR/>} />
</Routes>
</Router>
</BRcontext.Provider>
</div>
);
}
export default App;
PC code
import React, { useContext } from 'react'
import './Profile.css';
import { BRcontext } from '../App';
export default function Profile() {
const {value, setValue} = useContext(BRcontext);
return (
<div>
<div className='container mt-5'>
<div className='row'>
<div>
<h3 className='mt-5'>Send Request</h3>
<button className='btn btn-success mt-3 ps-3 pe-3' onClick={()=>{setValue(true)}}>Request</button>
</div>
</div>
</div>
</div>
)
}
BR1 code
import React, { useContext } from 'react'
import BR from './BR'
import { BRcontext } from '../App'
import { Link } from 'react-router-dom';
export default function BR1() {
const {value} = useContext(BRcontext);
// let navigate = useNavigate();
return (
<div>
{console.log(value)} //this remains false
{value ? <Link to="/BR"/>: console.log('hello there!')}
</div>
)
}
In BR1 code, I want the value to become true when a button in the PC component is clicked
Link - https://codesandbox.io/s/great-star-bzhuvw?file=/src/App.js
It seems there's no way to navigate from /PC to /BR1 unless changing the browser URL directly, and by doing this, you lose the current context value because it's in memory. If you intend to keep this behaviour, you should consider persisting the context value every time you change it and initialize it with the previously persisted one.
An example using the browser's local storage:
// Helper function to read the storaged value if it exists
function getPersistedValue() {
const serializedValue = localStorage.getItem('value')
try {
if (!serializedValue) {
throw new Error('No previously persisted value found')
}
return JSON.parse(serializedValue)
} catch {
return false
}
}
// Using the helper function to initialize the state
const [value, setValue] = useState(getPersistedValue())
// Synchronizing the persisted value on local storage with the in-memory one
useEffect(() => {
localStorage.setItem('value', JSON.stringify(value))
}, [value])
If you want, I forked your Code Sandbox and applied these changes: https://codesandbox.io/s/router-context-forked-uqhzye.

How to make a lazy loading high order component in React

So here is the main idea, HOC that is be able to load any wrapped in component with React.lazy and React.Suspense. Is it possible???
So, I already was able to write some, but not sure that I was able to made properly...
import React, { Suspense, lazy, useState, useEffect } from "react"
export function withLazyImport(LazyComponent) {
return (props) => {
const [loadedComponent, setLoadedComponent] = useState(null)
useEffect(() => {
setLoadedComponent(lazy(() => import(<LazyComponent {...props} />)))
//eslint-disable-next-line
}, [])
return (
<Suspense fallback="Lazy component is loading ...">
{loadedComponent}
</Suspense>
)
}
}
I don't understand why do you use useEffect. The resulted component will not pass new props to the lazy component because props are passed on did mount.
I came up with another solution based on the example provided by the author of this question
import React, { Suspense } from 'react';
export const withLazyComponent = (LazyComponent) => {
return (props) => (
<Suspense fallback="Lazy component is loading ...">
<LazyComponent {...props} />
</Suspense>
)
}
And then you use it like:
const LazyComponent = withLazyComponent(React.lazy(() => import('path/to/component')));
Here's how to achieve it in TypeScript
import { Loader } from "../components/loader";
import { Suspense } from "react";
/**
* HOC to wrap a component in a Suspense component.
*/
export default function withSuspense<P>(Component: React.ComponentType & any) {
return function WithSuspense(props: P) {
return (
<Suspense fallback={<Loader />}>
<Component {...props} />
</Suspense>
);
};
}
you can try using already existing solutions like Loadable Components

React useContext not triggering a re-render

I have set a three part Component for a filter menu. Component A) is the createContext which has an object with my globalData data and a function setData to change the data. When setData is triggered it retrieves data from the DB and updates the my globalData.data. Component B) Triggers component A and passes the appropriate values (This part works). Component C) is the useContext where it retrieves the data and users .Provider to display the data.
The problem is that Component C does not re-render any component.
Component A) Creates context and function to change context
import React, { createContext } from 'react';
import APIEndpoint from 'src/js/api/apAPIEndpoint.js';
const globalData={data:{kpi:[]}, filters:{date:'', login:'9166', country:[], city:[]}, setData:(field, value)=>{
globalData.filters = {...globalData.filters,[field]:value};
let fetchURL = `/APIURL?alias=${parseInt(globalData.filters.login)}&${globalData.filters.country.map((item)=>('country='+item.country)).join('&')}&${globalData.filters.city.map((item)=>('city='+item.city)).join('&')}`;
if(globalData.filters.login && globalData.filters.country.length>0 && globalData.filters.city.length>0){
APIEndpoint
.get(fetchURL)
.then(res => {
globalData.data={...globalData, data:res.data.data};
});
}
}
};
const GlobalDataContext = createContext(globalData);
export default GlobalDataContext;
Component B) Triggers context change in (A)
import React, {useContext} from 'react';
import GlobalDataContext from '/GlobalDataContext';
const globalData = useContext( GlobalDataContext );
const setGlobalData = globalData ? globalData.setData : null;
...
return(
<div>
<StyledSelect
onChange={(value) =>{
setGlobalData(props.valueField, value);
}} />
</div>
)
Component C) This is where it does not re-render. only want to re render one component under Section 2
import React, { useContext } from 'react';
import GlobalDataContext from '/GlobalDataContext';
const {data, setData} = useContext( GlobalDataContext );
...
return (
<>
<Helmet>
<title>Something - Home</title>
</Helmet>
<StencilResponsiveDesign sizes={[VIEWPORT_SIZES.S, VIEWPORT_SIZES.M]}>
...
{/* Section 1 */}
<FilterBar />
<Spacer height={50} />
{/* Section 2 */}
<GlobalDataContext.Provider value={globalData.data.kpi}>
<div>
<KpiWidget />
</div>
</GlobalDataContext.Provider>

Resources