I connected my react app to firebase and I think the problem is that the page loads before the data from my database is acquired, what can I do to delay the function until after it finishes acquiring the data?
function getPosts(){
db.collection("Posts").get().then(snapshot =>{
snapshot.docs.forEach(docs =>{
createPost(
docs.data().postName,
docs.data().createdAt,
docs.data().postContent,
)
})
})
}
getPosts();
function Blog(){
return (
<div>
<Navbar/>
<div className="container">
<div className="row" id="posts-collection">
</div>
</div>
</div>
)
}
export default Blog;
As MehmetDemiray already shows you can load data as an effect within a function component, but that answer assumes you only wish to track loading status.
If you want to use the data loaded to display the post data then you will also need to store the returned data.
const Blog: React.FunctionComponent = () => {
// state to store data returned by async call. (originally set to null).
const [posts, setPosts] = React.useState(null);
// use an effect to load async data.
// the effect only runs on component load and every time the data in
// the dependency array changes "[setPosts]" (reference comparison).
React.useEffect(() => {
// Create funtion to run async logic to load posts.
const getPosts = () => {
// load posts
db.collection("Posts").get().then(snapshot => {
// map loaded posts to an array of easy to manage objects.
const loadedPosts = snapshot.docs.map(docs => {
return {
name: docs.data().postName,
createdAt: docs.data().createdAt,
content: docs.data().postContent,
}
});
// store loaded posts in state.
setPosts(loadedPosts ?? []);
});
};
// run async function created above.
getPosts();
}, [setPosts])
// posts will remain null until the async function has loaded data.
// you can manually track loading in a separate state if required.
if (posts === null) {
// Show loading view while loading.
return (
<div>
Loading Posts...
</div>
);
}
// map out posts view after posts have been loaded.
return (
<div>
{posts.map(post => (
<div>
<div>{post.postName}</div>
<div>{post.createdAt}</div>
<div>{post.content}</div>
</div>
))}
</div>
);
};
You need to loading control before rendering jsx. Looks like this;
import {useEffect, useState} from 'react';
function Blog(){
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
function getPosts(){
db.collection("Posts").get().then(snapshot =>{
snapshot.docs.forEach(docs =>{
createPost(
docs.data().postName,
docs.data().createdAt,
docs.data().postContent,
)
})
setLoading(false);
})
}
getPosts();
}, [])
return (
loading ?
<div>
< Navbar/>
<div className="container">
<div className="row" id="posts-collection">
</div>
</div>
</div> : null
)
}
export default Blog;
Related
I want to use useEffect(on mount) to fetch from API and store it in useState. Fetch API is used to get the data. The problem is when initial page loading and also when I reload the page, it outputs an error called test.map is not a function. Why this happening and how to avoid this ?
import { useEffect, useState } from 'react';
function App() {
const[test, setTest] = useState({})
useEffect(() => {
testfunc()
}, [])
async function testfunc(){
let api = await fetch('https://jsonplaceholder.typicode.com/users')
let apijson = await api.json()
setTest(apijson)
}
return (
<div className="App">
{
test.map((item) => {
return(
<div>
{item.name}
</div>
)
})
}
</div>
);
}
export default App;
You can't map on an object {}, so you should need to define an array [] for the base state :
const[test, setTest] = useState([])
You have to change {} to array first to be able to map over it. You can easily place ? after test like this. or make in the default value of the state a default value for item name. because this error results as you map over an empty object.
import { useEffect, useState } from 'react';
function App() {
const[test, setTest] = useState([{name:"default"}])
useEffect(() => {
testfunc()
}, [])
async function testfunc(){
let api = await fetch('https://jsonplaceholder.typicode.com/users')
let apijson = await api.json()
setTest(apijson)
}
return (
<div className="App">
{
test?.map((item) => {
return(
<div>
{item.name}
</div>
)
})
}
</div>
);
}
export default App;
As already mentioned, you can't use the .map for objects.
Instead of this, you can make something like that
Object.keys(test).map(key => {
const currentSmth = test[key]
return(
<div>
{currentSmth.name}
</div>
)
})
})
I think it helps you to solve your problem.
Be careful using the correct data structures and methods.
This is the structure of the json being fetched. I am trying to render some of the nested threads data to a web page with react.
import react, {useState, useEffect} from "react";
import axios from 'axios'
import ReactJson from 'react-json-view'
const FeaturedBoards = () => {
const [boards, setBoards] = useState([{page: '', threads: {}}]);
useEffect(() => {
fetchBoards();
}, []);
const fetchBoards = () => {
axios.get('https://a.4cdn.org/po/catalog.json')
.then((res) => {
console.log(res.data);
setBoards(res.data);
})
.catch((err) => {
console.log(err);
});
};
if(boards === 0) {
return <div>Loading...</div>;
}
else{
return (
<div>
<h1>Featured Boards</h1>
<div className='item-container'>
{boards.map((board) => (
<div className='board' key={board.id}>
<p>{board['threads']}</p>
</div>
))}
</div>
</div>
);
}
};
export default FeaturedBoards;
I have tried everything to display some of the nested threads data but nothing comes up. I've tried doing a second call to map on board but no luck, storing it in a variable and calling from that still nothing. Am I doing something totally wrong?
I believe this is more fully answered by How can I access and process nested objects, arrays or JSON?. but to explain for this particular data structure, keep reading.
Look at your actual data... boards is an array. Each element in it is an object with page (int) and threads (array) properties. Each threads array element is an object with other properties. You can use map to iterate arrays and return a JSX representation of the objects within.
For example
const [boards, setBoards] = useState([]); // start with an empty array
const [loading, setLoading] = useState(true)
useEffect(() => {
fetchBoards().then(() => setLoading(false))
}, []);
const fetchBoards = async () => {
const { data } = await axios.get('https://a.4cdn.org/po/catalog.json')
setBoards(data)
}
return loading ? <div>Loading...</div> : (
<div>
<h1>Featured Boards</h1>
<div className="item-container">
{boards.map(board => (
<div className="board" key={board.page}> <!-- 👈 note "page", not "id" -->
{board.threads.map(thread => (
<p>{thread.name}</p>
<p>{thread.sub}</p>
<p>{thread.com}</p>
<!-- etc -->
))}
</div>
))}
</div>
</div>
)
I am using react app. I fetched one data from open api. in that api the ingredient in one single string but divided by \n1. When I fetched the data it came like this one single string and the \n1 shows like (, divided). I wanted to put the ingredients in the ul li elements or and comma-a after each ingredient. I tried lots of ways to split the data and also tried to add a comma after each word but it did not work. I shared my code in codesandbox.
This is my code
import React, { useEffect } from "react";
import "./styles.css";
export default function App() {
const [state, setState] = React.useState([]);
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
const response = await fetch("https://sampleapis.com/recipes/api/recipes");
const data = await response.json();
setState(data);
};
return (
<div className="App">
{state.map((recipe) => {
return (
<>
<div key={recipe.id}>
<h1>{recipe.title}</h1>
<p>{recipe.ingredients}</p>
</div>
</>
);
})}
</div>
);
}
try to split and mapping them in separate html elements
<div className="App">
{state.map((recipe) => {
return (
<div key={recipe.id}>
<h1>{recipe.title}</h1>
<div>{recipe.ingredients?.split("\n").map(ingre=><p>{ingre}</p>)}</div>
</div>
);
})}
</div>
I found solution and did it like this:
const ingre = newRecipe.ingredients // this is the data
var result = ingre.split(',')
.map(word => `${word.trim()}`)
.join(', ');
I've retrieved a list of categories using an API. Now I want to fetch images from an URL based on the categories. I tried using each category to fetch images from another API, but I'm not sure how to do it.
import React, { useEffect, useState } from 'react';
import './css/Category.css';
function Category() {
useEffect(() => {
fetchData();
getImage();
}, []);
const [categories, setCategories] = useState([]);
const [image, setImage] = useState('');
const fetchData = async () => {
const data = await fetch('https://opentdb.com/api_category.php')
const categories = await data.json();
console.log(categories.trivia_categories)
setCategories(categories.trivia_categories)
}
const getImage = async (name) => {
console.log(name)
const q = name.split(' ').join('+')
const img = await fetch(`https://pixabay.com/api/?key=apikey&q=${q}&image_type=photo`)
const image = await img.json();
console.log(image)
setImage(image.previewURL)
}
return (
<div className="categories">
Yesss
<div className="category-grid">
{categories.map(category => (
<div className="category">
{category.name}
<img src={getImage(category.name)} /> //do not know what to do here to fetch image of the respective category
</div>
))}
</div>
</div>
)
}
export default Category;
After changes suggested by Noah, I was able to show only one image.
const getImage = async (name) => {
const query = stringMan(name.name)
console.log(query)
const img = await fetch(`https://pixabay.com/api/?key=17160673-fd37d255ded620179ba954ce0&q=${query}&image_type=photo`)
const image = await img.json();
console.log(image)
setImage({ [name.name]: image.hits[0].largeImageURL })
}
return (
<div className="categories">
Yesss
<div className="category-grid">
{categories.map(category => (
<div className="category" key={category.id}>
{category.name}
<img key={category.id} src={image[category.name]} />
</div>
))}
</div>
</div>
)
There are a couple of changes that you can make here.
One issue that I see is that you have a single image variable, that's being re-used for every single category. So when you map over a list of categories (for example let's say we have categories: [history, science, and math]). The current code will call getImage three times, with history, science, and math as parameters.
However, there is only one state variable that is being written to. Which means the last execution of setImage is the only one that will be preserved.
So, you might want to change image from being the URL of a category image, to an object that has the shape:
{
history: [url],
science: [url],
math: [url]
}
The other change to make is that you are calling the getImage() function directly in the rendered output <img src={getImage(category.name)} />. Instead, this should simply use the value that was assigned to the image state: <img src={image} />.
To actually fetch the image, you can use the useEffect hook (https://reactjs.org/docs/hooks-effect.html) to react to changes to the categories variable. That might look something like:
useEffect(() => {
categories.forEach((c) => getImage(c));
}, [categories]);
The useEffect hook will invoke the function it is given, whenever the dependencies change. This will allow you to trigger the getImage function in response to changes to the categories.
There're lot of improvement that could be done as stated by #noah-callaway above/below but coming straight to the point you need to simply fix the URI creation logic to use encodeURIComponent like below:
import React, { useEffect, useState } from 'react';
function Category() {
useEffect(() => {
fetchData();
getImage();
}, []);
const [categories, setCategories] = useState([]);
const [image, setImage] = useState('');
const fetchData = async () => {
const data = await fetch('https://opentdb.com/api_category.php')
const categories = await data.json();
console.log(categories.trivia_categories)
setCategories(categories.trivia_categories)
}
const getImage = async (name) => {
return encodeURI(`https://pixabay.com/api/?key=apikey&q=${encodeURIComponent(name)}&image_type=photo`)
}
return (
<div className="categories">
Yesss
<div className="category-grid">
{categories.map(category => (
<div className="category">
{category.name}
<img src={getImage(category.name)} />
</div>
))}
</div>
</div>
)
}
don't have the api key so can't test but it'll give you something like
https://pixabay.com/api/?key=apikey&q=Entertainment%3A%20Comics&image_type=photo
good luck, hope it works.
I'm trying to add react lazy in my application, and for some reason, it doesn't seem to work.
The component in which I want the lazy load to work on, fetches its data from a server, then it renders the data. The problem is, the component in which the data is getting fetched, which is in the suspense tag, gets loaded before the data actually loads. Here's my code:
AnotherTest.jsx
const AnotherTest = () => {
const [toDoListData, setToDoListData] = useState([]);
useEffect(() => {
async function fetchData() {
setTimeout(async () => {
const result = await axios.get(`/api/ToDos/filter/completed`);
setToDoListData(result.data);
}, 5000);
}
fetchData();
}, []);
if (!toDoListData.length) return null;
return (
<div>
{toDoListData.map(item => {
return <div>{item.name}</div>;
})}
</div>
);
};
Test.jsx
import React, { lazy, Suspense } from 'react';
const AnotherTest = React.lazy(() => import('./AnotherTest'));
const Testing = () => {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<AnotherTest />
</Suspense>
</div>
);
};
The only way I know of that's currently possible is by using fetch-suspense.
Read this for a complete article on how he did it.
This would turn your component into
const AnotherTest = () => {
const toDoListData = useFetch('/api/ToDos/filter/completed', { method: 'GET' });
return (
<div>
{toDoListData.map(item => {
return <div>{item.name}</div>;
})}
</div>
);
};
If for some reason the fetch-suspense package does not suit your needs the only way is to show a loader in the AnotherTest component itself while fetching the data.
Note that the lazy function is meant for code-splitting and thus lazy loading the JS file, not waiting for anything async in the component itself.
(There is also react-cache but they advice not to use it in real world applications.)