Get the downloadURL for each file from firebase storage - javascript

I'm trying to get the downloadURL for each file from the firebase storage. Currently I'm displaying the metadata (in this case the name of each file).
const [fileNames, setFileNames] = useState([]);
useEffect(() => {
const getFiles = async () => {
const storage = await firebase.storage().ref("/recursos");
const list = await storage.listAll();
const items = list.items.map((ref) => ref.getMetadata());
const results = await Promise.all(items);
setFileNames(results);
};
getFiles();
}, []);
return (
<div className="home">
<SinapseNav />
<Container className="d-flex align-items-center justify-content-center mt-4 mb-4">
<Card className="home-card">
<Card.Body>
<h3 className="text-center mb-4">Pesquisar Recursos</h3>
<Form.Control placeholder='Ex: "Trigonometria"' />
{fileNames.map((file) => {
return (
<Container key={file.name}>
<ResourceCard title={file.name} />
</Container>
);
})}
</Card.Body>
</Card>
</Container>
</div>
);
}

As explained in the doc, you can call getDownloadUrl() on the items of the ListResult you got through listAll(). Exactly the same way than you do with getMetadata().
So the following should do the trick:
const getUrls = async () => {
const storage = firebase.storage().ref("/recursos");
const list = await storage.listAll();
const items = list.items.map((ref) => ref.getDownloadUrl());
const urls = await Promise.all(items);
// Do whatever you want with the urls Array
};
Note that listAll() has a default pagination size is 1000: It is actually a "helper method for calling list() repeatedly until there are no more results".

Related

How to get the first elements of an array and then get the next few elements?

I'm building my blog page for my website and I have a posts folder with markdown files of my blogs. I'm just figuring out a way to display all the blogs on a page, but I want to optimize it a bit so it doesn't try to load all blog posts at once but only the first 6 for example. And then when you click on a Load More button the next 6 get loaded and displayed.
This is the code I'm using to get the data from my blog posts:
async function getBlogPosts(n: number) {
const files = fs.readdirSync('posts');
const posts = files.slice(0, n).map((fileName) => {
const slug = fileName.replace('.md', '');
const readFile = fs.readFileSync(`posts/${fileName}`, 'utf-8');
const { data: frontmatter } = matter(readFile);
return {
slug,
frontmatter,
};
});
return posts;
}
And then display the title of the posts:
export default async function Blogs() {
const posts = await getBlogPosts(6);
return (
<div className="mx-auto flex">
{posts.map(({ slug, frontmatter }) => (
<div
key={slug}
className="m-2 flex flex-col overflow-hidden rounded-xl border border-gray-200 shadow-lg"
>
<Link href={`/blog/${slug}`}>
<h3 className="p-4">{frontmatter.title}</h3>
</Link>
</div>
))}
</div>
);
}
How would one go about implementing this?
Because I think if I were to call GetBlogPosts(12) it would load 12 posts but also the first 6 which have already been loaded.
You code is perfect.
Just implement pagination during slice method, increase page number as user clicks show more. and slice REQUIRED FILES data only.
Your code would look like this:
async function getBlogPosts(blogsPerPage:number, pageNumber:number) {
const files = fs.readdirSync('posts');
!IMPORTANT. sort files array here before slicing, so we wont get repeated posts...
const posts = files.slice((pageNumber-1)* blogsPerPage,pageNumber*blogsPerPage).map((fileName) => {
const slug = fileName.replace('.md', '');
const readFile = fs.readFileSync(`posts/${fileName}`, 'utf-8');
const { data: frontmatter } = matter(readFile);
return {
slug,
frontmatter,
};
});
return posts;
}
Now you can implement a state in your functional component and call this function somewhat like this:
export default async function Blogs() {
const [pageNumber, setPageNumber] = useState(0);
const posts = await getBlogPosts(6,pageNumber);
......
}
import { useState } from 'react';
export default function Blogs() {
const [numPosts, setNumPosts] = useState(6); // number of posts to display
const [posts, setPosts] = useState([]); // array of blog post data
const loadMore = () => {
setNumPosts(numPosts + 6); // increase the number of posts to display by 6
};
// fetch the blog post data when the component mounts or when the number of posts to display changes
useEffect(() => {
async function fetchData() {
const files = fs.readdirSync('posts');
const data = files.map((fileName) => {
const slug = fileName.replace('.md', '');
const readFile = fs.readFileSync(`posts/${fileName}`, 'utf-8');
const { data: frontmatter } = matter(readFile);
return {
slug,
frontmatter,
};
});
setPosts(data);
}
fetchData();
}, [numPosts]);
return (
<div className="mx-auto flex flex-wrap">
{posts.slice(0, numPosts).map(({ slug, frontmatter }) => (
<div
key={slug}
className="m-2 flex flex-col overflow-hidden rounded-xl border border-gray-200 shadow-lg"
>
<Link href={`/blog/${slug}`}>
<h3 className="p-4">{frontmatter.title}</h3>
</Link>
</div>
))}
{numPosts < posts.length && (
<button onClick={loadMore} className="my-4 mx-auto bg-blue-500 text-white px-4 py-2 rounded">
Load More
</button>
)}
</div>
);
}

React - I can't render the correct amount of items in from the map function

Image of the unwanted behaviourI'm using DaisyUi / Tailwindcss to render a Carousel and it's child item. The item are images that are fetched from a firebase storage.
The issue is when I try to map the image urls, which is a state of an array of strings, I only get 1 image instead of all the items.
There's 2 items in my storage and it's complete when I log it's content / arrays' length but the state(imageUrls) is not filled and it only shows 1 item.
Ask for any clarification.
Here is my code for my carousel component.
const MyCarousel = () => {
const [imageUrls, setImageUrls] = useState<string[]>([]);
const storage = useStorage();
const getImages = async () => {
const storageRef = ref(storage, 'Tech/');
const files = await listAll(storageRef);
const imageAppender: string[] = Array(files.items.length);
console.log('img', imageAppender.length);
files.items.forEach(async (item, key) => {
const imageRef = ref(storage, item.fullPath);
getDownloadURL(imageRef).then((url) => {
imageAppender[key] = url;
setImageUrls(imageAppender);
});
});
};
useEffect(() => {
getImages();
}, []);
return (
<>
<div className="carousel w-full">
{imageUrls.map((image, key) => {
return (
<div key={key} id={'item' + key} className="carousel-item w-full">
<img src={image} className="w-full" />
</div>
);
})}
</div>
<div className="flex justify-center w-full py-2 gap-2">
{imageUrls.map((_, key) => {
return (
<a key={key} href={'#item' + key} className="bth btn-ghost btn-circle text-center">
{key}
</a>
);
})}
</div>
</>
);
};
export default MyCarousel;
Problem is with the way you update your imageUrls state. You always pass same imageAppender in your setImageUrls, so in that case DOM is re-rendered only after first setImageUlrs state update.
files.items.forEach(async (item, key) => {
const imageRef = ref(storage, item.fullPath);
getDownloadURL(imageRef).then((url) => {
setImageUrls(currentUlrs => {
const updatedUrls = [...currentUlrs];
updatedAppender[key] = url;
return updatedAppender;
});
});
})
Please read useState functional updates - to get latest state on update.
So basically you need to pass new reference to new array (create updatedAppender) to trigger re-render.
In addition
It is better to not use forEach with async tasks. Please read this thread for alternatives What is the difference between async/await forEach and Promise.all + map

How to render data from an array created with "reduce()"

Hook and then map is my possible solution but i need to know how
Well I'm using react and firestore so, the data fetch is saved in a hook called "Asistencias"
well my target it was to get a group of weeks with the same number and collect data by that week, n i get it, but now i would like to render that data, so I need help.
this is the response with the function with reduce
export const Presupuesto = () => {
const auth = getAuth()
const dato =auth.currentUser;
const [Presupuestos, setPresupuesto] = useState([]);
const [Asistencias, setAsistencias] = useState([]);
const [itinerante, setItinerante] = useState([])
const getPresupuestos =async () => {
const q = query(collection(db, "asignaciones"),where("asistencias", "!=", [] ))
await onSnapshot(q, (query)=>{
const data=[]
query.forEach((doc)=>{
data.push(doc.data())
})
setPresupuesto(data)
}) }
useEffect(()=>{
getPresupuestos()
},[])
console.log("hook: ", Asistencias);
const AsistenciasPresupuesto = (props) => {
return props.reduce((past, current)=>{
const foundItem = past.find(it => it.semana === current.semana)
console.log('past:', past);
if (foundItem ){
foundItem.data=foundItem.data
?[...foundItem.data, {'trabajador': current.trabajador,'entrada':current.entrada, 'salida': current.salida}]
:[{ 'trabajador': current.trabajador,'entrada':current.entrada, 'salida': current.salida }]
}else{ past.push( {
'semana': current.semana,
'data': [{
'trabajador': current.trabajador,'entrada':current.entrada, 'salida': current.salida
}]
} ) }
return past;
}, [])}
AsistenciasPresupuesto(Asistencias)
return (
<Card>
<div className='presupuestos'>
{
Presupuestos.map((presupuesto)=>(
<Button variant="danger"
id={presupuesto.obra}
className={presupuesto.obra}
value={presupuesto.presupuesto}
onClick={
(e)=>{
e.preventDefault()
console.log("objeto completo:", presupuesto.asistencias)
setAsistencias(presupuesto.asistencias)
console.log("asistencias:", Asistencias)
}} > {presupuesto.presupuesto} </Button>))
}
</div>
<div>
<Card id="prueba" className='lg'>
{/*
i would like to render here! */}
</Card>
</div>
</Card>
)
}
this is my code
this is the render

Cannot read property 'map' of undefined [React.js]

so when i do my login and i try to redirect to this page it appears me that error it must be something with useEffect i dont know
here is my code
useEffect(() => {
let canUpdate = true;
getVets().then((result) => canUpdate && setVets(result));
return function cleanup() {
canUpdate = false;
};
}, []);
const getVets = async () => {
const url = 'http://localhost:8080/all/vet';
const response = await fetch(url);
const data = await response.json();
setVets(data);
};
// const { appointmentType, animalID, room, hora, notes } = this.state;
return (
<React.Fragment>
<div class='title'>
<h5>2 médicos vetenários disponíveis</h5>
</div>
<div>
{vets.map((data) => (
<ObterMedicos
key={data.name}
name={data.name}
specialty={data.specialty}
/>
))}
</div>
</React.Fragment>
);
}
vets might not have data on the first render and when the code tries to execute map operation on it, it gets undefined.map, which is not allowed.
You can either set vets to empty array at the time of defining the state
const [vets,setVets] = useState([]);
or just check on vets using Optional chaning (?) before using the map function:
{vets?.map((data) => (
<ObterMedicos
key={data.name}
name={data.name}
specialty={data.specialty}
/>
))}

Javascript - React Data from Async fetching is undefined using 'useEffect()' and 'useState'

I'm very new to react. I'm trying to fetch some data from API. I want to fetch a user. However, the user is undefined later when I want to process the user data. I'm confused about how the async function works.
I want to fetch a user, then pass the user to UserInfo component. In the UserInfo component, I want to process the data before I display it on the website. I got an error of "TypeError: Cannot read property 'split' of undefined".
Any help is greatly appreciated!!
App.js
function App() {
const classes = useStyles()
const [user, setUser] = useState({})
const [posts, setPosts] = useState([])
const [userList, setUserList] = useState([])
useEffect(() => {
const getUser = async () => {
const userFromServer = await fetchUser()
if (userFromServer) {
setUser(userFromServer)
} else {
console.log("error")
}
}
const getPosts = async () => {
const postsFromServer = await fetchPosts()
setPosts(postsFromServer)
}
const getUserList = async () => {
const userListFromServer = await fetchUserList()
setUserList(userListFromServer)
}
getUser()
getPosts()
getUserList()
}, [])
// Fetch user
const fetchUser = async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/users/1')
const data = await res.json()
return data
}
// Fetch posts
const fetchPosts = async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/posts?userId=1')
const data = await res.json()
return data
}
// Fetch list of users
const fetchUserList = async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/users/')
const data = await res.json()
return data
}
return (
<div>
<Box className={classes.headerImage}>
<UserMenu userList = {userList} />
</Box>
<Container maxWidth="lg" className={classes.userContainer}>
<UserInfo user = {user} />
</Container>
<Container maxWidth="lg" className={classes.blogsContainer}>
<PostList name = {user.name} posts = {posts} />
</Container>
</div>
);
}
export default App;
UserInfo.js
const classes = useStyles()
console.log(user.phone)
const phoneStr = user.phone.split(" ")[0]
// var companyStr = user.company.bs.replaceAll(" ", "· ")
// const addressDic = user.address
// const addressStr = addressDic.street + " " + addressDic.suite + " " + addressDic.city + " " +addressDic.zipcode
return (
<div>
<h2>{user.name}</h2>
<Paper elevation={0} className={classes.paper}>
<Grid container>
<Grid item xs={1}>
<img className={classes.icon} src={phoneIcon} />
</Grid>
<Grid item className={classes.gridText} xs={11}>
{phoneStr}
</Grid>
<Grid xs={1}>
<img className={classes.icon} src={categoryIcon} />
</Grid>
<Grid item className={classes.gridText} xs={11}>
</Grid>
<Grid item xs={1}>
<img className={classes.icon} src={shopIcon} />
</Grid>
<Grid item className={classes.gridText} xs={11}>
</Grid>
</Grid>
</Paper>
</div>
)
}
export default UserInfo
This is caused because the user info is not always available. While you wait the fetch user data the component tree continues to render. user.phone will be undefined and undefined.split() causes an error.
You can solve this by first checking for existence of user.phone before calling split.
user.phone && user.phone.split(" ")[0]
However this will only solve one line, you'll need to do this for every lookup in the User component which isn't great. It would be better to set the default state for user to null.
const [user, setUser] = useState(null)
And then only render the User component when user is not null
<Container maxWidth="lg" className={classes.userContainer}>
{ user ? <UserInfo user = {user} /> : 'loading...'}
</Container>
First, grab your call in a Promise.all, this way you are sure that all your calls are correctly executed before setting the states, in your useEffect:
useEffect(() => {
const getUser = async () => await fetchUser();
const getPosts = async () => await fetchPosts();
const getUserList = async () => await fetchUserList();
(async () => {
try {
const [response1, response2, response3] = await Promise.all(getUser(), getPosts(), getUserList());
userFromServer && setUser(response1);
setPosts(response2);
setUserList(response3);
} catch (err) {
throw new Error(`Something went wrong: ${err}`);
}
})();
}, []);
Then, in your return, wait until user data is set, something like:
{user?.phone && <UserInfo user={user} />}

Categories

Resources