I have a constant that receives a json with a fetch like this:
const [user, setUser] = useState([]);
async function users() { const result = await fetch (My API `,{
})
const data = await result.json();
setUser(data);`
and i wanted to show her data in another component, for example just the name or just the id.
I want to put only the name on this little blue block : this is the other component
more of the second component
The first component is the one where you obtain the data and the second the one you display it since your an receiving the user data by props
function FirstComponent(){
const [user, setUser] = useState([]);
return (
<SecondComponent user={user} />
)
}
function SecondComponent({user}){
console.log(user)
return (
//Here the code of the image you show
)
}
Related
I'm new to ReactJS and I'm now trying to do an interactive comments section (taken from frontendmentor.io), but the App component just doesn't show what it's supposed to show
This is my App component:
function App() {
const [data, setData] = useState([]);
useEffect(() => {
const getComm = async () => {
await fetchData();
};
getComm();
}, []);
console.log(data);
const fetchData = async () => {
const res = await fetch("db.json").then(async function (response) {
const comm = await response.json();
setData(comm);
return comm;
});
};
return (
<Fragment>
{data.length > 0 ? <Comments data={data} /> : "No Comments to Show"}
</Fragment>
);
}
export default App;
The console.log(data) logs two times:
the first time it's an empty Array;
the second time it's the Array with my datas inside.
As it follows:
If I force the App to print the Comments it just says that cannot map through an undefined variable
This is my Comments component:
function Comments({ data }) {
return (
<div>
{data.map((c) => (
<Comment key={c.id} />
))}
</div>
);
}
export default Comments;
I'm wondering why the page still displays No Comments to Show even if the log is correct
#Cristian-Irimiea Have right about response get from fetch. Response is an a object and can't be iterate. You need to store in state the comments from response
But you have multiple errors:
Take a look how use async function. Your function fetchData looks bad.
// Your function
const fetchData = async () => {
const res = await fetch("db.json").then(async function (response) {
const comm = await response.json();
setData(comm);
return comm;
});
};
// How can refactor
// fetchData function have responsibility to only fetch data and return a json
const fetchData = async () => {
const response = await fetch("./db.json");
const body = await response.json();
return body;
};
You are updating state inside fetch function but a good solution is update state then promise resolve:
useEffect(() => {
// here we use .then to get promise response and update state
fetchData().then((response) => setData(response.comments));
}, []);
The initial state of your data is an array.
After you fetch your data from the response you get an object. Changing state types is not a good practice. You should keep your data state as an array or as an object.
Considering you will keep it as an array, you need use an array inside of setData.
Ex.
comm && Array.isArray(comm.comments) && setData(comm.comments);
As for your Comments component you should consider expecting an array not an object.
Ex.
function Comments(data) {
return (
<div>
{data.map((c) => (
<Comment key={c.id} />
))}
</div>
);
}
export default Comments;
I have the following react code that pulls some data from an api and outputs it. In result['track_list'] I receive a list of tracks with a timestamp and in aggTrackList() I am aggregating the data into key value pair based on the day/month/year then displaying that aggregated data in a Card component I created.
import React, { useState, useEffect } from "react";
function App() {
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [trackList, settracks] = useState([]);
const [sortby, setSortby] = useState("day");
const [sortedList, setSortedList] = useState([]);
useEffect(() => {
aggTrackList();
}, [sortby]);
const aggTrackList = () => {
setSortedList([]);
let sortedObj = {};
switch (sortby) {
case "day":
trackList.forEach((track) => {
let dayVal = new Date(track[3]).toDateString();
dayVal in sortedObj
? sortedObj[dayVal].push(track)
: (sortedObj[dayVal] = [track]);
});
setSortedList(sortedObj);
break;
case "month":
trackList.forEach((track) => {
let monthVal = new Date(track[3]).toDateString().split(" ");
let monthYear = monthVal[1] + monthVal[3];
monthYear in sortedObj
? sortedObj[monthYear].push(track)
: (sortedObj[monthYear] = [track]);
});
setSortedList(sortedObj);
break;
case "year":
trackList.forEach((track) => {
let yearVal = new Date(track[3]).toDateString().split(" ");
let year = yearVal[3];
year in sortedObj
? sortedObj[year].push(track)
: (sortedObj[year] = [track]);
});
setSortedList(sortedObj);
break;
}
};
const getUserTracks = (username) => {
fetch(`http://localhost/my/api/${username}`, {
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
})
.then((res) => res.json())
.then(
(result) => {
settracks(result["tracks_played"]);
aggTrackList();
setIsLoaded(true);
},
(error) => {
console.log(error);
setIsLoaded(true);
setError(error);
}
);
};
return (
<div className="App">
<SortMenu
setSort={(selected) => {
setSortby(selected);
}}
/>
<UserForm onSubmit={getUserTracks} />
<div className="trackList">
{isLoaded ? (
Object.entries(sortedList).map(([day, track]) => (
<Card
className="card"
displayMode={sortby}
key={day}
timestamp={day}
content={track}
/>
))
) : (
<div>...</div>
)}
</div>
</div>
);
}
export default App;
The issue I am having is when UserForm is submitted and receives the data. The Card elements do not render unless I update the sortby state by clicking on one of the sortmenu options after the data has loaded. How can I get the data to show automatically after it has been loaded?
I'm creating this project to learn React so if something can be done better or if I am doing things wrong, please let me know.
Thanks.
Edit:
My code on codesandbox - https://codesandbox.io/s/fervent-minsky-bjko8?file=/src/App.js with sample data from my API.
You can do so in two ways:
In a blocking way using useLayoutEffect hook. Refer this.
In a non-blocking way using useEffect hook. Refer this.
1. useLayoutEffect
The thing to note here is that the function passed in the hook is executed first and the component is rendered.
Make the API call inside useLayoutEffect and then set the data once you obtain the response from the API. Where the data can initially be
const [data, setData] = useState(null)
The JSX must appropriately handle all the cases of different responses from the server.
2. useEffect
The thing to note here is that this function runs after the component has been rendered.
Make the API call inside the useEffect hook. Once the data is obtained, set the state variable accordingly.
Here your jsx can be something like
{
data === null ? (
<Loader />
) : data.length > 0 ? (
<Table data={data} />
) : (
<NoDataPlaceholder />
)
}
Here I am assuming the data is a list of objects. but appropriate conditions can be used for any other format. Here while the data is being fetched using the API call made inside useEffect, the user will see a loading animation. Once the data is obtained, the user will be shown the data. In case the data is empty, the user will be show appropriate placeholder message.
Filtering data by using this function, if I am calling this function in useEffect than its pushes to search results and not working well.
const AdvanceSearch = (props) => {
const [region, setRegion] = useState("");
const [searchStuhl, setSearchStuhl] = useState("");
const filterData = (async ()=> {
const filtereddata = await props.data.filter((item) => {
return (
item.region.toLowerCase().includes(region.toLowerCase())
&& item.stuhl.toLowerCase().includes(searchStuhl.toLowerCase())
)}
) await props.history.push({
pathname: '/searchResults/',
state:
{
data:filtereddata
}
})
})
//If the props. history.push is pass here instead of the above function then its sending the empty array and not the filtered data
const handleSubmit = async (e) => {
e.preventDefault()
await filterData();
}
when you are changing the navigation URL with some data and there is multiple rendering then the following problem would be there.
Check your route configuration for the path. is it configured to hold the changed path: in this scenario, you get fluctuated UI or we can say multiple renders
yes you can use useEffect hooks to change the path and set the data here is the peace of code. here whenever your props.data will be changed filteredData will run and it will return the value when data will be available.
const filteredData = useCallback(() => {
if(props.data){
const filteredData = props.data.filter((item) => (
item.region.toLowerCase().includes(region.toLowerCase())
&&item.stuhl.toLowerCase().includes(searchStuhl.toLowerCase())
));
return filteredData
}
},
[props && props.data]);
useEffect(()=> {
const data = filteredData();
if(data){
props.history.push({
pathname:'/search-results',
state:{data}
});
}
},[filteredData])
Try to remove async / await from the function. You don't need them to filter an array.
I have an async function that pulls data, in the form of a json, from an API. Each consecutive call is relying on what is previously returned. Sometimes when I try to access values pulled from an API, it received null since that has not fully loaded due to async function.
I tried to mitigate this with another useState() function but then it goes into a rendering loop.
The behavior I am looking for, is while the 3 sets of data are loading from the API, the CircularProgress is spinning. Once they are done loading, the page with information actually renders.
Load function
export function Load(url) {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchUrl() {
const response = await fetch(url);
const json = await response.json();
setData(json);
setLoading(false);
}
fetchUrl();
}, [url]);
// console.log(data)
return [data, loading];
}
main function
import React, { useState } from "react";
import CircularProgress from '#material-ui/core/CircularProgress';
const main (props) => {
const id = props.id
const [loading, setLoading] = useState(true)
const url1 = 'http://path/to/api'+id
const [data1, loading1] = Load(url1);
//const [data1, loading] = Load(url1) This works sometimes
const url2 = 'http://path/to/api2' + data1.val1
const [data2, loading2] = Load(url2)
const url3 = 'http://path/to/api3' + data2.val1
const [data3, loading3] = Load(url3)
const [loading, setLoading] = useState(false)
//if (loading1 === false){setLoading(false)}
Promise.all(data2).then(setLoading(false))
const GetValue = (props) => {
//do something with value
props.value=value
console.log(value)
}
return(<> {loading ? (
<Grid
container
spacing={0}
alignItems="center"
justify="center"
style={{ minHeight: '90vh' }}
>
<CircularProgress size="10vh" />
</Grid>
) : (
//render something
<GetValue props={data3}/>
)}
);
}
export default Main
I get this error:
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
If I just return loading in const [data1, loading] = DataLoader(url1);, and get rid of useState and Promise.all(), I get this error
Error: GetValue(...): Nothing was returned from render. This usually means a return statement is
missing. Or, to render nothing, return null.
I don't understand how GetValue is getting called because loading is still true.
The part
const [loading, setLoading] = useState(false)
//if (loading1 === false){setLoading(false)}
Promise.all(data2).then(setLoading(false))
doesn't make sense. data2 is not a promise (or an array of promises), and you're not passing a function to then() but are just synchronously calling setLoading every time the component is rerendered, which causes the infinite rendering loop.
What you want is a simple
const loading = loading1 || loading2 || loading3;
Also, you should ensure to skip the effect in your Load hook while the url is still undefined.
I have an app that fetches data from a movie API. It returns 20 items from page 1.
How would I go about adding the ability for pagination and allowing user to click a button that increases the page number value and returns the items from that page?
Here's my API call:
export const API_KEY = process.env.REACT_APP_MOVIES_API;
export const baseURL = 'https://api.themoviedb.org/3/movie/';
export const language = '&language=en';
export const region = '®ion=gb';
export const currentPage = 1;
export const fetchTopRatedMovies = async () => {
try {
const response = await fetch(
`${baseURL}top_rated?api_key=${API_KEY}${language}&page=${currentPage}${region}`
);
const data = await response.json();
console.log('TOP RATED', data);
return data;
} catch (err) {
console.log(err);
}
};
I'm thinking I need to add 1 to currentPage on request however I'm unsure how to set this up.
The function is called using useEffect in React in a functional component.
const [apiData, setApiData] = useState([]);
const [isLoading, setLoading] = useState(false);
const { results = [] } = apiData;
useEffect(() => {
setLoading(true);
fetchTopRatedMovies().then((data) => setApiData(data));
setLoading(false);
}, []);
You need to make currentPage a param of fetchTopRatedMovies, so that you can pass a dynamic value from your functional component.
You should control the current viewed page in the state of your functional component like this:
const [currentPage, setCurrentPage] = useState(1);
Then you can add a button to the rendering of the functional component that in the onClick handler sets the new value of currentPage and triggers the API call. Approximately like this:
<MyButton onClick={() => {
setCurrentPage(currentPage + 1);
fetchTopRatedMovies(currentPage).then(data => setApiData(data));
}}>
I say approximately because instead of doing immediately the call to fetchTopRatedMovies you could leverage useEffect to re-run the API request on each state / prop change. Or even better, trigger the API request using useEffect only when there's a meaningful state / prop change.
The fetchTopRatedMovies method should be improved like this:
export const fetchTopRatedMovies = async (pageNumber) => {
try {
const response = await fetch(
`${baseURL}top_rated?api_key=${API_KEY}${language}&page=${pageNumber}${region}`
);
const data = await response.json();
console.log('TOP RATED', data);
return data;
} catch (err) {
console.log(err);
}
};
This approach can be extended to all the other params of your API call.
Hope this helps!
Usually it's made by adding currentPage to the state, like
const [currentPage, setCurrentPage ] = useState(1);
So when you want to change it, via click or scroll, use setCurrentPage and in your api call it'll still use currentPage but now it'll reference the one in the state.