Here Below my code I would like to retrieve all data before starting the render of my component, is there any way to do that in react ? I guess it's maybe a simple code line but as I'm new in coding I still don't know all react components behavior. Thanks for your answer.
import { useState, useEffect } from "react";
import axios from "axios";
import Cookies from "js-cookie";
// import material ui
import CircularProgress from "#mui/material/CircularProgress";
import Box from "#mui/material/Box";
// import config file
import { SERVER_URL } from "../../configEnv";
const Products = ({ catList }) => {
// catList is data coming from app.js file in format Array[objects...]
console.log("catList ==>", catList);
const [isLoading, setIsLoading] = useState(true);
const [dataSku, setDataSku] = useState([]);
console.log("datasku ==>", dataSku);
const tab = [];
useEffect(() => {
// Based on the catList tab I fetch additionnal data linked with each object of catList array
catList.slice(0, 2).forEach(async (element) => {
const { data } = await axios.post(`${SERVER_URL}/products`, {
product_skus: element.product_skus,
});
// The result I receive from the call is an array of objects that I push inside the Tab variable
tab.push({ name: element.name, content: data });
setDataSku(tab);
console.log("tab ==>", tab);
setIsLoading(false);
});
}, [catList]);
return isLoading ? (
<Box sx={{ display: "flex" }}>
{console.log("there")}
<CircularProgress />
</Box>
) : (
<div className="products-container">
<div>LEFT BAR</div>
<div>
{dataSku.map((elem) => {
return (
<div>
<h2>{elem.name}</h2>
</div>
);
})}
</div>
</div>
);
};
export default Products; ```
#Jessy use your loading state to fetch data once,
In your useEffect, check for loading,
useEffect(() => {
if(loading) {
catList.slice(0, 2).forEach(async (element) => {
const { data } = await axios.post(`${SERVER_URL}/products`, {
product_skus: element.product_skus,
});
tab.push({ name: element.name, content: data });
setDataSku(tab);
console.log("tab ==>", tab);
setIsLoading(false);
});
}
}, [catList]);`
I finally managed to displayed all results by adding this condition on the isLoading
if (tab.length === catList.length) {
setIsLoading(false);
}
Many thanks guys for your insight :)
Related
This is my Json file which I created in my app.
export const Data = [
{
id: 1,
title: "Tilte 1",
description: "Decription 1 Data",
},
{
id: 2,
title: "Tilte 2",
description: "Decription 2 Data",
}
];
This is my main file from where I navigate it. I use json file to display all the records on page. When I click on selected item it will get its id and navigate to another page, where i can get the data of selected item coming from json.
import React from "react";
import { Data } from "./JSON"
import { useNavigate } from 'react-router-dom'
const Home = () => {
let naviagte = useNavigate();
return (
<>
{Data.map((data, key) => {
return (
<div class="card" >
<div class="card-body">
<h5 class="card-title" key={key.id}>{data.title}</h5>
<p class="card-text">{data.description}</p>
<button onClick={() => naviagte(`/service/${data.id}`)}>{data.title} </button>
</div>
</div>
);
})}
</>
)
}
export default Home;
When I navigate to another page where I want to display all data regarding the selected id. It shows only id not all data.
import React, {useState, useEffect} from "react";
import { Data } from "../home/JSON"
import { useParams } from "react-router-dom";
const Service = () => {
const { id } = useParams();
const [data, setData] =useState('');
console.log("check", data);
useEffect(() => {
setData (Data.map((_data) => _data.id === id ))
}, [id])
return(
<>
{id}
{data.title}
{data.description}
</>
)
}
export default Service;
Please guide me what I miss here. Thanks in Advance
Since you are importing the data in both places you just need to find the data by the id property instead of mapping it to booleans. Keep in mind that your id property is a number but the id route param will be a string, so you will need to convert them to a compatible type for the strict equality (===) check.
Example:
useEffect(() => {
setData(Data.find((_data) => String(_data.id) === id));
}, [id]);
Since data is treated as an object in the render return you'll want to insure you maintain a valid state invariant. Update the initial data state to be an object, and check that Array.prototype.find returned a defined object from the Data array before updating state.
const Service = () => {
const { id } = useParams();
const [data, setData] = useState({});
console.log("check", data);
useEffect(() => {
const data = Data.find((_data) => String(_data.id) === id);
if (data) {
setData(data);
}
}, [id]);
return (
<>
{id}
{data.title}
{data.description}
</>
);
};
When I make a request to an API and setting the state to the results from the Axios request it still shows up null. I am using React useState and setting the results from the request and wanting to check to see if its coming through correctly and getting the right data its still resulting into null. The request is correct but when I use .then() to set the state that is the issue I am having.
Below is the component that I am building to make the request called Details.js (first code block) and the child component is the DetailInfo.js file (second code block) that will be displaying the data. What am I missing exactly or could do better when making the request and setting the state correctly display the data?
import React, {useEffect, useState} from 'react';
import { Col, Container, Row } from 'react-bootstrap';
import axios from 'axios';
import { getCookie } from '../utils/util';
import DetailInfo from '../components/DetailInfo';
import DetailImage from '../components/DetailImage';
const Details = () => {
const [ countryData, setCountryData ] = useState(null);
let country;
let queryURL = `https://restcountries.eu/rest/v2/name/`;
useEffect(() => {
country = getCookie('title');
console.log(country);
queryURL += country;
console.log(queryURL);
axios.get(queryURL)
.then((res) => {
console.log(res.data[0])
setCountryData(res.data[0]);
})
.then(() => {
console.log(countryData)
}
);
}, [])
return (
<>
<Container className="details">
<Row>
<Col sm={6}>
<DetailImage />
</Col>
<Col sm={6}>
<DetailInfo
name={countryData.name}
population={countryData.population}
region={countryData.region}
subRegion={countryData.subRegion}
capital={countryData.capital}
topLevelDomain={countryData.topLevelDomain}
currencies={countryData.currencies}
language={countryData.language}
/>
</Col>
</Row>
</Container>
</>
)
}
export default Details;
The child component below......
import React from 'react';
const DetailInfo = (props) => {
const {name, population, region, subRegion, capital, topLevelDomain, currencies, language} = props;
return (
<>detail info{name}{population} {region} {capital} {subRegion} {topLevelDomain} {currencies} {language}</>
)
}
export default DetailInfo;
Ultimately, the problem comes down to not handling the intermediate states of your component.
For components that show remote data, you start out in a "loading" or "pending" state. In this state, you show a message to the user saying that it's loading, show a Spinner (or other throbber), or simply hide the component. Once the data is retrieved, you then update your state with the new data. If it failed, you then update your state with information about the error.
const [ dataInfo, setDataInfo ] = useState(/* default dataInfo: */ {
status: "loading",
data: null,
error: null
});
useEffect(() => {
let unsubscribed = false;
fetchData()
.then((response) => {
if (unsubscribed) return; // unsubscribed? do nothing.
setDataInfo({
status: "fetched",
data: response.data,
error: null
});
})
.catch((err) => {
if (unsubscribed) return; // unsubscribed? do nothing.
console.error('Failed to fetch remote data: ', err);
setDataInfo({
status: "error",
data: null,
error: err
});
});
return () => unsubscribed = true;
}, []);
switch (dataInfo.status) {
case "loading":
return null; // hides component
case "error":
return (
<div class="error">
Failed to retrieve data: {dataInfo.error.message}
</div>
);
}
// render data using dataInfo.data
return (
/* ... */
);
If this looks like a lot of boiler plate, there are useAsyncEffect implementations like #react-hook/async and use-async-effect that handle it for you, reducing the above code to just:
import {useAsyncEffect} from '#react-hook/async'
/* ... */
const {status, error, value} = useAsyncEffect(() => {
return fetchData()
.then((response) => response.data);
}, []);
switch (status) {
case "loading":
return null; // hides component
case "error":
return (
<div class="error">
Failed to retrieve data: {error.message}
</div>
);
}
// render data using value
return (
/* ... */
);
Because state only update when component re-render. So you should put console.log into useEffect to check the new value:
useEffect(() => {
country = getCookie('title');
console.log(country);
queryURL += country;
console.log(queryURL);
axios.get(queryURL).then(res => {
console.log(res.data[0]);
setCountryData(res.data[0]);
});
}, []);
useEffect(() => {
console.log(countryData);
}, [countryData]);
useState does reflecting its change immediately.
I think that it would be probably solved if you set countryData to second argument of useEffect.
useEffect(() => {
country = getCookie('title');
console.log(country);
queryURL += country;
console.log(queryURL);
axios.get(queryURL)
.then((res) => {
console.log(res.data[0])
setCountryData(res.data[0]);
})
.then(() => {
console.log(countryData)
}
);
}, [countryData])
The issue is, as samthecodingman, pointed out, an issue of intermediate data. Your component is being rendered before the data is available, so your child component needs to re-render when its props change. This can be done via optional chaining, an ES6 feature.
import React, { useEffect, useState } from "react";
import DetailInfo from "./DetailInfo";
import { Col, Container, Row } from "react-bootstrap";
import axios from "axios";
const Details = () => {
const [countryData, setCountryData] = useState({});
let country = "USA";
let queryURL = `https://restcountries.eu/rest/v2/name/`;
useEffect(() => {
console.log(country);
queryURL += country;
console.log(queryURL);
axios
.get(queryURL)
.then((res) => {
console.log(res.data[0]);
setCountryData(res.data[0]);
})
.then(() => {
console.log(countryData);
});
}, []);
return (
<Container className="details">
<Row>
<Col sm={6}>
<DetailInfo
name={countryData?.name}
population={countryData?.population}
region={countryData?.region}
subRegion={countryData?.subRegion}
capital={countryData?.capital}
language={countryData?.language}
/>
</Col>
<Col sm={6}></Col>
</Row>
</Container>
);
};
export default Details;
Checkout my Codesandbox here for an example.
I don't understand why the second line, which reads data from the props, is not displayed as instantly as the first, i would like them to be displayed instantly
I update the state when a button is clicked, which calls api, data is coming in, the state is updating, but the second line requires an additional press to display
How to display both lines at once after a call? What's my mistake?
I'm using react hooks, and i know that required to use useEffect for re-render component, i know, that how do work asynchronous call,but i'm a little confused, how can i solve my problem, maybe i need to use 'useDeep effect' so that watching my object properties, or i don't understand at all how to use 'useEffect' in my situation, or even my api call incorrectly?
I have tried many different solution methods, for instance using Promise.all, waiting for a response and only then update the state
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./test";
ReactDOM.render(<App />, document.getElementById("root"));
app.js
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
const useDataApi = (initialState) => {
const [state, setState] = useState(initialState);
const stateCopy = [...state];
const setDate = (number, value) => {
setState(() => {
stateCopy[number].date = value;
return stateCopy;
});
};
const setInfo = async () => {
stateCopy.map((item, index) =>
getFetch(item.steamId).then((res) => setDate(index, res.Date))
);
};
const getFetch = async (id) => {
if (id === "") return;
const requestID = await fetch(`https://api.covid19api.com/summary`);
const responseJSON = await requestID.json();
console.log(responseJSON);
const result = await responseJSON;
return result;
};
return { state, setState, setInfo };
};
const Children = ({ data }) => {
return (
<>
<ul>
{data.map((item) => (
<li key={item.id}>
{item.date ? item.date : "Not data"}
<br></br>
</li>
))}
</ul>
</>
);
};
const InfoUsers = ({ number, steamid, change }) => {
return (
<>
<input
value={steamid}
numb={number}
onChange={(e) => change(number, e.target.value)}
/>
</>
);
};
function App() {
const usersProfiles = [
{ date: "", id: 1 },
{ date: "", id: 2 }
];
const profiles = useDataApi(usersProfiles);
return (
<div>
<InfoUsers number={0} change={profiles.setID} />
<InfoUsers number={1} change={profiles.setID} />
<button onClick={() => profiles.setInfo()}>Get</button>
<Children data={profiles.state} loading={profiles} />
</div>
);
}
export default App;
To get the data, just click GET
In this example, completely removed useEffect, maybe i don’t understand how to use it correctly.
P.s: Sorry for bad english
You don't need stateCopy, as you have it in the callback of the setState:
const setInfo = async () => {
// we want to update the component only once
const results = await Promise.all(
state.map(item => getFetch(item.steamId))
);
// 's' is the current state
setState(s =>
results.map((res, index) => ({ ...s[index], date: res.Date })
);
};
Im having troubles rendering components based on api calls in React. I fetch my data in useEffect hook update a state with the data. The state is null for a while before the api get all the data but by that time, the components are rendering with null values. This is what I have:
import React, { useEffect, useState } from 'react'
import axios from 'axios';
import { Redirect } from 'react-router-dom';
const Poll = (props) => {
const [poll, setPoll] = useState(null);
//if found is 0 not loaded, 1 is found, 2 is not found err
const [found, setFound] = useState(0);
useEffect(() => {
axios.get(`api/poll/${props.match.params.id}`)
.then(res => {
console.log(res.data);
setPoll(res.data);
setFound(1);
})
.catch(err => {
console.log(err.message);
setFound(2);
});
}, [])
if(found===2) {
return(
<Redirect to="/" push />
)
}else{
console.log(poll)
return (
<div>
</div>
)
}
}
export default Poll
That is my workaround but it doesnt feel like thats the way it should be done. How can I set it so that I wait for my api data to get back then render components accordingly?
You don't need to track the state of the API call like const [found, setFound] = useState(1). Just check if poll exists and also you can create a new state variable for tracking the error.
For example if (!poll) { return <div>Loading...</div>} this will render a div with 'loading...' when there is no data. See the code below, for complete solution,
import React, { useEffect, useState } from 'react'
import axios from 'axios';
import { Redirect } from 'react-router-dom';
const Poll = (props) => {
const [poll, setPoll] = useState(null);
const [hasError, setHasError] = useState(false);
useEffect(() => {
axios.get(`api/poll/${props.match.params.id}`)
.then(res => {
console.log(res.data);
setPoll(res.data);
})
.catch(err => {
console.log(err.message);
setHasError(true)
});
}, [])
if(!poll) {
console.log('data is still loading')
return(
<div>Loading....</div>
)
}
if (hasError) {
console.log('error when fetching data');
return (
<Redirect to="/" push />
)
}
return (
<div>
{
poll && <div>/* The JSX you want to display for the poll*/</div>
}
</div>
);
}
export default Poll
In your than, try to use a filter:
setPoll(poll.filter(poll => poll.id !== id));
Make sure to replace id by your identificator
The standard way is to have other variables for the loading and error states like this
const Poll = (props) => {
const [poll, setPoll] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
useEffect(() => {
setLoading(true);
axios.get(`api/poll/${props.match.params.id}`)
.then(res => {
console.log(res.data);
setPoll(res.data);
})
.catch(err => {
console.log(err.message);
setError(true);
})
.finally(()=> {
setLoading(false);
};
}, [])
if(error) return <span>error<span/>
if(loading) return <span>loading<span/>
return (
<div>
// your poll data
</div>
)
}
I'm new in using React Context and Hooks in my project. Currently I'm facing a problem when my item in the component doesn't display on the screen when it initially load but the item does display on the screen when I clicked on some button.
I did do some debugging using console.logand in the console, it did shows my data, but on the screen, it shows nothing. The weird part is, when I clicked on any button on the screen, it finally show something on the screen.
Here is my code, in the OrderContext, I get all my data from Firestore.
//OrderContextProvider.js
import React, { createContext, useState, useEffect } from "react";
import Firebase from "../component/misc/firebase";
export const OrderContext = createContext();
const OrderContextProvider = props => {
const [userLocation, setUserLocation] = useState({
shop: "XXXXXXX"
});
const [food] = useState([]);
useEffect(() => {
userLocation.shop != null
? Firebase.firestore()
.collection("restaurants")
.doc(userLocation.shop)
.collection("foods")
.get()
.then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
food.push(doc.data());
});
})
.catch(function(error) {
console.log("Error getting documents: ", error);
})
: console.log("User location unknown, please insert the location!");
}, [food]);
return (
<OrderContext.Provider value={{ userLocation, food }}>
{props.children}
</OrderContext.Provider>
);
};
export default OrderContextProvider;
and in Foods component, I tried get food from OrderContext and display <Foods/> in <Home/>
//foods.js
import React, { useEffect, useContext } from "react";
import { OrderContext } from "../../context/OrderContextProvider";
import { Grid } from "#material-ui/core";
const Foods = () => {
const { food } = useContext(OrderContext);
useEffect(() => {
console.log(food);
}, []);
return food.map((foods, index) => {
return (
<Grid key={index} item xs={6} sm={6} md={3} xl={3} className="food">
{foods.name}
</Grid>
);
});
};
export default Foods;
//foods.js
<Grid container className="container">
<Foods/>
</Grid>
May I know what is my mistake or what I missed out here?
Sorry for my bad English and thanks for reading
It's still unclear as to where/how you are using the OrderContextProvider component. But, one possible problem I see in the code is the way you are updating the "food" array. The way you are pushing items into it won't necessarily trigger the updates since you are not updating the state. You might want to do something like -
const OrderContextProvider = props => {
....
const [food, setFood] = useState([]);
useEffect(() => {
...
.then(function(querySnapshot) {
let newFoods = [];
querySnapshot.forEach(function(doc) {
newFoods.push(doc.data());
});
setFood([...food, ...newFoods]);
})
...
});
...
};
const food = [];
useEffect(() => {
userLocation.shop != null
? Firebase.firestore()
.collection("restaurants")
.doc(userLocation.shop)
.collection("foods")
.get()
.then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
food.push(doc.data());
});
})
.catch(function(error) {
console.log("Error getting documents: ", error);
})
: console.log("User location unknown, please insert the location!");
},[food]);
You have not passed second argument to the useEffect that tells the hook to run for the first time it loads.
I guess this is the reason why it's not showing the data on the first load.