I am trying to read the data from a Firebase Firestore collection called 'posts'. Its having few documents in it. When I am using the following code to read data, I am able to read it but two times:
code in posts.jsx file:
import React, { useEffect, useState } from "react";
import '../index.css';
import '../../node_modules/antd/dist/antd.min.css';
import PostSnippet from './PostSnippet';
import _ from 'lodash';
import { PageHeader } from "antd";
import { db } from '../firebase.js';
import { collection, getDocs } from "firebase/firestore";
function Posts(props) {
const [posts, setPosts] = useState([]);
useEffect(() => {
const fetchData = async () => {
const postRef = collection(db, 'posts');
const postSnap = await getDocs(postRef);
postSnap.forEach(doc => {
let data = doc.data()
let { id } = doc
let payload = {
id,
...data,
}
setPosts((posts) => [...posts, payload])
})
}
fetchData()
.catch(console.error);
}, [])
return (
<div className="posts_container">
<div className="page_header_container">
<PageHeader
style={{
border: '5px solid rgb(235, 237, 240)',
fontSize: '25px',
margin: '40px',
}}
title="Post"
/>
</div>
<div className="articles_container">
{
_.map(posts, (article, idx) => {
return (
<PostSnippet
key={idx}
id={article.id}
title={article.title}
content={article.content.substring(0, 300)} />
)
})
}
</div>
</div>
)
}
export default Posts;
Code in PostSnippet.jsx file which is used to give the view to individual cards:
import React from "react";
import { Card } from "antd";
import { Link } from "react-router-dom";
const PostSnippet = (props) => {
return (
<>
<div className="post_snippet_container" style={{ margin: "40px" }}>
<Card
type="inner"
title={props.title}
extra={
<Link to={`/post/${props.id}`}>
Refer the Article
</Link>}
>
<p className="article_content">
{
props.content.split('\n').map((paragraph, idx) => {
return <p key={idx}>{paragraph}</p>
})
}
</p>
</Card>
</div>
</>
)
}
export default PostSnippet;
Actual data in Firestore:
Retried data from the firestore:
setPosts((posts) => [...posts, payload])
You only ever add to the array, so when data is fetched for the second time, you grow your array to twice the size. Instead, replace the array with the new data. That way the second fetch will overwrite the first:
const fetchData = async () => {
const postRef = collection(db, 'posts');
const postSnap = await getDocs(postRef);
const newPosts = postSnap.docs.map(doc => {
return {
id: doc.id,
...doc.data(),
}
});
setPosts(newPosts);
}
Related
I have a page which shows some collections from my firestore database, I am struggling to work out how to use the orderBy function to show the documents in a specific order.
I'm not sure where to put orderBy in the code. I would like to order them by a field from the firestore documents called 'section'.
I've been trying this week following other tutorials and answers from StackOverflow but can't yet work it out.
import React, { useEffect, useState, Component, setState } from 'react';
import { collection, getDocs, getDoc, doc, orderBy, query } from 'firebase/firestore';
import "./AllSections.css";
import { Firestoredb } from "../../../../../firebase.js";
import AllCourses from './AllCourses';
import ReactPlayer from 'react-player'
import ViewSection from './ViewSection';
import SectionsTabData from './SectionsTabData';
import {
BrowserRouter as Router,
Link,
Route,
Routes,
useParams,
} from "react-router-dom";
import VideoJS from './VideoJS';
function SectionsData() {
const videoJsOptions = {
controls: true,
sources: [{
src: sectionVideo,
type: 'video/mp4'
}]
}
const {courseId} = useParams();
const {collectionId} = useParams();
const params = useParams();
const [sectionId, setSectionId] = useState('');
const [sectionImage, setSectionImage] = useState('');
const [sectionVideo, setSectionVideo] = useState('');
const [sectionContent, setSectionContent] = useState('');
const [isShown, setIsShown] = useState(false);
const handleClick = event => {
// 👇️ toggle shown state
setIsShown(current => !current);
}
const [active, setActive] = useState();
const [id, setID] = useState("");
const [Sections, setCourses, error, setError] = useState([]);
useEffect(() => {
getSections()
}, [])
useEffect(() =>{
console.log(Sections)
}, [Sections])
function getSections() {
const sectionsCollectionRef = collection(Firestoredb, collectionId, courseId, 'Sections');
orderBy('section')
getDocs(sectionsCollectionRef)
.then(response => {
const content = response.docs.map(doc => ({
data: doc.data(),
id: doc.id,
}))
setCourses(content)
})
.catch(error => console.log(error.messaage))
}
const handleCheck = (id, image, video, content) => {
console.log(`key: ${id}`)
/*alert(image)*/
setSectionId(id)
setSectionImage(image)
setSectionVideo(video)
setSectionContent(content)
}
return (
<>
<div className='MainSections'>
<div className='Sidebar2'>
<ul className='SectionContainer'
>
{Sections.map(section => <li className='OneSection' key={section.id}
style={{
width: isShown ? '100%' : '200px',
height: isShown ? '100%' : '50px',
}}
onClick={() =>
handleCheck(section.id, section.data.thumbnailImageURLString, section.data.videoURLString, section.data.contentURLString)}
id = {section.id}
>
<br />
{section.data.name}
<br />
<br />
{isShown && (
<img className='SectionImage' src={section.data.thumbnailImageURLString !== "" ? (section.data.thumbnailImageURLString) : null} alt='section image'></img>
)}
<br />
</li>)}
</ul>
</div>
<div className='ViewSection'>
<iframe className='Content' src={sectionContent}
width="100%"/>
</div>
</div>
</>
)
}
export default SectionsData
You are using orderBy incorrectly please view the docs here: https://firebase.google.com/docs/firestore/query-data/order-limit-data
Your query should look something along these lines if you're trying to order your data in a specific way. Assuming your sectionsCollectionRef is correct:
const sectionsCollectionRef = collection(Firestoredb, collectionId, courseId, 'Sections')
const q = query(sectionsCollectionRef, orderBy('section', 'desc'))
const querySnapshot = await getDocs(q);
The orderBy() won't do anything on it's own. You must use it along query() function to add the required QueryConstraint and build a Query as shown below:
import { collection, query } from "firebase/firestore"
const sectionsCollectionRef = collection(Firestoredb, collectionId, courseId, 'Sections');
const sectionsQueryRef = query(sectionsCollectionRef, orderBy("section"))
I want to get data from Firestore in Next.JS like Reddit. For example (http://localhost:3000/r/BestBurger) in cloud firestore my data is in (/BestBurger/posts/posts/KrcvgXMX4HyNKPD1kRAR)
import { useRouter } from 'next/router';
import { useState, useEffect } from 'react';
import firebase from '../../firebase/clientApp';
import Card from '../../components/card/Card';
;
export default function Page() {
const router = useRouter();
const { page } = router.query;
function usePosts() {
const [posts, setPosts] = useState([])
useEffect(() => {
firebase
.firestore()
.collection(`${page}`).doc('posts').collection('posts')
.onSnapshot((snapshot) => {
const newPosts = snapshot.docs.map((doc) => (
{
id: doc.id,
...doc.data()
}))
setPosts(newPosts)
}
)
}, [])
return posts;
}
const posts = usePosts()
console.log(posts)
return (
<>
{
posts.map((post) => (
<Card key={post.id}>
<h2>{post.title}</h2>
{post.body}
</Card>
))
}
</>
)
}
Again, I want to get my page route to the .collection()
I am learning React as I am fetching data from Pokéapi to make a list component, card component, detail component and filter component. I am trying to make a filter so you can filter by pokémon type. Only the cards that also contain that type string should then render (Not there yet). So I am not sure if a) I should make a different call from API inside PokemonList depending on selected value or b) if I should compare the values and just change how the PokemonCard element is rendered inside PokemonList.js depending on the comparison. I managed to pass data from filter to the list component. I have then been trying to pass the type data from PokemonCard.js to the list component so that I can compare these two values but I find it hard to use callbacks to pass the type data from the card component, since I dont pass it through an event or something like that.
Which method should I use here to simplify the filtering? Make different API call or render PokemonCard element conditionally?
Is it a good idea to compare filter option to pokemon card's type in PokemonList.js? Then how can I pass that data from the card component since I don't pass it through click event?
Thankful for any ideas! I paste the code from list component that contains the cards, card component and filter component.
PokemonList component:
import { useState } from 'react';
import useSWR from 'swr';
import PokemonCard from './PokemonCard';
import PokemonFilter from './PokemonFilter';
import './PokemonList.css';
const PokemonList = () => {
const [index, setIndex] = useState(0);
const [type, setType] = useState('');
function selectedType(type) { // value from filter dropdown
setType(type)
console.log("handled")
console.log(type)
}
const url = `https://pokeapi.co/api/v2/pokemon?limit=9&offset=${index}`;
const fetcher = (...args) => fetch(...args).then((res) => res.json())
const { data: result, error } = useSWR(url, fetcher);
if (error) return <div>failed to load</div>
if (!result) return <div>loading...</div>
result.results.sort((a, b) => a.name < b.name ? -1 : 1);
return (
<section>
<PokemonFilter onSelectedType={selectedType} selectedPokemonType={type} />
<div className="pokemon-list">
<div className="pokemons">
{result.results.map((pokemon) => (
<PokemonCard key={pokemon.name} pokemon={pokemon} /> // callback needed??
))}
</div>
<div className="pagination">
<button
onClick={() => setIndex(index - 9)}
disabled={result.previous === null}
>
Previous
</button>
<button
onClick={() => setIndex(index + 9)}
disabled={result.next === null}
>
Next
</button>
</div>
</div>
</section>
)
}
export default PokemonList;
PokemonCard component:
import { Link } from "react-router-dom";
import useSWR from 'swr';
import './PokemonCard.css';
const PokemonCard = ({ pokemon }) => {
const { name } = pokemon;
const url = `https://pokeapi.co/api/v2/pokemon/${name}`;
const { data, error } = useSWR(url);
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
const { types, abilities } = data;
// types[0].type.name <---- value I want to pass to PokemonList.js
return (
<div className='pokemon-card'>
<div className='pokemon-card__content'>
<img
className='pokemon-card__image'
src={data.sprites.front_default}
alt={name}
/>
<div className='pokemon-card__info'>
<p className='pokemon-card__name'>Name: {name}</p>
<p className='pokemon-card__abilities'>Abilities: {abilities[0].ability.name}</p>
<p className='pokemon-card__categories'>Category: {types[0].type.name}</p>
</div>
</div>
<Link className='pokemon-card__link' to={{
pathname: `/${name}`,
state: data
}}>
View Details
</Link>
</div>
)
}
export default PokemonCard;
PokemonFilter component:
import './PokemonFilter.css';
import useSWR from 'swr';
const PokemonFilter = ({onSelectedType, selectedPokemonType}) => {
const url = `https://pokeapi.co/api/v2/type/`;
const fetcher = (...args) => fetch(...args).then((res) => res.json())
const { data: result, error } = useSWR(url, fetcher);
if (error) return <div>failed to load</div>
if (!result) return <div>loading...</div>
function filteredTypeHandler(e) {
console.log(e.target.value);
onSelectedType(e.target.value);
}
console.log(selectedPokemonType)
return(
<div className="pokemon-types__sidebar">
<h2>Filter Pokémon by type</h2>
<select
name="pokemon-type"
className="pokemon-types__filter"
onChange={filteredTypeHandler}
>
<option value="All">Filter By Type</option>
{result.results.map((type) => {
return (
<option key={type.name} value={type.name}> {type.name}</option>
)
})}
</select>
</div>
)
}
export default PokemonFilter;
Here is an example to improve, modify, ... I didn't test, it's just a visual example.
I don't know about useSWR sorry, I use axios in my example...
If you want to centralize all your API requests, you can create a useApi hook, on the internet you will find tutorials.
PokemonList.js
import React, { useState, useEffect } from 'react';
import axios from 'axios'; // or swr
import PokemonFilter from './PokemonFilter';
import PokemonCard from './PokemonCard';
export default function PokemonList() {
const [data, setData] = useState([]);
const [filter, setFilter] = useState('');
// Executed every first render
useEffect(() => {
getData();
}, []);
// Executed only when filter changes
useEffect(() => {
getDataByTypes(filter);
}, [filter]);
// Get data
const getData = async () => {
const uri = 'https://xxx';
try {
const response = await axios.get(uri);
setData(response.data...);
} catch (error) {
console.log(error);
}
};
// Get data by types
const getDataByTypes = async (filter) => {
const uri = `https://xxx/type/${filter}...`;
if (filter) {
try {
const response = await axios.get(uri);
setData(response.data...);
} catch (error) {
console.log(error);
}
}
};
return (
<div className="main">
<PokemonFilter filter={filter} setFilter={setFilter} />
<div className="container">
<div className="cards-container">
{data.map((d) => (
<PokemonCard key={d.name} data={d} />
))}
</div>
</div>
</div>
);
}
PokemonCard.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export default function PokemonCard({ data }) {
const [pokemons, setPokemons] = useState();
useEffect(() => {
getPokemons(data);
}, [data]);
// Get Pokemons
const getPokemons = async (data) => {
const uri = `https://xxx/pokemon/${data.name}/`;
try {
const response = await axios.get(uri);
setPokemons(response.data...);
} catch (error) {
console.log(error);
}
};
return (
<div>
{pokemons && (
<div className="card">
<img src={pokemons.sprites.front_default} alt={pokemons.name} />
<p>{pokemons.name}</p>
<p>{pokemons.abilities[0].ability.name}</p>
<p>{pokemons.types[0].type.name}</p>
</div>
)}
</div>
);
}
PokemonFilter.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export default function PokemonFilter({ filter, setFilter }) {
const [types, setTypes] = useState([]);
useEffect(() => {
getType();
}, []);
// Get Type
const getType = async () => {
const uri = 'https://xxx/type/';
try {
const response = await axios.get(uri);
setTypes(response.data.results....);
} catch (error) {
console.log(error);
}
};
const handleFilter = (e) => {
setFilter(e.target.value);
};
return (
<select onChange={handleFilter} value={filter}>
<option>Filter by type</option>
{types.map((type) => {
return (
<option key={type.name} value={type.name}>
{type.name}
</option>
);
})}
</select>
);
}
How to pass the {requests} prop to the RequestRow component after executing the setRequests? My understanding is that the requests get initialized as undefined in the beginning and before being set with the asynchronously called object, it gets passed to the RequestRow component as undefined, and the error occurs.
import React, { useState, useEffect } from 'react';
import 'semantic-ui-css/semantic.min.css';
import Layout from '../../../components/Layout';
import { Button } from 'semantic-ui-react';
import { Link } from '../../../routes';
import Campaign from '../../../blockchain/campaign';
import { Table } from 'semantic-ui-react';
import RequestRow from '../../../components/RequestRow';
const RequestsIndex = ({ address }) => {
const { Header, Row, HeaderCell, Body } = Table;
const campaign = Campaign(address);
const [requestCount, setRequestCount] = useState();
const [requests, setRequests] = useState([]);
const getRequests = async () => {
const count = await campaign.methods.getRequestsCount().call();
setRequestCount(count);
};
let r;
const req = async () => {
r = await Promise.all(
Array(parseInt(requestCount))
.fill()
.map((_element, index) => {
return campaign.methods.requests(index).call();
})
);
setRequests(r);
};
useEffect(() => {
getRequests();
if (requestCount) {
req();
}
}, [requestCount]);
return (
<Layout>
<h3>Requests List.</h3>
<Link route={`/campaigns/${address}/requests/new`}>
<a>
<Button primary>Add Request</Button>
</a>
</Link>
<Table>
<Header>
<Row>
<HeaderCell>ID</HeaderCell>
<HeaderCell>Description</HeaderCell>
<HeaderCell>Amount</HeaderCell>
<HeaderCell>Recipient</HeaderCell>
<HeaderCell>Approval Count</HeaderCell>
<HeaderCell>Approve</HeaderCell>
<HeaderCell>Finalize</HeaderCell>
</Row>
</Header>
<Body>
<Row>
<RequestRow requests={requests}></RequestRow>
</Row>
</Body>
</Table>
</Layout>
);
};
export async function getServerSideProps(context) {
const address = context.query.address;
return {
props: { address },
};
}
export default RequestsIndex;
The RequestRow component is shown below. It takes in the {requests} props, which unfortunately is undefined.
const RequestRow = ({ requests }) => {
return requests.map((request, index) => {
return (
<>
<div>Request!!!</div>
</>
);
});
};
export default RequestRow;
The snapshot of the error is shown below:
I think React is trying to render your component before your promises resolve. If that's the case, all you need to do is set a default value (an empty array in your case) for your requests.
const [requests, setRequests] = useState([]);
May the force be with you.
This is my main component for seraching:
import React, {useState, useEffect} from 'react';
import SearchBar from "./SearchBar";
import SearchList from "./SongList";
const SearchMusic = (props) => {
const [input, setInput] = useState('');
const [songListDefault, setSongListDefault] = useState();
const [songList, setSongList] = useState();
const fetchData = async () => {
return await fetch('http://ws.audioscrobbler.com/2.0/?method=tag.gettoptracks&tag=disco&api_key=c17b1886d9465542a9cd32437c804db6&format=json')
.then(response => response.json())
.then(data => {
setSongList(data)
setSongListDefault(data)
});
}
const updateInput = async (input) => {
const filtered = songListDefault.filter(song => {
return song.name.toLowerCase().includes(input.toLowerCase())
})
setInput(input);
setSongList(filtered);
}
useEffect(() => {
fetchData()
}, [])
return (
<div>
<h1>Song List</h1>
<SearchBar
input={input}
onChange={updateInput}
/>
<SearchList songList={songList}/>
</div>
);
};
export default SearchMusic;
below is separate input js file:
import React from 'react';
const SearchMusic = ({keyword, setKeyword}) => {
const BarStyling = {width: "20rem", background: "#F2F1F9", border: "none", padding: "0.5rem"};
return (
<input
type="text"
style={BarStyling}
key='random1'
value={keyword}
placeholder={'Search a song'}
onChange={(e => setKeyword(e.target.value))}
/>
);
};
export default SearchMusic;
end it is my song list below:
import React from 'react';
const SongList = ({songList = []}) => {
return (
<div>
{
songList && songList.tracks.track.map((song, index) => {
if (song) {
return (
<div key={song.name}>
<h1>{song.name}</h1>
</div>
)
}
return null;
}
)
}
</div>
);
};
export default SongList;
I get this mistake --> TypeError: setKeyword is not a function. I don't what's wrong and don't know how to get rid of it. It seems to me problem is in updateInput function more precisely in what it returns --> song.name.toLowerCase(). There is api link:
http://ws.audioscrobbler.com/2.0/?method=tag.gettoptracks&tag=disco&api_key=c17b1886d9465542a9cd32437c804db6&format=json
I need to get name of a song in search input... But something's wrong
The props that this component are expecting are not getting passed into component in your parent component
const SearchMusic = ({keyword, setKeyword}) => {
const BarStyling = {width: "20rem", background: "#F2F1F9", border: "none", padding: "0.5rem"};
return (
<input
type="text"
style={BarStyling}
key='random1'
value={keyword}
placeholder={'Search a song'}
onChange={(e => setKeyword(e.target.value))}
/>
);
};
That is assuming that the following is the above component
<SearchBar
input={input}
onChange={updateInput}
/>
How about
<SearchBar
keyword={input}
setKeyword={updateInput}
/>