I want to sort data by timestamp in firestore subcollection using orderBy. I looked example and tried to follow but it didn't work. How to use orderBy timestamp in firestore with React. This is a my reference to fetch a data in subcollection ref
this is a my result on web
result
App.js
import "./App.css";
import { collection } from "#firebase/firestore";
import { useCollectionData } from "react-firebase-hooks/firestore";
import { db } from "./firebase";
import ChildrenList from "./ChildrenList";
export default function App() {
const query = collection(db, "profile");
const [docs, loading, error] = useCollectionData(query);
return (
<div>
<h1>test</h1>
{loading && "Loading..."}
<ul>
<tbody>
{docs?.map((doc) => (
<div key={Math.random()}>
<ChildrenList path={`profile/${doc.uid}/time`} />
</div>
))}
</tbody>
</ul>
</div>
);
}
ChildrenList.js
import { collection } from "#firebase/firestore";
import { useCollectionData } from "react-firebase-hooks/firestore";
import { db } from "./firebase";
import Table from 'react-bootstrap/Table';
export default function ChildrenList({ path }) {
const query = collection(db, path);
const [docs, loading, error] = useCollectionData(query);
return (
<ul>
{loading && "Loading..."}
<Table striped bordered hover variant="dark" size="md">
<tbody>
{docs?.map((doc) => {
const timestamp = { nanoseconds: doc.timeStamp.nanoseconds, seconds: doc.timeStamp.seconds }
const firebasetime = new Date(
timestamp.seconds * 1000 + timestamp.nanoseconds / 1000000,
);
const date = firebasetime.toDateString();
const timeStamp = firebasetime.toLocaleTimeString();
console.log(date, timeStamp);
return(
<tr>
<td>{doc.name}</td>
<td>{doc.sid}</td>
<td>{doc.group}</td>
<td>{doc.room}</td>
<th>{date}</th>
<th>{timeStamp}</th>
</tr>
)})}</tbody>
</Table>
</ul>
);
}
import { getFirestore, collectionGroup, getDocs,
query, orderBy, limit } from '#firebase/firestore';
export default function App() {
// const query = collection(db, "profile"); // This is not query it's reference
const q = query(collectionGroup(getFirestore(), 'subCollectionName'),
orderBy('createdAt', 'desc'), limit(15))
// I dont know how reactFirebaseHooks returns so make remplate on your own.
const [docs, loading, error] = useCollectionData(q);
// Firebase would use `onSnapshoot()` or `getDocs()` function to
// get data using query i made above.
return (
<div>
<h1>test</h1>
{loading && "Loading..."}
<ul>
{docs?.map((doc) => (
...
))}
</ul>
</div>
);
}
<tbody> tag is for <table> not <ul>. I don't know what react firebase hooks returns, but I know that getDocs from Firebase returns array of documents snapshots that under .data() has document data.
Related
I'm trying to create an appointments section with upcoming and past button where each button (when clicked) displays a table based on defined condition. They also both use the same collection. However, whenever I clicked one of them, it looks like they are just looping.
import React,{useState} from 'react';
import './App.css';
import db from './firebase';
import 'react-toastify/dist/ReactToastify.css';
const App = () => {
// get current date
var currentDate = new Date().toISOString().slice(0, 10);
const [accounts, setAccounts] = useState([]);
const handleUpcoming = async () => {
const response = db.collection('accounts').where('schedule', '>', currentDate);;
const data = await response.get();
const newAccounts = data.docs.map(item => item.data())
setAccounts(newAccounts);
}
const handlePast = async () => {
const response = db.collection('accounts').where('schedule', '<', currentDate);;
const data = await response.get();
const newAccounts = data.docs.map(item => item.data())
setAccounts(newAccounts);
}
return (
<div className="app-container">
<h1>Appointments</h1>
<div className="button-container">
<button onClick={handleUpcoming()}>Upcoming</button>
<button onClick={handlePast()}>Past</button>
<div className = "scrollbar">
<table>
{
accounts && accounts.map(account=>{
return(
<tr>
<td className="appointmentDataOne">{account.name}
<div className="appointmentDataTwo">{account.email}</div></td>
<td className="appointmentDataOne">{account.schedule}</td>
<td className="appointmentDataTwo">{account.service}</td>
<td className="appointmentDataTwo">{account.mobileNumber}</td>
</tr>
)
})
}
</table>
</div>
</div>
</div>
);
}
export default App;
I got the State Data from Store. I created the Search Box to filter that Data, Now I got the FilterData also, But how I need to update my UI with that Filtered Data, In HandleSearch method I stored the the Filtered data in FilteredData varibale, But I am Unable to Iterate the FilteredData varibale and I am unable to update in the UI, BUt it is working in console, Now i need to update in the UI, Please can anyone help in this, Thanks in Advance...
import { Dispatch } from "redux"
import axios from 'axios'
export const FETCH_SUCCESS : string ='FETCH_SUCCESS';
export const FETCH_SEARCH ='FETCH_SEARCH';
export const fetchUser=()=>{
return async (dispatch:Dispatch) =>{
try{
let dataUrl : string ="http://localhost:3000/users";
let response = await axios.get(dataUrl);
dispatch({type:FETCH_SUCCESS, payload : response.data})
} catch {
}
}
}
import * as searchAction from './SearchAction';
import {SearchingInter} from '../../componets/SearchingInter';
export interface ISearch{
search : SearchingInter[]
}
let initialSate : ISearch ={
search : [] as SearchingInter[]
}
export const reducer =(state =initialSate , action:any) :ISearch =>{
switch(action.type){
case searchAction.FETCH_SUCCESS :
return {
...state,
search : action.payload
};
default : return state;
}
}
import React, { ChangeEvent } from 'react';
import {useEffect} from 'react';
import {useSelector,useDispatch} from 'react-redux';
import * as searchActions from '../Redux/SearchFetch/SearchAction';
import * as searchReducsers from '../Redux/SearchFetch/Searchreducer';
import SearchingData from './SearchingData';
const Search = () => {
let dispatch = useDispatch();
let readingStateData : searchReducsers.ISearch = useSelector((state : {searchingData:searchReducsers.ISearch})=>{
return state.searchingData;
})
useEffect(() => {
console.log(readingStateData.search)
dispatch(searchActions.fetchUser());
}, [])
const handlesearching =(e:ChangeEvent<HTMLInputElement>)=>{
//console.log(e.target.value);
let defaultData = readingStateData.search;
//console.log(defaultData);
const filteredData = e.target.value ? defaultData.filter(user =>user.UserName.toLowerCase().startsWith(e.target.value)) : defaultData
}
return (
<React.Fragment>
<div className="container mt-3">
<div className="row">
<div className="col-md-3">
<div className="card">
</div>
</div>
</div>
</div>
<SearchingData handleSearch={handlesearching}/>
<table className="table table-hover text-center table-primary">
<thead className="text-black">
<tr>
<th>UserName</th>
<th>Phone</th>
<th>Email</th>
<th>Gender</th>
</tr>
</thead>
<tbody>
<React.Fragment>
{
readingStateData.search.map(user =>{
return(
<tr>
<td>{user.UserName}</td>
<td>{user.PhoneNumber}</td>
<td>{user.email}</td>
<td>{user.gender}</td>
</tr>
)
})
}
</React.Fragment>
</tbody>
</table>
</React.Fragment>
)
}
export default Search;
import { type } from 'os';
import React, { ChangeEvent } from 'react'
type searchData = {
handleSearch : (e:ChangeEvent<HTMLInputElement>) => void;
}
const SearchingData:React.FC<searchData> = ({handleSearch}) => {
const UpdateData =(e:ChangeEvent<HTMLInputElement>)=>{
//console.log(e);
handleSearch(e)
}
return (
<React.Fragment>
<div>
<input type="text" onChange={UpdateData} />
</div>
</React.Fragment>
)
}
export default SearchingData
You need to have a state which will hold the filterData value. And set the initialValue of the state to the Data from the store
const [ dataToDisplay, setDataToDisplay ] = useState(readingStateData?.search || []);
Add a second useEffect which looks for the change in the readingStateData?.search. Initially you have the search as [] but once there is data we need to sync that data with the components's internal state.
useEffect(() => {
if(readingStateData?.search?.length > 0){
setDataToDisplay(readingStateData?.search)
}
}, [readingStateData?.search])
Now inside your handleChange you can update the state
const handlesearching =(e:ChangeEvent<HTMLInputElement>)=>{
const newDataToDisplay = e.target.value ? dataToDisplay.filter(user =>user.UserName.toLowerCase().startsWith(e.target.value)) : readingStateData?.search
setDataToDisplay(newDataToDisplay);
}
Now while rendering map over this dataToDisplay instead readingStateData?.search
dataToDisplay.map((user) => {
return (
<tr>
<td>{user.UserName}</td>
<td>{user.PhoneNumber}</td>
<td>{user.email}</td>
<td>{user.gender}</td>
</tr>
);
});
you can make your input as controlled input and have its value being read from the state
const Search = () => {
let dispatch = useDispatch();
let readingStateData: searchReducsers.ISearch = useSelector(
(state: {searchingData: searchReducsers.ISearch}) => {
return state.searchingData;
}
);
const [searchText, setSearchText] = useState('');
useEffect(() => {
console.log(readingStateData.search);
dispatch(searchActions.fetchUser());
}, []);
const handlesearching = (e: ChangeEvent<HTMLInputElement>) => {
setSearchText(e.target.value);
};
const dataToDisplay = searchText.trim().length > 0
? readingStateData?.search.filter((user) =>
user.UserName.toLowerCase().startsWith(searchText)
)
: readingStateData?.search;
return (
<React.Fragment>
<SearchingData handleSearch={handlesearching} searchText={searchText} />
{dataToDisplay.map((user) => {
return (
....
);
})}
</React.Fragment>
);
};
// In your Search Component add another prop called searchText
type searchData = {
handleSearch : (e:ChangeEvent<HTMLInputElement>) => void;
searchText: string;
}
const SearchingData:React.FC<searchData> = ({handleSearch, searchText}) => {
const UpdateData =(e:ChangeEvent<HTMLInputElement>)=>{
//console.log(e);
handleSearch(e)
}
return (
<React.Fragment>
<div>
<input type="text" value={searchText} onChange={UpdateData} />
</div>
</React.Fragment>
)
}
export default SearchingData
I am trying to build a search and sorting functionality for the table content. I don't want to use package as I am trying to learn and see how the react search work. I have the following that loads the content from payloads
import React, {useState, useEffect} from 'react'
import '../css/about.css';
import Pagination from '../components/Pagination'
function About() {
const [userData, setUserData] = useState([]);
const [loading , setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [postsPerPage, setPostsPerPage] = useState(5);
const [search, setSearch] = useState("");
async function getData()
{
let response = await fetch('https://api.github.com/users');
let data = await response.json();
// setUserData(data)
return data;
}
//call getData function
getData()
.then(data => console.log(data)
);//
useEffect(() => {
setLoading(true)
getData()
.then(
data => {
setUserData(data) }
)
.catch(error => {
console.log(error);
})
}, [])
// Get current posts
const indexOfLastPost = currentPage * postsPerPage;
const indexOfFirstPost = indexOfLastPost - postsPerPage;
const currentPosts = userData.slice(indexOfFirstPost, indexOfLastPost);
// changw page
const paginate = (pageNumber) => setCurrentPage(pageNumber);
// Search Table
const handleFilterChange = e => {
const value = e.target.value || undefined;
if( search !== "" && userData.login.indexOf(search.toLowerCase()) === -1 ) {
return null;
}
setSearch(value)
}
return (
<div className="container">
<div>
<input value={search}
onChange={handleFilterChange}
placeholder={"Search"}
/>
<table>
<thead>
<tr>
<td>id</td>
<td>avatar_url</td>
<td>events_url</td>
<td>followers_url</td>
<td>following_url</td>
<td>gists_url</td>
<td>gravatar_id</td>
<td>html_url</td>
<td>login</td>
<td>node_id</td>
<td>organizations_url</td>
<td>received_events_url</td>
<td>repos_url</td>
<td>site_admin</td>
<td>starred_url</td>
<td>subscriptions_url</td>
<td>type</td>
<td>url</td>
</tr>
</thead>
<tbody>
{
currentPosts.map((item, index) => (
<tr key={index}>
<td>{item.id}</td>
<td>{item.avatar_url}</td>
<td>{item.events_url}</td>
<td>{item.followers_url}</td>
<td>{item.following_url}</td>
<td>{item.gists_url}</td>
<td>{item.gravatar_id}</td>
<td>{item.html_url}</td>
<td>{item.login}</td>
<td>{item.node_id}</td>
<td>{item.organizations_url}</td>
<td>{item.received_events_url}</td>
<td>{item.repos_url}</td>
<td>{item.site_admin}</td>
<td>{item.starred_url}</td>
<td>{item.subscriptions_url}</td>
<td>{item.type}</td>
<td>{item.url}</td>
</tr>
))
}
</tbody>
</table>
<Pagination postsPerPage={postsPerPage} totalPosts={userData.length} paginate={paginate} />
</div>
</div>
)
}
export default About
The pagination code is listed below.
import React from 'react'
const Pagination = ({ postsPerPage, totalPosts, paginate }) => {
const pageNumbers = [];
for(let i = 1; i <= Math.ceil(totalPosts / postsPerPage); i++) {
pageNumbers.push(i);
}
return (
<div>
<ul className="pagination">
{pageNumbers.map(number => (
<li key={number} className="page-item">
<a onClick={() => paginate(number)}
href="#" className="page-link">
{number}
</a>
</li>
))}
</ul>
</div>
)
}
export default Pagination
I am think because I used .map within the tbody and the search isn't affecting the content. Though I have no error, only that nothing is displaying from search parameters.
I noticed you didn't create the function to handle the searching. You can use this generic approach which will search across the rows and the column and will match the cases.
function DataSearch(rows) {
const columns = rows[0] && Object.keys(rows[0]);
return rows.filter((row) =>
columns.some((column) => row[column].toString().toLowerCase().indexOf(search.toLowerCase()) > -1)
);
}
instantiate the function
const searchPosts = DataSearch(currentPosts);
Use the searchPosts on your .map function in tbody.
I'm creating a simple React app with graphql, I'm using strapi as the server.
In Strapi I have a Vechicle collection type with a name field and a make field.
In the name field I have car and in make I have volvo, saab, audi, ford
I'd simple like to display car and the makes in a select menu.
The query to get the data
import gql from 'graphql-tag';
export const GET_ALL_CARS = gql `
query Cars{
cars{
_id
name
makes
}
}
`
The react page
import React from 'react';
import { GET_ALL_CARS } from './queries';
import {Vechicles} from './generated/Vechicles'
import { useQuery } from "#apollo/react-hooks";
const App:React.FC = () => {
const {data, loading} = useQuery<Vechicles>(GET_ALL_CARS, {})
if(loading) return <div>Loading</div>
if(!data) return <div>No Data</div>
return (
<div className="App">
<h1>Vehicles</h1>
{
data && data.vechicles && data.vechicles.map(vechicle => (
<div>
//car displays ok
{vechicle?.name}
//trying to create an array here from the string of makes
const makes_arr = {car?.makes ?? ''}.split(',')
<select>
{
makes_arr.map(make = > {
return(
<option>{make}</option>
)
})
}
</select>
</div>
))
}
</div>
);
}
export default App;
How do I simple display the comma seperated list in a select menu.
You can't create an array in this place because it's a part of the JSX. Try to create array inline in your select.
I have removed optional chaining because it's not supported by the editor.
function App() {
const data = {
vehicles: [
{
name: "car",
makes: "volvo, saab, audi, ford"
},
{
name: "car2",
makes: "volvo, saab, audi, ford"
}
]
};
return (
<div className="App">
<h1>Vehicles</h1>
{data &&
data.vehicles &&
data.vehicles.map(vehicle => (
<div>
{vehicle.name}
<select>
{vehicle.makes
.split(",")
.map(make => <option>{make.trim()}</option>)}
</select>
</div>
))}
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root" />
I am new to Gatsbyjs and reactjs and i still don't understand much of how props.and states work.
I am building this simple application that gets a list of customers from an API and a list of tasks for each customer.
I am using Reach/Router to render the components. Everything works as expected as for as to displaying a table with a list of customers and when i click on a customer a new page is rendered which shows a list of task for that said customer.
Now, i am trying to make the table editable. I am starting by trying to simply delete some rows. this is where I am stuck.
edit
I believe that i get the error of Uncaught TypeError: Cannot read property 'data' of null because i am trying to access data (state) which is managed by the fetch.js class. How can I pass the data (state) to the ClientTasks class?
---
I have the following code
index.js
import React from "react"
import { createHistory, LocationProvider } from '#reach/router'
import createHashSource from '../utils/hash-source'
import { ToastContainer } from 'react-toastify';
import "../css/main.css"
import "../css/materialize.css"
import "../css/blackjack.css"
import '../../node_modules/react-toastify/dist/ReactToastify.css';
import { NavBar } from '../components/navBar'
import { Main } from '../components/main'
const isClient = typeof window !== 'undefined' && window;
let source
let history
if (typeof window !== `undefined` ) {
source = createHashSource()
history = createHistory(source)
}
class App extends React.Component {
render() {
return (
<LocationProvider history={history}>
<div className="app" >
<NavBar/>
<Main/>
<ToastContainer position="bottom-right"/>
</div>
</LocationProvider>
)
}
}
export default App
main.js
import React from 'react'
import { Router } from "#reach/router"
import { Home } from '../components/home'
import { Customers } from './customers';
import { ClientTasks } from './clientTasks1';
const Main = () => (
<main className="main container">
<Router className="row">
<Home path='/'/>
<Customers path='customers'/>
<ClientTasks path="customers/tasks/:customerId"/>
</Router>
</main>
)
export { Main }
fetch.js
I am using this file to work as a single class component that helps me fetch data for the tables I am displaying (customers and tasks). It works fine as is. there is probably better ways to do it, but for now this is how i am doing it. Any pointers are welcome.
import React, { Component } from 'react'
const axios = require('axios')
class Fetch extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null,
isLoading: true,
error: null,
};
}
componentDidMount() {
this.setState({ isLoading: true });
axios.get(this.props.url)
.then(result => this.setState({
data: result.data,
isLoading: false
}))
.catch(error => this.setState({
error,
isLoading: false
}));
}
render() {
return this.props.children(this.state);
}
}
export default Fetch
Customers.js
This where i display my customers' table. I have links on each customer and with the help of "reach/router" render the cutomer tasks table.
import React, { Component } from 'react'
import { Link } from "#reach/router"
import Fetch from './fetch'
import { UploadForm } from './upLoadtoS3'
import { AnimatedDiv } from './AnimatedDiv'
const APIURL = `https://SomeAIPURL`
let APIQuery = `customers`
const Customers = () => (
<Fetch url={APIURL + APIQuery}>
{({ data, isLoading, error }) => {
if (!data) {
return (
<div className="progress">
<div className="indeterminate"></div>
</div>)
}
if (error) {
return <p>{error.message}</p>;
}
if (isLoading) {
return (
<div className="progress">
<div className="indeterminate"></div>
</div>)
}
return (
<AnimatedDiv className='col m12 s12'>
<h1> Client List </h1>
<table className='highlight'>
<thead>
<tr>
<th>#</th>
<th>Client ID</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{data.map((customer, i) => (
<tr key={customer.customerid}>
<td>{i + 1}</td>
<td>
<Link to={`tasks/${customer.customerid}`}>{customer.customerid}</Link>
</td>
<td>{customer.enabled}</td>
</tr>
))}
</tbody>
</table>
<UploadForm></UploadForm>
</AnimatedDiv>
);
}
}
</Fetch>
)
export { Customers }
ClientTasks.js
Fetch is called once again and populates the table with data pulled from the API.
I used another file to define the contents of this table. listTasks.js
import React, { Component } from 'react'
import { Link } from "#reach/router"
import Fetch from './fetch'
// import Delete from './delete'
import { AnimatedDiv } from './AnimatedDiv'
import DisplayList from './listTasks'
const APIURL = `https://SomeAIPURL`
const CUSTOMERQUERY = `tasks?customerid=`
const TASKQUERY = `&taskid=`
class ClientTasks extends React.Component {
handleDelete(taskToBeDeleted) {
// console.log(taskToBeDeleted);
let newData = this.state.data.filter((_data) => {
return _data != taskToBeDeleted
});
this.setState({ data: newData })
}
render() {
let customerId = this.props.customerId
return (
<Fetch url={APIURL + CUSTOMERQUERY + customerId}>
{({ data, isLoading, error }) => {
if (!data) {
return (
<div className="progress">
<div className="indeterminate"></div>
</div>)
}
if (error) {
return <p>{error.message}</p>;
}
if (isLoading) {
return (
<div className="progress">
<div className="indeterminate"></div>
</div>)
}
else {
return (
<AnimatedDiv className='col m12 s12'>
<h1>{customerId} Tasks</h1>
<table id="customerList" className="highlight" >
<thead>
<tr>
<th>Task ID</th>
<th>Qty</th>
<th>Asset Category</th>
<th>Asset</th>
<th>Location</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<DisplayList handleDelete={this.handleDelete.bind(this)} data={data}/>
</table>
<Link to='/customers'> Back to Client List ... </Link>
</AnimatedDiv>
)
}
}
}
</Fetch>
)
}
}
export { ClientTasks }
>
Here i have an onClick function that runs handleDelete inside the ClientTasks.js file.
If i console.log(taskstobedeleted) then the console shows me the contents of the row that i am trying to delte. This is as for as i get. Then i am trying to use the following function in clientasks.js but i get an error in the console the says Cannot read property 'data' of null) and i believe that is because the props of data are the accessible that this clientTasks class.
I am still learning and there are better ways to structure the code, but i have hit a wall and i don't want to rebuild the app from scratch, if i did i would probably use redux, but that is another lesson for later. I figured that this project of mine is small enough and does not need Redux yet.
Can somehow give me any pointers of how to delete the rows of my table?
handleDelete(taskToBeDeleted) {
// console.log(taskToBeDeleted);
let newData = this.state.data.filter((_data) => {
return _data != taskToBeDeleted
});
this.setState({ data: newData })
}
Listtasks.js
import React from 'react'
import { Icon } from 'react-icons-kit'
import { ic_delete_forever } from 'react-icons-kit/md/ic_delete_forever'
export default class DisplayList extends React.Component {
render() {
return (
<tbody>
{this.props.data.map((task) => (
<tr key={task.taskid}>
<td>{task.taskid}</td>
<td>{task.qty}</td>
<td>{task.category}</td>
<td>{task.asset}</td>
<td>{task.location}</td>
<td>{task.enabled}</td>
<td>
<button style={{ padding: '0px', background: 'transparent', border: '0', cursor: 'pointer' }} onClick={this.props.handleDelete.bind(this, task)} >
<Icon style={{ color: 'red' }} icon={ic_delete_forever} />
</button>
</td>
</tr>
))}
</tbody>
)
}
}
I think your function should be like this:
handleDelete(taskToBeDeleted) {
// console.log(taskToBeDeleted);
let newData = this.state.data.filter((_data) => _data.taskid != taskToBeDeleted.taskid});
this.setState({ data: newData })
}
If your console is giving you object that you wanted, then, first, you do not need return in arrow function since return is implicit. Second, all of your tasks have been returned because you were asking for two objects are they the same which will always be false even if they have the same key value pairs inside of them. They have different references. That is why I used id since I suppose the value of that key is number and you can evaluate that with operator == or !=