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>
);
}
I need to create a link which opens more than one route in a new tab when I click on the "Box" component. I'm using Redux
That's the template I'm following, but I'm stucked cause it seems it is not persisting state (which contains authentication datas) opening in a new tabs.
const onClickBox = () => {
onClickPageOne();
onClickPageTwo();
}
const onClickPageOne = () => {
dispatch(actions.fetchDataPageOne)
}
const onClickPageTwo = () => {
dispatch(actions.fetchDataPageTwo)
}
return (
<>
<Box
onClick = {onClickBox }
>
Click me!
</Box>
</>
)
I tried using Link component by react-router-dom but I need only one link for both pages so I don't know what to put in 'to' prop.
I tried using Redirect in onClickBox function but I don't get how to tell him to open in a new page.
You can use single function to dispatch bothh actions from there only
Trying to render data from the CoinGekco API in my React component. It works on first render but if I leave the page or refresh, coin.market_data is undefined. I also tried passing coin to the useEffect() dependency array and that didn't work.
import React, { useEffect, useState } from "react";
import axios from "../utils/axios";
import CoinDetail from "./CoinDetail";
function CoinPagePage() {
const [coin, setCoin] = useState({});
useEffect(() => {
const getCoin = () => {
const coinid = window.location.pathname.split("/").splice(2).toString();
axios
.get(`/coins/${coinid}`)
.then((res) => {
setCoin(res.data);
console.log(res.data);
})
.catch((error) => console.log(error));
};
getCoin();
}, []);
return (
<div>
<CoinDetail current_price={coin.market_data.current_price.usd} />
</div>
);
}
export default CoinPagePage;
The GET request only happens when rendering the parent page. Re-rendering the child component will not run the fetch code again. Instead of passing current_price as a prop to your <CoinDetail> component, you could try passing coinid and doing the fetch inside your detail page.
That way, when the page is refreshed, the request will be executed again.
Edit
If you try to access a not existing property on an object, your application will crash. What you could do to prevent this from happening is checking if the request is done, before trying to access the property.
One way you could do this by setting the initial state value to null
const [coin, setCoin] = useState(null);
Then, above the main return, you could check if the value is null, if it is, return some sort of loading screen
if(coin === null) return <LoadingScreen />;
// main render
return (
<div>
<CoinDetail current_price={coin.market_data.current_price.usd} />
</div>
);
This way, when the fetch is done, the state gets updated and the page will re-render and show the updated content.
I have a component that shows no of players logged in as soon as the component loads.
function PlayerList({gamePin}){
const [playerList, setPlayerList] = useState("");
useEffect( ()=>{
axios.get("http://localhost:8080/game/lobby/"+gamePin)
.then( response =>{
setPlayerList(response.data)
})
})
return(
<div className='container'>
<div className='lobbycontainer'>
<h1>Lobby</h1>
<Grid container spacing={3}>
{playerList.map(player=>{
<Player {PlayerName,PlayerId} />
})}
</Grid>
</div>
</div>
)}
export default PlayerList;
This will display the name of the player who is logged in and any other players already logged into the lobby.
But my question is how do the players who are already logged in will get to know about the new players who joined.
Possible Approach
Send a request with a time interval of every 2 seconds.
setInterval(httpRequest,2000);
Is this the right way to do this? are there any alternate approaches?
How does a component dynamically update its state based on the changes in the backend? and respond to the changes by rerendering the component to reflect the changes.
That is pretty close. Use a "componentDidMount" useEffect hook patter, i.e. provide an empty dependency array ([]). Refactor the GET request into a callback function invoked on an interval and don't forget to return an effect cleanup function to clear the interval when this component unmounts.
useEffect(() => {
const timerId = setInterval(() => {
axios.get("http://localhost:8080/game/lobby/" + gamePin)
.then(response => setPlayerList(response.data))
}, 2000);
return () => clearInterval(timerId);
}, []);
I've got really weird problem. I mean, to me, really. This is not some kind of "use setState instead of this.state" problem. I'm working on trip planning App. I'm using ContextAPI which provides login information and user data (logged user trips etc ) to the whole app.
"Schedule" component, showing day-by-day trip schedule is context subscriber.
Now I want to delete one of the trip days (Day, also subscribing to the context is component rendered by Schedule Component on the Schedule list). And then magic happens:
when I do it right after logging in to the app, everything is working fine. But when I'll refresh the page and delete another day, context state (which as I said, holds all the data, including Days on schedule list) changes (checked via developer tools) but Schedule component is not re-rendering itself, so deleted element remains visible.
But as I said, state changes, and everything is working fine without page refresh before deleting operation. Also when I switch to another tab (Schedule is just one of the tabs, I've got for example Costs and Notes tab on the navbar rendering Costs and Notes components), and then go back to the Schedule tab it re-renders and everything is looking fine, component is showing info accordingly to context state.
Code works like this: deletion (context function ) is triggered by delete icon click in Day.js (context subscriber), Day.js is rendered by Schedule.js (context subscriber too).
In Context.js
... context functions ...
//WRAPPER FOR DELETE FUNCTIONS
delete(url, deleteFunc) {
this.requestsApi.request(url, "DELETE", null)
.then(response => {
if(response.status===204) {
deleteFunc();
} else {
this.toggleRequestError(true);
}
})
.catch( err => {
console.log(err);
this.toggleRequestError(true);
});
}
deleteDay = (id, tripId) => {
let newTrip = this.state.userData.trips.find((trip => trip.id ===
tripId));
newTrip.days = newTrip.days.filter(day => day.id !== id)
this.updateTrip(newTrip);
}
updateTrip = (newTrip) => {
let trips = this.state.userData.trips;
this.setState(prevState => ({
userData : {
...prevState.userData,
trips: trips.map(trip => {
if(trip.id !== newTrip.id)
return trip;
else
return newTrip;
})
}
}
));
}
...
In Schedule.js
... in render's return ...
<ul>
{this.trip.days.map((day,index) => {
return <DayWithContext
inOverview={false}
key={index}
number={index}
day={day}
tripId={this.trip.id}
/>
})}
</ul>
....
In Day.js (delete icon)
...
<img
src={Delete}
alt="Delete icon"
className="mr-2"
style={this.icon}
onClick={() => {
this.props.context.delete(
`/days/${this.props.day.id}`,
() => this.props.context.deleteDay(this.props.day.id,
this.props.tripId)
);
}}
/>
...
Anyone is having any idea what's going on and why Schedule can be not re-rendered after deletion done after page refresh? And even if context state changes? Only idea I have for now is that this is some kind of a bug...
//UPDATE
In Schedule component, in function componentWillReceiveProps() I console log props.context.trip[0].day[0] - first day. It's not an object, just plain text, so its not evaluated "as I click on it in the console".
Before I delete it, console logs first item. After I delete it, console logs second item (so now it's first item on the list) so props given to Schedule are changing, but no render is triggered... What the hell is going on there?
//UPDATE 2
I've also noticed that when I switch to Schedule component from another component, it works good (for example I'm refreshing the page on /costs endpoint then click Schedule tab on navbar menu which leads to /schedule). But when I refresh the page when on /schedule endpoint, this "bug" occurs and component is not re-rendering.
//Render from Context:
...
render() {
const {
isLoggedIn,
wasLoginChecked,
userData,
isLogoutSuccesfulActive,
isLogoutUnSuccesfulActive,
isDataErrorActive,
isRequestErrorActive,
isDataLoaded
} = this.state;
const context = {
isLoggedIn,
wasLoginChecked,
isLogoutSuccesfulActive,
isLogoutUnSuccesfulActive,
isDataErrorActive,
isRequestErrorActive,
userData,
isDataLoaded,
requestsApi : this.requestsApi,
toggleLogin : this.toggleLogin,
checkLogin: this.checkLogin,
setUserData: this.setUserData,
loadData: this.loadData,
addTrip: this.addTrip,
delete: this.delete,
deleteDay: this.deleteDay,
deleteActivity: this.deleteActivity,
deleteTrip: this.deleteTrip,
updateTrip: this.updateTrip,
uncheckLogin: this.uncheckLogin,
toggleLogoutSuccesful: this.toggleLogoutSuccesful,
toggleLogoutUnSuccesful: this.toggleLogoutUnSuccesful,
toggleRequestError: this.toggleRequestError,
toggleDataError: this.toggleDataError
};
return (
<Context.Provider value={context}>
{this.props.children}
</Context.Provider>
)
}
}
export const Consumer = Context.Consumer;
-------------Context HOC:
export default function withContext(Component) {
return function ContextComponent(props) {
return (
<Context.Consumer>
{context => <Component {...props} context={context} />}
</Context.Consumer>
);
}
}
You mutate your state.
In this place, you change an object in the state without using setState:
newTrip.days = newTrip.days.filter(day => day.id !== id)
And then in your map function, you always return the same objects (it is already updated).
So, to fix your issue you can try to change your deleteDay method:
deleteDay = (id, tripId) => {
const trip = this.state.userData.trips.find((trip => trip.id ===
tripId));
const newTrip = {...trip, days: trip.days.filter(day => day.id !== id)};
this.updateTrip(newTrip);
}
UPDATE:
You have one more issue in your Schedule component.
You need to get current trip dynamically, don't set it in the constructor:
Try this:
constructor(props) {
super(props);
this.getTrip = this.getTrip.bind(this);
}
getTrip() {
return this.props.context.userData.trips.find(trip => {
return `${trip.id}`===this.props.match.params.id;
});
}
render() {
const trip = this.getTrip();
return (
...
<ul>
{trip.days.map((day,index) => {
return <DayWithContext
inOverview={false}
key={day.id}
number={index}
day={day}
tripId={trip.id}
/>
})}
</ul>
...
)
}