firestore get and show data with react - reactjs

Hey guys I'm trying to show the data I get from firestore.
When I'm saving the code in the IDE and I'm on the current page, it is working.
But if then I go to another page/refresh the browser - it doesn't render/render in time and render the "hold" I set him to return
the code:
import React, { useState, useEffect } from 'react'
import firebase from 'firebase';
import { useAuth } from '../contexts/AuthContext';
export default function Cart() {
const [userMail, setUserMail] = useState(undefined)
const [userCart, setUserCart] = useState(undefined)
const user = useAuth()
const userDoc = firebase.firestore().collection("cart").doc(userMail)
useEffect(() => {
if (user.currentUser) {
setUserMail(user.currentUser.email, console.log(userMail))
userDoc.get().then((doc) => {
if (doc.exists) {
let cart = doc.data()
setUserCart(cart)
}
})
}
}, [])
if (userCart === undefined) return <h1>hold</h1>
const { item } = userCart
console.log(item);
return (
<main className="main-cart">
//here im try to make sure it got the data befor render//
{item && item.map(item => {
return (
<div key={item.itemId}>
<h3>{item.name}</h3>
</div>
)
})}
</main>
)
}

i just had to replace the 2nd parameter from the useEffect to userCart

Related

Weird React Native Behavior

I have been building this mobile app with React Native/Expo and Firestore for a while now. The app schedules study sessions, and when a study session is active, a Pomodoro timer screen is to be shown, and when a session is inactive, the main homepage should be shown. However, I have been having trouble implementing this after a refactor to my database structure. Currently, for each schedule, a document is created in a subcollection corresponding to the user's UID. So, the path for a schedule would be "Users/(auth.currentUser.uid)/S-(auth.currentUser.uid)/(document id). To implement this feature, I have tried to run a function every second that checks through all of the documents and finds out whether a schedule is active, and if it is, it shows the Pomodoro timer screen. However, there is some weird behavior occurring. I am reading the database once using a Context Api, and the data shows perfectly in the screen where you view all your schedules, however in the function it is showing as an empty object. I have a feeling that it might be due to the bounds of the Context, however I am not sure. Does anyone know why?
CurrentDataProvider.js
import React, { createContext, useEffect, useState } from "react";
import {
doc,
getDocs,
onSnapshot,
collection,
query,
} from "firebase/firestore";
import { db, auth } from "../config/firebase";
export const CurrentDataContext = createContext({});
const CurrentDataProvider = ({ children }) => {
const [data, setData] = useState({});
useEffect(async () => {
if (auth.currentUser) {
const ref = query(
collection(
db,
"Users",
auth.currentUser.uid,
`S-${auth.currentUser.uid}`
)
);
const unsub = onSnapshot(ref, (querySnap) => {
let dat = {};
querySnap.forEach((doc) => {
dat[doc.id] = doc.data();
});
setData(dat);
});
return () => unsub;
}
}, []);
return (
<CurrentDataContext.Provider value={{ data, setData }}>
{children}
</CurrentDataContext.Provider>
);
};
export { CurrentDataProvider };
function being used to read schedules
const readSchedules = () => {
const currentTime = new Date();
Object.keys(data).forEach((key) => {
const clientSeconds =
currentTime.getHours() * 3600 + currentTime.getMinutes() * 60;
const startTimestamp = new Timestamp(data[key]["start"]["seconds"]);
const endTimestamp = new Timestamp(data[key]["end"].seconds);
const utcStartSeconds = startTimestamp.seconds;
const utcEndseconds = endTimestamp.seconds;
console.log(utcStartSeconds, clientSeconds, utcEndseconds);
const greaterTime = clientSeconds > utcStartSeconds;
const lessTime = clientSeconds < utcEndseconds;
const trueDay = data[key][dayOfWeekAsString(currentTime.getDay())];
if (trueDay) {
if (greaterTime && lessTime) {
setPomodoro(true);
setCurrentSchedule(key.toString());
console.log(`Schedule ${currentSchedule} selected!`);
return;
}
}
});
setPomodoro(false);
};
RootStack.js
import SplashScreen from "../screens/SplashScreen";
import AuthStack from "./AuthStack";
import React, { useState, useContext, useEffect } from "react";
import { View, ActivityIndicator } from "react-native";
import { auth } from "../config/firebase";
import { onAuthStateChanged } from "firebase/auth";
import { UserContext } from "./../components/UserProvider";
import { NavigationContainer } from "#react-navigation/native";
import FinalStack from "./MainStack";
import { CurrentDataProvider } from "../components/CurrentDataProvider";
const RootStack = () => {
const { user, setUser } = useContext(UserContext);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const authListener = auth.onAuthStateChanged(async (user) => {
try {
await (user ? setUser(user) : setUser(null));
setTimeout(() => {
setIsLoading(false);
}, 3000);
} catch (err) {
console.log(err);
}
});
return authListener;
}, []);
if (isLoading) {
return <SplashScreen />;
}
return (
<NavigationContainer>
{user ? (
<CurrentDataProvider>
<FinalStack />
</CurrentDataProvider>
) : (
<AuthStack />
)}
</NavigationContainer>
);
};
export default RootStack;
Thanks for all your help!

Get single data from state in React

I'm trying to make a page to show the details of each video.
I fetched multiple video data from the back-end and stored them as global state.
This code works if I go to the page through the link inside the app. But If I reload or open the URL directory from the browser, It can not load the single video data.
How should I do to make this work?
Thanx
Single Video Page
import { useState, useEffect, useContext } from "react";
import { useParams } from "react-router-dom";
import { VideoContext } from "../context/videoContext";
const SingleVideo = () => {
let { slug } = useParams();
const [videos, setVideos] = useContext(VideoContext);
const [video, setVideo] = useState([]);
useEffect(() => {
const result = videos.find((videos) => {
return videos.uuid === slug;
});
setVideo((video) => result);
}, []);
return (
<>
<div>
<h1>{video.title}</h1>
<p>{video.content}</p>
<img src={video.thumbnail} alt="" />
</div>
</>
);
};
export default SingleVideo;
Context
import React, { useState, createContext, useEffect } from "react";
import Axios from "axios";
import { AxiosResponse } from "axios";
export const VideoContext = createContext();
export const VideoProvider = (props) => {
const [videos, setVideos] = useState([]);
const config = {
headers: { "Access-Control-Allow-Origin": "*" },
};
useEffect(() => {
//Fetch Vidoes
Axios.get(`http://localhost:5000/videos`, config)
.then((res: AxiosResponse) => {
setVideos(res.data);
})
.catch((err) => {
console.log(err);
});
}, []);
return (
<VideoContext.Provider value={[videos, setVideos]}>
{props.children}
</VideoContext.Provider>
);
};
I think the reason is because when you refresh the app, you fetch the video data on context and the useEffect on your single video page component runs before you receive those data.
To fix you can simply modify slightly your useEffect in your single video component to update whenever you receive those data:
useEffect(() => {
if (videos.length) {
const result = videos.find((videos) => {
return videos.uuid === slug;
});
setVideo((video) => result);
}
}, [videos]);

dispatch inside useEffect opposite result

I am displaying a foto in the front using Leigh Halliday's Image Previews in React with FileReader from https://www.youtube.com/watch?v=BPUgM1Ig4Po and everything is super BUT:
1.I want to get information from the image is displaying, exactly the base64 info, and have it then globally in my reactjs app.
2.for that reason I made a Context, i configured it ok BUT:
when I am doing dispatch inside a useEffect I want the image rendering and the info store in my variable globally
but I have one thing or another
if my image renders ok in my front, I can not obtain the value of my dispatch and viceversa
this is the code of my component:
import React, { useContext, useEffect, useRef, useState } from 'react'
import { AuthContext } from '../../auth/AuthContext'
import { types } from '../../types/types'
export const ButtonLoadFoto = () => {
const { dispatchFoto } = useContext(AuthContext)
const [image, setImage] = useState('')
const [preview, setPreview] = useState('')
const [status, setStatus] = useState(false)
useEffect(() => {
if (image) {
const reader = new FileReader()
reader.onloadend = () => {
setPreview(reader.result)
}
reader.readAsDataURL(image)
setStatus(true)
} else {
setPreview('')
}
}, [image])
// useEffect(() => {
// if (status) {
// dispatchFoto({
// type: types.foto,
// payload: {
// foto: preview.split(',')[1]
// }
// })
// }
// return () => setStatus(false)
// }, [preview])
const fileInputRef = useRef()
const handleRef = (e) => {
e.preventDefault()
fileInputRef.current.click()
}
const handleFile = (e) => {
const file = e.target.files[0]
if (file && file.type.substr(0, 5) === 'image') {
setImage(file)
}
}
return (
<div className='load-input '>
{
preview
?
(<img src={preview} alt='' onClick={() => setImage('')} />)
:
(<button
className='alert alert-danger'
onClick={handleRef}>
foto
</button>
)
}
< input
type='file'
style={{ display: 'none' }}
ref={fileInputRef}
accept='image/*'
onChange={handleFile}
/>
</div>
)
}
in the code above if you put away the comments we will have the information we want but the foto won t display at all
thanks all for your time , I really appreciate!
EDIT
this is the main component
import React, { useEffect, useReducer } from 'react'
import { AuthContext } from './auth/AuthContext'
import { fotoReducer } from './components/formScreen/fotoReducer'
import { AppRouter } from './routers/AppRouter'
const initImage = () => {
return { foto: '' }
}
export const CMI = () => {
const [foto, dispatchFoto] = useReducer(fotoReducer, {}, initImage)
return (
<div>
<AuthContext.Provider value={{
foto,
dispatchFoto
}}>
<AppRouter />
</AuthContext.Provider>
</div>
)
}
this is the componenent I use
import React, { useContext} from 'react'
import { ButtonLoadFoto } from '../components/formScreen/ButtonLoadFoto'
import { AuthContext } from '../auth/AuthContext'
export const FormScreen = () => {
const { foto } = useContext(AuthContext)
}
return (
<div>
<ButtonLoadFoto/>
</div>
)
as I said : if a render the image I can not have the information and viceversa...
when I use dispatch I don t know I it brokes the image render
thanks in advance

REACT Context Maximum update depth exceeded

I have trouble with react context.
Especially with function getProductCategory which I use in the second component.
My React context provider looks like :
import React, { createContext, useState, useEffect } from "react";
import data from "./data";
export const ProductContext = createContext();
const ProductContextProvider = ({ children }) => {
const [productsCategory, setProductCategory] = useState();
const [products, setProducts] = useState();
useEffect(() => {
setProducts(data);
});
function getProductCategory(category) {
const productFromCategory = data.filter(
(product) => product.type === category
);
setProductCategory(productFromCategory); //this line is causing the problem
console.log(productFromCategory);
return productsCategory;
}
const getProduct = (product) => {
const currentProduct = products
? products.filter((item) => item.slug === product)[0]
: undefined;
return currentProduct;
};
return (
<ProductContext.Provider
value={{ getProduct, getProductCategory }}
>
{children}
</ProductContext.Provider>
);
};
export default ProductContextProvider;
I want to get access to my context from another component Mats which is a page.
import React, { useContext, useEffect } from "react";
import { ProductContext } from "../../context";
/accesoriesComponent/CategoryTitle/CategoryTitle";
import Filters from "../../components/accesoriesComponent/Filters/Filters";
import ProductWrapper from "../../components/accesoriesComponent/ProductWrapper/ProductWrapper";
export default function Mats(props) {
const { getProductCategory } = useContext(ProductContext);
const mats = getProductCategory("mats");
return (
<div>
<div style={{ display: "flex" }}>
<Filters />
{mats && <ProductWrapper products={mats} />}
</div>
</div>
);
}
When I want to visit page Mats, I get error "Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside component WillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.".
I marked the line of code in the first code, that courses the problem .
I don't know how to handle it.
Any suggestions ?
Based on your code:-
instead of return-ing productsCategory from your getProductCategory function. Send your productsCategory as ProductContext.Provider values props
Btw, your useEffect should have [] or empty array as dependency
ProductContextProvider.js:-
import React, { createContext, useState, useEffect } from "react";
import data from "./data";
export const ProductContext = createContext();
const ProductContextProvider = ({ children }) => {
const [productsCategory, setProductCategory] = useState();
const [products, setProducts] = useState();
useEffect(() => {
setProducts(data);
}, []); // making sure it will only run once when rendered
function getProductCategory(category) {
const productFromCategory = data.filter(
(product) => product.type === category
);
setProductCategory(productFromCategory); // you already set the data in here
console.log(productFromCategory);
// return productsCategory; // no need to return this
}
const getProduct = (product) => {
const currentProduct = products
? products.filter((item) => item.slug === product)[0]
: undefined;
return currentProduct;
};
return (
<ProductContext.Provider
value={{ productsCategory, getProduct, getProductCategory }}
>
{children}
</ProductContext.Provider>
);
};
export default ProductContextProvider;
So in Mats.js, just get display your updated ``
export default function Mats(props) {
const { productsCategory, getProductCategory } = useContext(ProductContext);
// Shouldn't do this. Cause it will keep rerender
// const mats = getProductCategory("mats");
// default state of category
const [category, setCategory] = useState('mats')
// use useffect to send the updated products by category
useEffect(() => {
(() => {
if(category) {
getProductCategory(category);
}
})()
}, [category]) // will initiated every time category change
return (
<div>
<div style={{ display: "flex" }}>
<Filters />
{productsCategory && <ProductWrapper products={productsCategory} />}
</div>
</div>
);
}

useEffect Memory Leak in AWS Amplify Storage.get function

I have a pretty simple component in React that gets an image name and then displays it using the code below. It works fine but when I leave the page and then come back to it, I get a memory leak. Perhaps there is something basic about effects I am not understanding:
import React, {useState, useEffect} from 'react';
import {Storage} from 'aws-amplify';
const S3Image = ({photoName, ...props}) => {
const [imageURL, setImageURL] = useState("")
useEffect(() => {
var response = ""
async function s3Fetch() {
if (photoName !== "") {
response = Storage.get(photoName);
const data = await response;
setImageURL(data);
}
}
s3Fetch();
}, [imageURL])
return (
<>
{ imageURL === "" ?
null
:
<img {...props} src={imageURL} alt={photoName} />
}
</>
)};
export default S3Image
If I return a function at the end to clean up like:
return () => usetImageUrl("")
Then it goes in a crazy loop of rendering and then re-rendering.
Thanks for any help!
You are updating the imageURL in the effect and also rerun the effect if the url changes => Loop.
You need set the dependency array to [photoName] since that's the variable you are listening for and is the deciding factor in the fetch function:
import React, {useState, useEffect} from 'react';
import {Storage} from 'aws-amplify';
const S3Image = ({photoName, ...props}) => {
const [imageURL, setImageURL] = useState("")
useEffect(() => {
var response = ""
async function s3Fetch() {
if (photoName !== "") {
response = Storage.get(photoName);
const data = await response;
setImageURL(data);
}
}
s3Fetch();
}, [photoName])
return (
<>
{ imageURL === "" ?
null
:
<img {...props} src={imageURL} alt={photoName} />
}
</>
)};
export default S3Image
If you have a linter installed, it should tell you that as well.
#Domino987 answer was really helpful, but I needed to add the logic with "componentIsMounted" variable to get rid of the warning. We set the variable as false in the cleanup function of the useEffect. This way, we only set the imageUrl if the component is mounted.
import React, { useState, useEffect } from 'react';
import { Storage } from 'aws-amplify';
const S3Image = ({ imgKey, ...props }) => {
const [imageURL, setImageURL] = useState('');
useEffect(() => {
async function s3Fetch() {
if (imgKey !== '') {
try {
return Storage.get(imgKey, { level });
} catch (e) {
console.error(e);
}
}
}
let componentIsMounted = true;
s3Fetch().then((url) => {
if (componentIsMounted) {
if (url) setImageURL(url);
}
});
return () => {
componentIsMounted = false;
};
}, [imgKey]);
return (
<>
{imageURL === '' ? null : (
<img {...props} src={{ uri: imageURL }} />
)}
</>
);
};
export default S3Image;

Resources