Firestore orderBy function in react - javascript

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

Related

Firebase reading data two times from firestore

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

how to print firebase data in Agenda | Firebase expo

I'm trying to print the data I get from my database but I can't think of how to get the data variable and use it in renderItem(). I've already managed to create keys through useState to use in map(), I just wanted to know a way to get these variables out of useState and be able to use them in react-agenda
import React, { useEffect, useState } from 'react'
import { StyleSheet, View, Text, TouchableOpacity, Modal, TextInput, Alert } from 'react-native'
import { LocaleConfig, Agenda } from 'react-native-calendars'
import DateTimePicker from 'react-native-modal-datetime-picker';
import { getAuth } from 'firebase/auth';
import { getDatabase, ref, onValue, set, push, get, child } from 'firebase/database';
const Calendario = () => {
const [modalVisible, setModalVisible] = useState(false);
const [events, setEvents] = useState('');
const [dateSelected, setDateSelected] = useState('');
const [description, setDescription] = useState('');
const [refreshCalender, setRefreshCalender] = useState(false);
const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
const [data, setmyData]=useState({})
const db = getDatabase();
const app = getAuth();
const refe = ref(db, 'users/' + app.currentUser.uid);
const dbRef = ref(db);
const renderItem = (item, data) => {
return(
data.map(() => {
<View style={styles.itemContainer}>
<Text id={item["$key"]} style={styles.textInf}>{item.title}</Text>
<Text style={styles.textInf}>{item.details}</Text>
</View>
})
)
}
const storeInDB = () => {
push(refe, {
date: strDate,
title: events,
details: description
});
}
useEffect(()=>{
onValue(ref(db, 'users/' + app.currentUser.uid), snapshot =>{
let data = [];
snapshot.forEach(childsnap => {
let val = childsnap.val();
val['$key'] = childsnap.key;
data.push(val);
});
setmyData(data);
})
},[]);
return (
<>
<Agenda
//items={ dataF }
renderEmptyDate={() => {
return <View />;
}}
renderEmptyData={() => {
return <View />;
}}
selected={new Date()}
minDate={null}
renderItem={renderItem}
markingType="custom"
refreshing={refreshCalender}
/>
</>
)
}
export default Calendario
Data from firebase:
I create an array to store the data and converted it to an object using Object.assign(), now the problem is just to render the items
const data = []
var obj = {}
useEffect(() => {
get(child(dbRef, 'users/' + app.currentUser.uid)).then((snapshot) => {
snapshot.forEach(childsnap => {
let dateD = childsnap.child("date").val()
let titleD = childsnap.child("title").val()
let dtsD = childsnap.child("details").val()
// "yyyy-MM-dd": [{any: "whatever", any2: "whatever"}],
data.push({
[dateD] : [{ title: titleD, details: dtsD }],
});
})
obj = Object.assign({}, ...data)
console.log(obj)
})
} ,[])

Pagination with React doesn't work, all items are still displayed on screen

I have a pagination made with a react-paginate package called react-paginate here is the link to the doc. https://www.npmjs.com/package/react-paginate
I have implemented it in my App which is a notes diary, the user creates notes and these are dynamically saved in the localStorage and displayed on screen, well, I have established that there are 6 notes per page, that is, when there is a seventh note, it should not be displayed unless the user goes to page 2, when there are 13 notes page 3 and so ...
The functionality of my component that I have called Pagination works correctly, it is dynamic, I have right now testing 13 notes, so it shows me 3 pages, if I had 12, it would show me 2.
The problem is that although my pagination is correct, the 13 notes are being shown on the screen, when it should be 6 - 6 - 1.
I leave you the code to see if we can find the error, greetings and thanks in advance.
The prop that Pagination receives called data, are basically the notes that are created dynamically in App.js. const [notes, setNotes] = useState([]);
Component Pagination
import React, { useEffect, useState } from 'react'
import ReactPaginate from 'react-paginate';
import '../styles/Pagination.css';
const Pagination = (props) => {
const { data } = props;
// We start with an empty list of items.
const [currentItems, setCurrentItems] = useState([]);
const [pageCount, setPageCount] = useState(0);
// Here we use item offsets; we could also use page offsets
// following the API or data you're working with.
const [itemOffset, setItemOffset] = useState(0);
const itemsPerPage = 6;
useEffect(() => {
// Fetch items from another resources.
const endOffset = itemOffset + itemsPerPage;
console.log(`Loading items from ${itemOffset} to ${endOffset}`);
setCurrentItems(data.slice(itemOffset, endOffset));
setPageCount(Math.ceil(data.length / itemsPerPage));
}, [itemOffset, itemsPerPage, data]);
// Invoke when user click to request another page.
const handlePageClick = (event) => {
const newOffset = (event.selected * itemsPerPage) % data.length;
console.log(
`User requested page number ${event.selected}, which is offset ${newOffset}`
);
setItemOffset(newOffset);
};
return (
<>
<ReactPaginate
breakLabel="..."
nextLabel="next >"
onPageChange={handlePageClick}
pageRangeDisplayed={3}
pageCount={pageCount}
previousLabel="< previous"
renderOnZeroPageCount={null}
containerClassName="pagination"
pageLinkClassName="page-num"
previousLinkClassName="page-num"
nextLinkClassName="page-num"
activeLinkClassName="activee boxx"
/>
</>
);
}
export default Pagination;
Component App
import { useState, useEffect } from "react";
import { nanoid } from 'nanoid';
import NoteList from "./components/NoteList";
import './App.css';
import Search from "./components/Search";
import Header from "./components/Header";
import Pagination from "./components/Pagination";
const App = () => {
const [notes, setNotes] = useState([]);
const [searchText, setSearchText] = useState('');
const [darkMode, setDarkMode] = useState(false);
//Se encarga de mostrar la nota para escribir
const [showNote, setShowNote] = useState(true); //eslint-disable-line
useEffect(() => {
const saveNotes = JSON.parse(localStorage.getItem('notes-data'));
if (saveNotes){
setNotes(saveNotes);
}
}, []);
useEffect(() => {
localStorage.setItem('notes-data', JSON.stringify(notes))
},[notes])
const addNote = (inputText, text) => {
const date = new Date();
const newNote = {
id: nanoid(),
title: inputText,
text: text,
date: date.toLocaleString()
}
const newNotes = [newNote, ...notes];
setNotes(newNotes)
}
const deleteNote = (id) => {
var response = window.confirm("Are you sure that you want to remove the note?");
if (response){
const notesUpdated = notes.filter((note) => note.id !== id)
setNotes(notesUpdated);
}
}
return (
<div className={darkMode ? 'dark-mode' : ''}>
<div className="container">
<Header
handleToggleTheme={setDarkMode}
/>
<Search
handleSearchNote={setSearchText}
setShowNote={setShowNote}
/>
<NoteList
notes={notes.filter((noteText) =>
noteText.title.toLowerCase().includes(searchText)
)}
handleAddNote={addNote}
handleDeleteNote={deleteNote}
/>
<Pagination data={notes}/>
</div>
</div>
)
}
export default App;
The problem is you are not using currentItems and the paginated data is stored in that state.
Codesandbox: https://codesandbox.io/s/sweet-keldysh-2u72vd
Pagination.js
import React, { useEffect, useState } from 'react'
import ReactPaginate from 'react-paginate';
import NoteList from "./components/NoteList";
import '../styles/Pagination.css';
const Pagination = (props) => {
const { data, searchText, handleAddNote, handleDeleteNote } = props;
// We start with an empty list of items.
const [currentItems, setCurrentItems] = useState([]);
const [pageCount, setPageCount] = useState(0);
// Here we use item offsets; we could also use page offsets
// following the API or data you're working with.
const [itemOffset, setItemOffset] = useState(0);
const itemsPerPage = 6;
useEffect(() => {
// Fetch items from another resources.
const endOffset = itemOffset + itemsPerPage;
console.log(`Loading items from ${itemOffset} to ${endOffset}`);
setCurrentItems(data.slice(itemOffset, endOffset));
setPageCount(Math.ceil(data.length / itemsPerPage));
}, [itemOffset, itemsPerPage, data]);
// Invoke when user click to request another page.
const handlePageClick = (event) => {
const newOffset = (event.selected * itemsPerPage) % data.length;
console.log(
`User requested page number ${event.selected}, which is offset ${newOffset}`
);
setItemOffset(newOffset);
};
return (
<>
<NoteList
notes={currentItems.filter((noteText) =>
noteText.title.toLowerCase().includes(searchText)
)}
handleAddNote={handleAddNote}
handleDeleteNote={handleDeleteNote}
/>
<ReactPaginate
breakLabel="..."
nextLabel="next >"
onPageChange={handlePageClick}
pageRangeDisplayed={3}
pageCount={pageCount}
previousLabel="< previous"
renderOnZeroPageCount={null}
containerClassName="pagination"
pageLinkClassName="page-num"
previousLinkClassName="page-num"
nextLinkClassName="page-num"
activeLinkClassName="activee boxx"
/>
</>
);
}
export default Pagination;
App.js
import { useState, useEffect } from "react";
import { nanoid } from 'nanoid';
import './App.css';
import Search from "./components/Search";
import Header from "./components/Header";
import Pagination from "./components/Pagination";
const App = () => {
const [notes, setNotes] = useState([]);
const [searchText, setSearchText] = useState('');
const [darkMode, setDarkMode] = useState(false);
//Se encarga de mostrar la nota para escribir
const [showNote, setShowNote] = useState(true); //eslint-disable-line
useEffect(() => {
const saveNotes = JSON.parse(localStorage.getItem('notes-data'));
if (saveNotes){
setNotes(saveNotes);
}
}, []);
useEffect(() => {
localStorage.setItem('notes-data', JSON.stringify(notes))
},[notes])
const addNote = (inputText, text) => {
const date = new Date();
const newNote = {
id: nanoid(),
title: inputText,
text: text,
date: date.toLocaleString()
}
const newNotes = [newNote, ...notes];
setNotes(newNotes)
}
const deleteNote = (id) => {
var response = window.confirm("Are you sure that you want to remove the note?");
if (response){
const notesUpdated = notes.filter((note) => note.id !== id)
setNotes(notesUpdated);
}
}
return (
<div className={darkMode ? 'dark-mode' : ''}>
<div className="container">
<Header
handleToggleTheme={setDarkMode}
/>
<Search
handleSearchNote={setSearchText}
setShowNote={setShowNote}
/>
<Pagination data={notes} handleAddNote={addNote}
handleDeleteNote={deleteNote} searchText={searchText} />
</div>
</div>
)
}
export default App;

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

Rendering info from the Reddit API using React

I am trying to build a basic web app where a user can search for subreddits using the Reddit API.
However, while I can console.log the data I need from the API I cannot seem to figure out how to display it.
import React, { useState, useEffect } from "react";
import Amplify, { API, graphqlOperation } from "aws-amplify";
import aws_exports from "./aws-exports";
import { withAuthenticator, AmplifySignOut } from '#aws-amplify/ui-react';
import awsconfig from "./aws-exports";
import './App.css'
import axios from 'axios'
import CharacterGrid from './components/CharacterGrid'
import SearchBar from './components/SearchBar'
Amplify.configure(awsconfig);
const App = () => {
const [items, setItems] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [query, setQuery] = useState('')
useEffect(() => {
const fetchItems = async () => {
setIsLoading(true)
const result = await axios(
`https://www.reddit.com/subreddits/search.json?q=${query}`
)
setItems(result.data)
setIsLoading(false)
}
fetchItems()
}, [query])
return (
<div className='container'>
<AmplifySignOut/>
<SearchBar style={{ marginTop: "6%" }} getQuery={(q) => setQuery(q)} />
<CharacterGrid isLoading={isLoading} items={items} />
</div>
)
}
export default withAuthenticator(App)
The child component CharacterGrid looks like this:
import React from 'react'
import CharacterItem from './CharacterItem'
const CharacterGrid = ({items, isLoading}) => {
return
isLoading
? (<h1>Loading ...</h1>)
: (
<section className='cards'>
<p>{items.data}</p> //this does not work
<p>{items.children}</p> //this does not work either
</section>
)
}
export default CharacterGrid
All I want to do is show the names of the subreddits that are returned from the API for the query string the user enters. Where am I going wrong? I have also tried converting to JSON, and mapping through the responses using .map() but I keep getting errors no matter what I try. Where am I going wrong?
However, while I can console.log the data I need from the API I cannot seem to figure out how to display it.
Because Reddit API returns an array of subreddits you should use map() function to iterate over the array and convert each item into React element.
items.map(i => (
<li key={i.data.display_name_prefixed}>
{i.data.display_name}
</li>
))
All I want to do is show the names of the subreddits that are returned from the API for the query string the user enters.
You need to inspect the data schema yourself and scrape the response properly.
Here is working example:
const { useState, useEffect } = React;
const App = () => {
const [items, setItems] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [query, setQuery] = useState("");
useEffect(() => {
setIsLoading(true);
if (query.length >= 3) {
axios(`https://www.reddit.com/subreddits/search.json?q=${query}`)
.then(response => setItems(response.data.data.children))
.catch(error => console.log("Error", error));
}
setIsLoading(false);
}, [query]);
return (
<div>
<input type="text" value={query} onChange={e => setQuery(e.target.value)} />
<CharacterGrid items={items} isLoading={isLoading} />
</div>
);
}
const CharacterGrid = ({ items, isLoading }) => {
return isLoading ? (
<h1>Loading ...</h1>
) : (
<ul>
{items.map(i => (
<li key={i.data.display_name_prefixed}>
{i.data.display_name} ({i.data.display_name_prefixed})
</li>
))}
</ul>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js"></script>
<div id="root"></div>
In order to loop through the elements and e.g: print the title, you could do the following.
items.data.children.map(child => <p> child.data.title </p>)
According to the response provided by reddit,
reddit response

Categories

Resources