How to retrieve data from a Firestore database - javascript

I am doing a project in React, where after I type a value and then click on search button, the app searches if the id exists in the database. If so, it displays the result of the search in the same page. I am having trouble assigning the value of the search and then displaying it. When I try to assign the result of the search to an array, it gives me the error:
Type 'DocumentData[]' is not assignable to type 'Dispatch<SetStateAction<Identification[]>>'.
Type 'DocumentData[]' provides no match for the signature '(value:SetStateAction<Identification[]>): void'.
When I did a console.log of just the data in no variable, I can get the results, but I need it in the setId variable.
Here is the code:
import React, {ChangeEvent} from "react";
import { useState,useEffect } from "react";
import LongText from "../atoms/LongText";
import AppListBI from "./AppListBI";
import {Identification} from "../../assets/Person/Person";
import db from "../../firebase.config"
const Core = () => {
var [input, setInput] = useState('')
const [showResults, setShowResults] = React.useState(false)
var [person, setId] = useState<Identification[]>([]);
const fetchBI = async () => {
const ref=db.collection('id').where('numberId','==',input).get().then((snapshot) => {
snapshot.docs.forEach(doc =>{
setId=[...person,doc.data()]
//I also tried
setId=doc.data()
})
})
}
return (
<>
<div className="mx-7">
<span className="font-bold text-xl"><h5>Pesquisar:</h5></span></div>
<div className="flex justify-center">
<LongText placeholder="Pesquisar Id" onChange={
(e: ChangeEvent<HTMLInputElement>)=>setInput(e.target.value)}
onClick={useEffect(()=>{
setShowResults(true)
fetchBI();
})}/>
</div>
<div className="flex justify-center">
<span className="my-4 w-11/12">
{ showResults ? <AppListId persons={person} /> : null }
</span>
</div>
</>
);
}
export default Core;

After long days I found the solution:
I traded this:
const fetchBI = async () => {
const ref=db.collection('id').where('numberId','==',input).get().then((snapshot) => {
snapshot.docs.forEach(doc =>{
setId=[...person,doc.data()]
to:
const fetchBI = async () => {
try{
var people : ID[] = []
await db.collection('id').where('numberId','==',input).get().then(
querySnapshot=>{
const data = querySnapshot.docs.map(
doc=>{
let dat = doc.data()
people.push({
numberId: dat.numberId,
name: dat.name,
dateOfBirth: dat.dateOfBirth,
placeOfBirth: dat.placeOfBirth,
fathersName: dat.fathersName,
mothersName: dat.mothersName,
gender: dat.gender,
profession: dat.profession,
dateOfIssue: dat.dateOfIssue,
expirationDate: dat.expirationDate
})
})
setId(people)
}
)
}catch (error) {
console.log(error.message)
}
}

Related

property does not exist on type in React Typescript

I am currently learning TypeScript in React so i was working on learning how to make API request with typescript I am fetching a single data by Id the result of the api request is displaying on my web page but i encountered an error the typescript compiler is saying Property does not exits
here is my code
import { To, useParams } from "react-router-dom";
import axios from "axios";
import { useState, useEffect } from "react";
const SinglePOST = () => {
type Todo = {
title: string;
body: string;
userId: number;
id: number;
};
const { id } = useParams();
const [data, setData] = useState<Todo[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [isError, setError] = useState<any>(null);
useEffect(() => {
const singleReq = async () => {
try {
setLoading(true);
const res = await axios.get<Todo[]>(
`https://jsonplaceholder.typicode.com/posts/${id}`,
);
await setData(res.data);
console.log(res.data);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
singleReq();
}, [id]);
return (
<div className=' w-full h-screen bg-slate-900 text-neutral-300 p-4'>
<div className='w-full flex justify-center '> Single Post {id}</div>
{loading && <p>...Loading</p>}
{isError && <p> Error in getting post</p>}
<div className='text-2xl'> {data.title}</div>
<div className=' text-xl'> {data.body}</div>
</div>
);
};
export default SinglePOST;
This is the error it was displaying
Property 'title' does not exist on type 'Todo[]'
Property 'body' does not exist on type 'Todo[]'
because your data is a single object but you defined your data as a list of objects.
import { To, useParams } from 'react-router-dom';
import axios from 'axios';
import { useState, useEffect } from 'react';
const SinglePOST = () => {
type Todo = {
title: string;
body: string;
userId: number;
id: number;
};
const { id } = useParams();
const [data, setData] = useState<Todo>();
const [loading, setLoading] = useState<boolean>(false);
const [isError, setError] = useState<any>(null);
useEffect(() => {
const singleReq = async () => {
try {
setLoading(true);
const res = await axios.get<Todo>(
`https://jsonplaceholder.typicode.com/posts/${id}`
);
await setData(res.data);
console.log(res.data);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
singleReq();
}, [id]);
return (
<div className=' w-full h-screen bg-slate-900 text-neutral-300 p-4'>
<div className='w-full flex justify-center '> Single Post {id}</div>
{loading && <p>...Loading</p>}
{isError && <p> Error in getting post</p>}
<div className='text-2xl'> {data?.title}</div>
<div className=' text-xl'> {data?.body}</div>
</div>
);
};
You've set the type to your state as a list of Todo objects, hence the error.
const [data, setData] = useState<Todo[]>([]);
Does your get request return an array of todos?
If yes then you need to map through them:
{
data.map((todo, idx) => {
return (
<div key={idx}>
<div className='text-2xl'> {data.title}</div>
<div className=' text-xl'> {data.body}</div>
</div>
)
});
}
If your get request returns a todo object then you need to change the type on your state:
const [data, setData] = useState<Todo>();

Firestore orderBy function in react

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"))

Cannot fix "Uncaught TypeError: posts.map is not a function" error

I am creating a sns-like web application. As one of the functions, I am trying to display all posts users made. However, my code shows nothing and get an error on console saying "Uncaught TypeError: posts.map is not a function". I am totally a beginner in Javascript, react and firebase. Could anyone look into my code? Thank you.
import React, { useState, useEffect } from 'react';
import "./Post.css";
import Posts from "./Posts.js";
import { getFirestore } from "firebase/firestore";
import { collection, doc, onSnapshot } from "firebase/firestore";
import { useNavigate } from "react-router-dom";
import ImageUpload from "./ImageUpload.js";
function Post( {user} ) {
const db = getFirestore();
const navigate = useNavigate("");
const [posts, setPosts] = useState('');
const colRef = collection(db, 'posts');
useEffect(()=>
onSnapshot(colRef,(snapshot) => {
setPosts(
snapshot.docs.map((doc) => {
return{
post: doc.data(),
id: doc.id
};
})
);
}),
[]);
return (
<div className = "post">
<ImageUpload username = {user?.displayName} />
{
posts.map(({id, post}) => (
<Posts key = {id}
postId = {id}
origuser = {user?.displayName}
username = {post.username}
userId = {user.uid}
caption = {post.caption}
imageUrl = {post.imageUrl}
noLikes = {post.noLikes}
/>
))
}
</div>
)
}
export default Post
The first step is to initialise posts as an array, currently you have it as a string and in the string prototype there is no .map function.
const [posts, setPosts] = useState([]);
After that you need to make sure that in the setPosts call you also pass an array. By looking at the example it seems like already it is snapshot.docs.map.
Try this :
{
posts && posts.map(({id, post}) => (
<Posts key = {id}
postId = {id}
origuser = {user?.displayName}
username = {post.username}
userId = {user.uid}
caption = {post.caption}
imageUrl = {post.imageUrl}
noLikes = {post.noLikes}
/>
))
}
define your state as a array
const [posts, setPosts] = useState([]);
then add extra validation layer on the map method
{
posts && posts.length > 0 && posts.map(({id, post}) => (
<Posts key = {id}
postId = {id}
origuser = {user?.displayName}
username = {post.username}
userId = {user.uid}
caption = {post.caption}
imageUrl = {post.imageUrl}
noLikes = {post.noLikes}
/>
))
}

How to pass data from child to parent and render content based on selected value in dropdown?

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>
);
}

Reactjs filter not returning correct users, when i delete the characters in the search filter

I am fetching the users from dummyapi and i am listing them. There is a search input, i want to filter the users on the page by name. When i type the characters, it filters correctly. When i start to delete the character, users are not listed correctly. It remains filtered. How can i fix this ? This is my code:
import { useEffect, useState } from "react";
import Header from "../components/Header";
import User from "./User";
import axios from "axios";
function App() {
const BASE_URL = "https://dummyapi.io/data/api";
const APP_ID = "your app id";
const [users, setUsers] = useState(null);
const handleChange = (e) => {
const keyword = e.target.value.toLowerCase();
const filteredUsers =
users &&
users.filter((user) => user.firstName.toLowerCase().includes(keyword));
setUsers(filteredUsers);
};
useEffect(() => {
async function fetchData() {
try {
const response = await axios.get(`${BASE_URL}/user?limit=1`, {
headers: { "app-id": APP_ID },
});
setUsers(response.data.data);
} catch (error) {
console.log(error);
}
}
fetchData();
}, []);
return (
<>
<Header />
<div className="container">
<div className="filter">
<h3 className="filter__title">USER LIST</h3>
<div>
<input
id="filter"
type="text"
placeholder="Search by name"
onChange={handleChange}
/>
</div>
</div>
<div className="user__grid">
{users &&
users.map((user, index) => {
const { id } = user;
return <User key={index} id={id} />;
})}
</div>
</div>
</>
);
}
export default App;
This is because you are manipulating the original array of users. So after each filter the original array has less values than previous hence after deleting it will search from the reduced number of elements.
To avoid this, keep original way as it is, apply filter on that and store the result in a separate array.
Something like this:
const [allUsers, setAllUsers] = useState(null); //will store original records
const [users, setUsers] = useState(null); // will store filtered results
then in useEffect hook:
useEffect(() => {
async function fetchData() {
try {
const response = await axios.get(`${BASE_URL}/user?limit=1`, {
headers: { "app-id": APP_ID },
});
setUsers(response.data.data);
setAllUsers(response.data.data); //add this line
} catch (error) {
console.log(error);
}
}
fetchData();
}, []);
and finally in handleChange event:
const handleChange = (e) => {
const keyword = e.target.value.toLowerCase();
// use allUsers array (with original unchanged data)
const filteredUsers =
allUsers &&
allUsers.filter((user) => user.firstName.toLowerCase().includes(keyword));
setUsers(filteredUsers);
};
Obviously, you can use some better approach, but this is just to give the idea of original issue.

Categories

Resources