I have created an app that consists of a loading screen. However, if the API is failed after 5 seconds, a failure message is to be conveyed. I am getting too-many re-renders error on the line which I have mentioned in the code below.
I have used setTimeout function to replace the message of failure if API fails after 5 seconds of loading the page. The rest of my app functionality is working fine.
My app code is: -
function App() {
//Is website loaded for first time?
const [loadingFirstTime, setLoadingFirstTime] = useState(true);
//Has the site loading failed? If yes, pass that to loading component
const [loadingFailed, setLoadingFailed] = useState(false);
//To be run first time the website is loaded
useEffect(() => {
getMovies()
.then(res => setMoviesDetails(res))
.then(() => setLoadingFirstTime(false));
}, [])
................... some code here .............
//If the details of api haven't been loaded or api loading failed
if (Object.keys(moviesDetails).length === 0 && loadingFirstTime) {
//------**Error occurs here after 5 seconds as per the console**-----------------
//check for the same thing after 5 seconds, if initial data still has been loaded?
setTimeout(() => {
if (Object.keys(moviesDetails).length === 0 && loadingFirstTime) {
setLoadingFailed(true);
}
}, 5000);
return (
<LoadingScreen status={loadingFailed} />
)
}
return (
<>
........ App components which are working fine .............
</>
);
}
Code for my loading component: -
function LoadingScreen({status}) {
const [loadingText, setLoadingText] = useState("Loading the site...");
//check if site loading failed and display appropiate message
if (status) {
setLoadingText("Website loading failed. Please reload or contact the administrator.");
}
return (
<div className="loading-screen">
<h1>{loadingText}</h1>
</div>
)
}
In React, you should avoid changing states when rendering, which is what you are doing in your LoadingScreen component when you are setting loadingText.
setLoadingText("Website loading failed. Please reload or contact the administrator.");
This is happening all the time you are passing a truthy value to status. That line is making LoadingScreen component to re-render itself again and again in an infinite loop.
Generally, it is better to implement this feature inside a useEffect function like this one:
function LoadingScreen({ status }) {
const [loadingText, setLoadingText] = useState("Loading the site...");
useEffect(() => {
if (status) {
const newLoadingText =
"Website loading failed. Please reload or contact the administrator.";
if (newLoadingText !== loadingText) {
// This state setter provokes a re-render so set only if new value is
// different than the actual value stored in state
setLoadingText(newLoadingText);
}
}
}, [loadingText, status]);
return (
<div className="loading-screen">
<h1>{loadingText}</h1>
</div>
);
}
Related
I'm writing a website. It's a website that uses and fetches data from a stock API to displays stock prices and charts.
I have a search bar which changes state whenever a letter is typed in.. However, it's causing problems because it's instantly updating the state, which then fetches data from the API, and if for example I type in Z - then the API is instalty looking for a stock named "Z"
and the app crashes, goes blank ( because such link doesnt exist ). I literally have to copy the stock name like "AAPL" for example and then paste it - for the app to work properly.
var baseUrl = `https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=${search}&apikey=**********`;
because state of "search" is updated and it's searching for that stock/
Anyways, here's my stock search bar component.
const StockSearchBar = () => {
const { search, setSearch } = useContext(SearchContext); // i'm using context API to store state so I can access it across different components
const handleClick = (e) => {
if (setSearch !== '') { // if there's something in state, then post data to backend
const options =
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({search}),
};
const response = fetch(`http://localhost:3001/api/search`, options);
};
function handleButtonEvents(e) { // i'm using a function to chain handleClick() and setTimeout() to be run when button is clicked
e.preventDefault();
handleClick();
setTimeout(() => window.location.reload(), 3000); // delay the function for 3 seconds to give it time to fetch the data and display it, otherwise it might refresh the page without updating it with new data
}
const handleSearch = e => {
e.preventDefault();
setSearch(e.target.value.toUpperCase()); // i think the problem is here - i need to delay setting state
}
return (
<>
<div className="searchBar">
<label>Look for Stocks or Cryptos: </label>
<input type='text' onChange={handleSearch} onKeyPress={(ev) => {
if (ev.key === "Enter") { handleButtonEvents(); }}}
{/* search if Enter key is pressed */} // i think the problem is with **onChange** or the handleSearch **function** but not sure how to deal with it
placeholder={search} required />
<Button variant="success" type="submit" onClick={handleButtonEvents} >Search</Button>
</div>
<h1 className="src">{search}</h1>
{// display state to see if it's changing ( just testing }
</>
);
};
export default StockSearchBar;
Let me know if you want to see the context API file, or something else. But I think the probem is in the search bar component.
Thank you in advance for help.
you need to use Debounce in order to create a delay e.g. from lodash. so in your case it would be
const handleSearch = e => {
e.preventDefault();
debounce(setSearch(e.target.value.toUpperCase()), 500)// debounce 500 milliseconds
}
so i have a bit of a weird problem i dont know how to solve.
In my code i have a custom hook with a bunch of functionality for a fetching a list
of train journeys. I have some useEffects to that keeps loading in new journeys untill the last journey of the day.
When i change route, while it is still loading in new journeys. I get the "changes to unmounted component" React error.
I understand that i get this error because the component is doing an async fetch that finishes after i've gone to a new page.
The problem i can't figure out is HOW do i prevent it from doing that? the "unmounted" error always occur on one of the 4 lines listed in the code snippet.
Mock of the code:
const [loading, setLoading] = useState(true);
const [journeys, setJourneys] = useState([]);
const [hasLaterDepartures, setHasLaterDepartures] = useState(true);
const getJourneys = async (date, journeys) => {
setLoading(true);
setHasLaterDepartures(true);
const selectedDateJourneys = await fetchJourney(date); // Fetch that returns 0-3 journeys
if (condition1) setHasLaterDepartures(false); // trying to update unmounted component
if (condition2) {
if (condition3) {
setJourneys(something1); // trying to update unmounted component
} else {
setJourneys(something2) // trying to update unmounted component
}
} else {
setJourneys(something3); // trying to update unmounted component
}
};
// useEffects for continous loading of journeys.
useEffect(() => {
if (!hasLaterDepartures) setLoading(false);
}, [hasLaterDepartures]);
useEffect(() => {
if (hasLaterDepartures && journeys.length > 0) {
const latestStart = ... // just a date
if (latestStart.addMinutes(5).isSameDay(latestStart)) {
getJourneys(latestStart.addMinutes(5), journeys);
} else {
setLoading(false);
}
}
}, [journeys]);
I can't use a variable like isMounted = true in the useEffect beacuse it would reach inside the if statement and reach a "setState" by the time i'm on another page.
Moving the entire call into a useEffect doesn't seem to work either. I am at a loss.
Create a variable called mounted with useRef, initialised as true. Then add an effect to set mounted.current to false when the component unmounts.
You can use mounted.current anywhere inside the component to see if it's mounted, and check that before setting any state.
useRef gives you a variable you can mutate but which doesn't cause a rerender.
When you use useEffect hook with action which can be done after component change you should also take care about clean effect when needed. Maybe example help you, also check this page.
useEffect(() => {
let isClosed = false
const fetchData = async () => {
const data = await response.json()
if ( !isClosed ) {
setState( data )
}
};
fetchData()
return () => {
isClosed = true
};
}, []);
In your use case, you probably want to create a Store that doesn't reload everytime you change route (client side).
Example of a store using useContext();
const MyStoreContext = createContext()
export function useMyStore() {
const context = useContext(MyStoreContext)
if (!context && typeof window !== 'undefined') {
throw new Error(`useMyStore must be used within a MyStoreContext`)
}
return context
}
export function MyStoreProvider(props) {
const [ myState, setMyState ] = useState()
//....whatever codes u doing with ur hook.
const exampleCustomFunction = () => {
return myState
}
const getAllRoutes = async (mydestination) => {
return await getAllMyRoutesFromApi(mydestination)
}
// you return all your "getter" and "setter" in value props so you can use them outside the store.
return <MyStoreContext.Provider value={{ myState, setMyState, exampleCustomFunction, getAllRoutes }}>{props.children}</MyStoreContext.Provider>
}
You will wrap the store around your entire App, e.g.
<MyStoreProvider>
<App />
</MyStoreProvider>
In your page where you want to use your hook, you can do
const { myState, setMyState, exampleCustomFunction, getAllRoutes } = useMyStore()
const onClick = async () => getAllRouters(mydestination)
Considering if you have client side routing (not server side), this doesn't get reloaded every time you change your route.
I make an request in react js and i get data using useSelector. So in useEffect i show the message from backend after the request.
useEffect(() => {
if (selector.response ? .message) {
console.log(selector);
message.success(selector.response?.message, 2);
setLoading(false);
}
console.log('render');
}, [selector.response]);
The code works fine, but appears an issue when i change the page(route). Clicking on another menu item i go to another page and when i come back, the useEffect is triggered again and user again sees the message from message.success(selector.response?.message, 2);. Question: How to stop showing the message each time after i come back to my route and to show the message just one time?
You have to use User Effects with Cleanup
You need to clean up the effects from the previous render or actions. Otherwise it will hold the previous state
You will find more details in official tutorial
userEffect Cleanup Process
Update Answer
import React, {useState, useEffect} from "react";
export default function App() {
const [count, setCount] = useState(0);
useEffect(() => {
function handleChange(val) {
setCount(val)
}
window.addEventListener('load', handleChange(1))
return () =>{
handleChange(0);
console.log(count)
};
})
return (
<div className="App">
{count}
</div>
);
}
Here I take a count variable and setCount methods to set the count variable
Inside useEffect I create a function which will be responsible for setting up count value
Then I create an addEventListner when the page will load it will set the count value to 1
Then I call an anonymous function for clean up things which will remove the previously set value to 0
After that I set a console to just check if its sets the value
So when user come back to your page again initially it will find the default value then set the dynamic value.
You can make it more efficient way. I just give you an example
Below I share a link which will help you to handle api response
How to call api and cleanup response using react usereffect hooks
It seems like in your case your component unmounts upon page change and remounts when the user comes back. When the componet remounts, the useEffect always fires. That's simply how it works.
Answering your question, this is a simple thing you can do for your case.
// this will create a function that will return true only once
const createShouldShowSuccessMessage = () => {
let hasShownMessage = false;
()=> {
if(hasShownMessage) {
return false;
} else {
hasShownMessage = true;
return true;
}
}
};
const shouldShowSuccessMessage = createShouldShowSuccessMessage();
const SomeComponent = () => {
useEffect(() => {
if(shouldShowSuccessMessage()) {
message.success(selector.response?.message, 2);
}
setLoading(false);
}, [selector.response]);
}
I would advise you got with a better setup that performing your side effects inside your components but I hope my answer helps you till you get there
I'm facing a weird situation ...
The setState sometimes worked fine, but sometimes will get the warning for setting state to the unmounted component.
If the user's cart is undefined, then call the API to get the cart information and set the state.
const loginData = () => {
const isUnmounted = useRef(false);
const handleData = () => {
if (isUnmounted.current) return false;
const setData = (user) => {
//setCart failed sometimes, because of setting State to the unmount component
if (cart == undefined) getUserData((response) => setCart(response.cart));
}
if (userData !== null) {
setUserData(user);
}
}
useEffect(() => {
handleData();
return () => {
isUnmounted.current = true;
}
}, [])
}
When I enter the page contain this component, it will sometimes success to work.
And the other time the page crashed because cart can't be set with the unmounted component (will get the warning in console), and the further cart.id will crash the process.
Looks like it just happened randomly...
Sometimes the process crash when I just enter the page,
but sometimes I can enter the page and refresh the page multiple times then the page crashed ...
Have no idea what is happened. May anyone give me some suggestions? Thanks!
I am developing React + Redux single page application. I have a table with documents in page and I need to refresh data every 20 seconds. There are two functions in javascript: setTimeout and setInterval. I guess I can't use setInterval, because it just call function after some period of time. In my case I need to call function and wait for response (request processing in backend takes some time). So I used setTimeout and wrote this component (now it's simplified):
import {connect} from 'react-redux';
const { DATA_REFRESH_TIMEOUT, RETRY_REFRESH_TIMEOUT } = __ENVIRONMENT_CONFIG__;
#connect(
(state) => ({
documents: state.documents.documents,
loadingDocuments: state.documents.loading
}),
(dispatch) => bindActionCreators(
{
dispatchLoadDocuments: loadDocuments
},
dispatch
)
)
export default class Dashboard extends Component {
documentasTimeoutId;
constructor(props) {
super(props);
this.state = {
documentType: null
};
}
....
handleDocumentTypeChange = (event, documentType) => {
//If document type was changed I must to abort current timer
//and get documents with particular type immediately
this.setState({documentType: documentType});
this.clearTimeoutAndGetDocuments(documentType);
};
getDocuments = (documentType) => {
//Here I am checking for document loading phase
//If it is loading, we will wait and repeat loading after short time
const{ loadingDocuments } = this.props;
if(!loadingDocuments) {
this.props.dispatchLoadDocuments(documentType);
} else {
this.documentasTimeoutId = setTimeout(() => { this.getDocuments(documentType); }, RETRY_REFRESH_TIMEOUT);
}
};
clearTimeoutAndGetDocuments = (documentType) => {
//Abort delayed data getting and get data immediately
clearTimeout(this.documentasTimeoutId);
this.getDocuments(documentType);
};
componentDidMount(){
//Load documents on start up
this.props.dispatchLoadDocuments();
}
componentWillReceiveProps(newProps) {
//Here I trying to get event when documents loaded
let areDocumentsJustLoaded = this.props.loadingDocuments && !newProps.loadingDocuments;
if(areDocumentsJustLoaded) {
//If they loaded, I am setting timeout to refresh documents after some time
this.documentasTimeoutId = setTimeout(() => { this.getOutstandingFailures(this.state.search); }, DATA_REFRESH_TIMEOUT);
}
}
render() {
const {columns, documents} = this.props;
return (
//.....
<DataTable
columns={columns}
source={documents}
title="Documents"
name="documents"
emptyMessage="No data"/>
//....
);
}
}
As you can see I'm getting documents and loadingDocuments from reducer. I put documents to my DataTable, and by loadingDocuments changes I can define when data loading completed.
It's working, but I'am not sure for correct react and redux using (I am a newbie in React/Redux). Maybe there a better approach to do same actions? Maybe we can somehow create a separate component for this purpose and reuse it in other pages?