In React applications, where do you put the networking code. I have seen code as shown below. But in this case, the request is right there in the component and cannot be reused. I know I can use Redux but the products array is not meant to be shared globally with other components.
function App() {
const [products, setProducts] = useState([])
useEffect(() => {
const getProducts = async () => {
const response = await fetch(`someurl.com/products`)
const products = await response.json()
setProducts(products)
}
getProducts()
}, [])
const productItems = products.map(product => {
return <li key = {product.id}>{product.title}</li>
})
return (
<ul>
{productItems}
</ul>
);
}
I think, there is a lot of ways, of app architecture design for handling this scenario, depending of app scale.
The simpiest way to reuse and incapsulate such behaviour would be custom hook:
const getProducts = async () => {
const response = await fetch(`someurl.com/products`)
const products = await response.json()
return products
}
export const useProducts = () =>
{
const [products, setProducts] = useState([])
useEffect(() => {
getProducts().then(setProducts)
}, [])
return products
}
function App() {
const products = useProducts()
const productItems = products.map(product => {
return <li key = {product.id}>{product.title}</li>
})
return (
<ul>
{productItems}
</ul>
);
}
Related
I'm trying to learn ReactJS..
Today I was trying to create an array of objects with fetch results and after that create the cards, but I just can update the state but the cards are not re-render.. can you help me?
App.js
const teamsForLoop = [
Team1,
Team2,
Team3
];
const [allPlayers, setAllPlayers] = useState([]);
const [team, setTeam] = useState([]);
const [allTeams] = useState(teamsForLoop);
const [loading, setLoading] = useState(true);
useEffect(() => {
const playerInfo = async() => {
setLoading(true)
allTeams.map(async(teamArray) => {
setTeam([])
teamArray.map(async (player) => {
let playerName = player.split(" ");
const result = await axios.get(
`https://www.thesportsdb.com/api/v1/json/2/searchplayers.php?p=${playerName[0]}%20${playerName[1]}`
);
if (result.data.player === null) {
setTeam((state) => {
return [...state];
});
} else {
setTeam((state) => {
return [...state, result.data.player[0]];
});
}
});
setAllPlayers(team);
});
setLoading(false);
};
playerInfo();
},[]);
if (loading) return "...Loading...";
return (
<>
<PlayerList allPlayers={allPlayers} />
</>
);
}
export default App;
PlayerList.js
function PlayerList({ allPlayers }) {
const myData = []
.concat(allPlayers)
.sort((a, b) => (a.idTeam !== b.idTeam ? 1 : -1))
return (
<div>
{myData.map((player,index) =>
(
<div key={index}>
...........
</div>
)
)}
</div>
);
}
I think my problem was on the useEffect hook or maybe on my fetch function..
I already have done it using just arrays but without state.
Issue
The issue I see now is that you are attempting to cache the fetched players in the team state in the loops and then use the team state to update the players state. The problem here is that React state updates are asynchronously processed, so team hasn't updated when setAllPlayers(team); is called.
Solution
It would be simpler to map the allTeams arrays to the GET requests, wait for them to resolve, and enqueue a single allPlayers state update. Flatten the arrays of team's players and map these to the axios GET Promise. Wait for these to resolve and map the results to the array of players.
Example:
function App() {
const [allPlayers, setAllPlayers] = useState([]);
const [allTeams] = useState(teamsForLoop);
const [loading, setLoading] = useState(true);
const playerInfo = async () => {
setLoading(true);
const response = await Promise.all(
allTeams
.flat()
.map((player) =>
axios.get(
`https://www.thesportsdb.com/api/v1/json/2/searchplayers.php?p=${player}`
)
)
);
const players = response.map((result) => result.data.player[0]);
setAllPlayers(players);
setLoading(false);
};
useEffect(() => {
playerInfo();
}, []);
if (loading) return "...Loading...";
return <PlayerList allPlayers={allPlayers} />;
}
I am new to using React with Firebase and I am struggling to return the data that I have in firebase. I have a collection called "users" and multiple documents inside with auto-generated IDs. I also have 3 fields in each document, fullname, email and id. This is the code I am using to fetch the documents:
function App() {
const db = firebase.firestore();
const [users, setUsers] = useState([])
const fetchUsers = async () => {
const response = db.collection('users');
const data = await response.get();
data.docs.forEach(item => {
setUsers([...users, item.data()])
})
}
useEffect(() => {
fetchUsers();
}, [])
return (
<div>
{
users && users.map(user => {
return (
<div key={user.id}>
<div>
<h4>{user.fullname}</h4>
<p>{user.email}</p>
</div>
</div>
)
})
}
</div>
);
}
In the console, it is returning all of the documents in individual arrays but on the webpage, it is only returning the last document. Is there a way to return all of the documents? Any help would be appreciated, thank you.
On your fetchUsers function you need to pass in a function with the previous state.
const fetchUsers = async () => {
const response = db.collection('users');
const data = await response.get();
data.docs.forEach(item => {
setUsers((prevState)=>{return ({[...prevState, item.data()]})})
})
}
Hey guys I've been learning react for a few weeks now so please be easy on me =). When I was using dummy data, the filter function worked and showed the correct products in the category. I built the back end api using django and now my filter function doesn't work anymore. It does filter but the data totally disappears after pressing the different filter buttons. Can anyone help?
import React, { useState, useEffect } from "react";
import axios from "axios";
import ButtonList from "../components/ButtonList";
import ProductList from "../components/ProductList";
const ProductPage = () => {
const [products, setProducts] = useState([]);
useEffect(() => {
const fetchProduct = async () => {
const { data } = await axios.get("/api/products/");
setProducts(data);
};
fetchProduct();
}, []);
const filter = (button) => {
if (button === "All") {
setProducts(products);
return;
}
const filteredData = products.filter(
(products) => products.category === button
);
setProducts(filteredData);
};
return (
<div>
<ButtonList onClickFilter={filter} />
<ProductList product={products} />
</div>
);
};
export default ProductPage;
You are losing the original list of products as your setting filtered data in it. So, currently there is no way to get the original products list back.
To fix it, you can set search in a state and use that to filter the products. This way original data is always present in products but filtered data is used for rendering the list:
const ProductPage = () => {
const [products, setProducts] = useState([])
const [search, setSearch] = useState('ALL') // New State for search
// ...
const filter = (button) => {
setSearch(button)
}
return (
<div>
<ButtonList onClickFilter={filter} />
<ProductList
product={products.filter((p) => search === 'ALL' || p.category === search)}
/>
</div>
)
}
Right now, after filtering, you're losing the full products array information permanently, since it only exists in the stateful products variable that setProducts will essentially overwrite. Add another state, one which contains the full products, and filter off of it instead.
const ProductPage = () => {
const [fullProducts, setFullProducts] = useState([]);
const [products, setProducts] = useState([]);
useEffect(() => {
const fetchProduct = async () => {
const { data } = await axios.get("/api/products/");
setFullProducts(data);
};
fetchProduct();
}, []);
const filter = (button) => {
if (button === "All") {
setProducts(fullProducts);
return;
}
const filteredData = fullProducts.filter(
(product) => product.category === button
);
setProducts(filteredData);
};
return (
<div>
<ButtonList onClickFilter={filter} />
<ProductList product={products} />
</div>
);
};
I need some help understanding why I'm getting the error from the title: 'TypeError: Cannot read property 'map' of undefined'. I need to render on the page (e.g state & country here) some data from the API, but for some reason is not working.
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const APIFetch = () => {
const [user, setUser] = useState('');
const [info, setInfo] = useState([]);
const fetchData = async () => {
const data = await axios.get('https://randomuser.me/api');
return JSON.stringify(data);
}
useEffect(() => {
fetchData().then((res) => {
setUser(res)
setInfo(res.results);
})
}, [])
const getName = user => {
const { state, country } = user;
return `${state} ${country}`
}
return (
<div>
{info.map((info, id) => {
return <div key={id}>{getName(info)}</div>
})}
</div>
)
}
Can you guys provide me some help? Thanks.
Try this approach,
const APIFetch = () => {
const [user, setUser] = useState("");
const [info, setInfo] = useState([]);
const fetchData = async () => {
const data = await axios.get("https://randomuser.me/api");
return data; <--- Heres is the first mistake
};
useEffect(() => {
fetchData().then((res) => {
setUser(res);
setInfo(res.data.results);
});
}, []);
const getName = (user) => {
const { state, country } = user.location; <--- Access location from the user
return `${state} ${country}`;
};
return (
<div>
{info.map((info, id) => {
return <div key={id}>{getName(info)}</div>;
})}
</div>
);
};
Return data without stringify inside the fetchData.
Access user.location inside getName.
Code base - https://codesandbox.io/s/sharp-hawking-6v858?file=/src/App.js
You do not need to JSON.stringify(data);
const fetchData = async () => {
const data = await axios.get('https://randomuser.me/api');
return data.data
}
Do it like that
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const APIFetch = () => {
const [user, setUser] = useState('');
const [info, setInfo] = useState([]);
useEffect(() => {
const fetchData = async () => {
const res = await axios.get('https://randomuser.me/api');
setUser(res.data);
setInfo(res.data.results);
}
featchData();
}, [])
const getName = user => {
const { state, country } = user;
return `${state} ${country}`
}
return (
<div>
{info.map((info, id) => {
return <div key={id}>{getName(info)}</div>
})}
</div>
)
}
Codesandbox: https://codesandbox.io/s/vigorous-lake-w52vj?file=/src/App.js
Similar questions have been asked but I haven't found a solution for this particular one. I have one component which renders all boards and I am using a custom useFetch hook to fetch all boards.
const BoardsDashboard = () => {
let [boards, setBoards] = useState([]);
const { response } = useFetch(routes.BOARDS_INDEX_URL, {});
setBoards(response);
return (
<main className="dashboard">
<section className="board-group">
<header>
<div className="board-section-logo">
<span className="person-logo"></span>
</div>
<h2>Personal Boards</h2>
</header>
<ul className="dashboard-board-tiles">
{boards.map(board => (
<BoardTile title={board.title} id={board.id} key={board.id} />
))}
<CreateBoardTile />
</ul>
</section>
</main>
);
};
const useFetch = (url, options) => {
const [response, setResponse] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url, options);
const json = await res.json();
setResponse(json);
} catch (error) {
setError(error);
}
};
fetchData();
}, []);
return { response, error };
};
I am getting too many re-renders due to setBoards(response) line. What is the right way to handle this?
Thanks!
Sounds like you might want a useEffect hook to take action when response is updated.
useEffect(() => {
setBoards(response);
}, [response]);
Note: if you have no need to ever change the boards state, then maybe it doesn’t need to be stateful at all, you could just use the returned value from your useFetch hook and be done with it.
const { response: boards } = useFetch(...);