Why my Firebase get() function fires twice inside React component? - reactjs

In the initialize file, I code like this.
import { initializeApp } from 'firebase/app';
import { getDatabase, ref, child, get } from "firebase/database";
const config = {
};
const app = initializeApp(config);
const db = getDatabase(app);
const dbRef = (ref(db));
get(child(dbRef, "shop_data")).then((snap) => {
console.log(snap.val())
})
export {dbRef};
From here, I receive one result from console.log
Now, in my Store component, I only put the get() function in
import '../css/StoreSelectable.css';
import { dbRef } from '../js/firebase_init';
import { getDatabase, ref, child, get } from "firebase/database";
function StoreSelectable(){
const getStore = () => {
get(child(dbRef, "shop_data")).then((snap) => {
console.log(snap.val())
})
return;
}
return(
<div className="Store-Selectable">
<table id="place_option" align="center" style={{tableLayout: 'fixed'}} className="radioButtons collapseBorder">
<tbody>
{getStore()}
</tbody>
</table>
</div>
);
}
export default StoreSelectable;
Now, firebase function fires twice.
Edit 10/6/2022
I tried useEffect, but it still gets the data twice. I really do not want to use Firestore since I have to rewrite a lot of codes. What should I do in this situation?
import "../css/StoreSelectable.css";
import { dbRef } from "../js/firebase_init";
import { getDatabase, ref, child, get } from "firebase/database";
import { useEffect, useState } from "react";
function StoreSelectable() {
const pin = {
TestiKirppis: ["Jarii1", "spr1234", "6899bc73da4ace09"],
Vaasa: ["D0ED5D57F47580F2", "spr9876", "Vas183"],
Seinäjoki: ["a1a1a1a1a1a1a1a1", "spr9999", "Seina19"],
Kokkola: ["regT863", "spr0000", "b4b8bb4ceeaa2aee"],
};
const [count, setCount] = useState([]);
useEffect(() => {
const getStore = () => {
get(child(dbRef, "shop_data")).then((snap) => {
let val = snap.val();
Object.keys(val).forEach((key) => {
setCount((count) => [...count, val[key].name]);
})
});
}
getStore();
}, []);
return (
<div className="Store-Selectable">
<table
id="place_option"
align="center"
style={{ tableLayout: "fixed" }}
className="radioButtons collapseBorder"
>
<tbody>{count.map((data) => {return (<p>{data}</p>)})}</tbody>
</table>
</div>
);
}
export default StoreSelectable;

I think it is because of strict mode in react 18. If you remove it, the issue will be resolved.
Please check : multiple execution of files leading to multiple server calls in react js

Related

React hook "useMemo" with array as dependency

I am new to react (that I use with typeScript) and I am facing an issue with the use of the useMemo hook.
Here is my fetching service:
export default class FetchingService {
datas: Data[] = [];
constructor() {
this.fetch();
}
async fetch(): Promise<Data[]> {
const d = // await an async array from an api, using Array.flat()
this.datas = d;
console.log(this.datas);
return d;
}
}
In a component, I try to watch for change of the datas attribute of my service:
import fetchingService from '../services/fetchingService.ts';
const Home: React.FC = () => {
const ds: Data[];
const [datas, setDatas] = useState(ds);
const fetchDatas = useMemo(() => {
console.log('Render datas', fetchingService.datas?.length)
setDatas(fetchingService.datas);
return fetchingService.datas;
}, [fetchingService.datas]);
return (
<ul>{datas.map(d => {
return (
<li key={d.id}>{d.id}</li>
);
</ul>
);
}
The problem I am facing is that the useMemo hook is not recompouted when the datas attribute changes within my fetchService. I am pretty sure that my FetchingService.fetch() function works because the console.log within the fetch function always display the fetched datas.
The observed behavior is that sometimes datas are well rendered (when fetch ends before rendering ...), but sometimes it isn't.
The expected one is that datas are rendered every time and only on refresh, exept when datas are modified
I also tried to put the length of the data array as a dependency in useMemo, but in both cases it doesn't work and I have a warning in my IDE, telling me it is an unnecessary dependency.
I don't really understand if it is a typescript or a specific react behavior issue. I think the reference of the datas attribute should change at the end of the fetch (or at least its length attribute ...), but tell me if I am wrong.
I do appreciate every help !
in fetchingService, when datas change, probably the dependency cannot be accepted. You can use a custom hook in stead of it.
You can use this source about useMemo: useMemo with an array dependency?
import { useState, useLayoutEffect, useCallback } from "react";
export const useFetchingService = () => {
const [fetchedData, setFetchedData] = useState([]);
const fetch = useCallback(async () => {
const d = await new Promise((res, rej) => {
setTimeout(() => {
res([1, 2, 3]);
}, 5000);
}); // await an async array from an api, using Array.flat()
setFetchedData(d);
}, []);
useLayoutEffect(() => {
fetch();
}, []);
return [fetchedData];
};
useLayoutEffect runs before rendering
using:
const [fetchData] = useFetchingService();
const fetchDatas = useMemo(async () => {
console.log("Render datas", fetchData.length);
setDatas(fetchData);
return fetchData;
}, [fetchData]);
You can also use this directly without 'datas' state.
I hope that this will be solution for you.
So I put together a codesandbox project that uses a context to store the value:
App.tsx
import React, { useState, useEffect, createContext } from "react";
import Home from "./Home";
export const DataContext = createContext({});
export default function App(props) {
const [data, setData] = useState([]);
useEffect(() => {
const get = async () => {
const d = await fetch("https://dummyjson.com/products");
const json = await d.json();
const products = json.products;
console.log(data.slice(0, 3));
setData(products);
return products;
};
get();
}, []);
return (
<div>
Some stuff here
<DataContext.Provider value={{ data, setData }}>
<Home />
</DataContext.Provider>
</div>
);
}
Home.tsx
import React, { FC, useMemo, useState, useEffect, useContext } from "react";
import { DataContext } from "./App";
import { Data, ContextDataType } from "./types";
const Home: FC = () => {
const { data, setData }: ContextDataType = useContext(DataContext);
return (
<>
<ul>
{data.map((d) => {
return (
<li key={d.id}>
{d.title}
<img
src={d.images[0]}
width="100"
height="100"
alt={d.description}
/>
</li>
);
})}
</ul>
</>
);
};
export default Home;
This was my first time using both codesandbox and typescript so I apologize for any mistakes

firestore get and show data with react

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

React custom hook state not 'always there'

I thought had a better grasp of hooks but I've clearly got something wrong here. Not all of the character objects will have what I'm trying to get but it wont work with those that do.
I cna't even build in a check for character.comics.available. Same errors appear. I'm presuming I'm getting them before the state is set? But {character.name} always works.
CharacterDetail.js
import React from "react";
import { useParams } from "react-router-dom";
import useCharacter from "../hooks/useCharacter";
const CharacterDetail = () => {
// from the Route path="/character/:id"
const { id } = useParams();
// custom hook. useCharacter.js
const [character] = useCharacter(id);
// this only works sometimes but errors if i refresh the page
// console.log(character.comics.available);
return (
<div>
<h2 className="ui header">Character Details</h2>
<p>Works every time: {character.name}</p>
<div className="ui segment"></div>
<pre></pre>
</div>
);
};
export default CharacterDetail;
Custom hook useCharacter.js
import { useState, useEffect } from "react";
import marvel from "../apis/marvel";
const useCharacter = (id) => {
const [character, setCharacter] = useState({});
useEffect(() => {
loadItem();
return () => {};
}, [id]);
const loadItem = async (term) => {
const response = await marvel.get(`/characters/${id}`);
console.log(response.data.data.results[0]);
setCharacter(response.data.data.results[0]);
};
return [character];
};
export default useCharacter;
error when console is uncommented
Uncaught TypeError: Cannot read property 'available' of undefined
at CharacterDetail (CharacterDetail.js:11)
...
Here is the character object.
thanks to #Nikita for the pointers. Settled on this...
CharacterDetail.js
import React from "react";
import { useParams } from "react-router-dom";
import useCharacter from "../hooks/useCharacter";
const CharacterDetail = () => {
const { id } = useParams();
// custom hook. useCharacter.js
const { isLoading, character } = useCharacter(id);
const isArray = character instanceof Array;
if (!isLoading && isArray === false) {
console.log("isLoading", isArray);
const thumb =
character.thumbnail.path +
"/portrait_uncanny." +
character.thumbnail.extension;
return (
<div>
<h2 className="ui header">{character.name}</h2>
<img src={thumb} />
<div className="ui segment">{character.comics.available}</div>
<div className="ui segment">{character.series.available}</div>
<div className="ui segment">{character.stories.available}</div>
</div>
);
}
return <div>Loading...</div>;
};
export default CharacterDetail;
useCharacter.js
import { useState, useEffect } from "react";
import marvel from "../apis/marvel";
function useCharacter(id) {
const [character, setCharacter] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setIsLoading(true);
const fetchData = async () => {
setIsLoading(true);
await marvel
.get(`/characters/${id}`)
.then((response) => {
/* DO STUFF WHEN THE CALLS SUCCEEDS */
setIsLoading(false);
setCharacter(response.data.data.results[0]);
})
.catch((e) => {
/* HANDLE THE ERROR (e) */
});
};
fetchData();
}, [id]);
return {
isLoading,
character,
};
}
export default useCharacter;

ReactJs only rendering one img element from mapping an array of URLs

I am reading image data from firebase storage and getting the URLs of the images in an array.
I console logged the array. It is fine.
I made a variable of img elements through map() function on that array.
That variable is also fine.
But I am not able to render more than one in the component. Only the last image tag renders from the array.
import React, { useRef, useState, useEffect } from 'react'
import { Card, Button, Alert } from 'react-bootstrap'
import { Link, useHistory } from 'react-router-dom'
import { UseAuth } from '../context/AuthContex'
import app from './../firebase'
import { db } from './../firebase'
import './Dashboard.css'
function Dashboard() {
const [error, setError] = useState('')
const { currentUser, logout } = UseAuth()
const history = useHistory()
const picURLS = []
const [photo, setPhoto] = useState()
async function getPics() {
const DBRef = db.collection('pics');
const snapshot = await DBRef.where('author', '==', currentUser.uid).get();
if (snapshot.empty) {
console.log('No matching documents.');
return;
}
snapshot.forEach(doc => {
var storage = app.storage();
var gsReference = storage.refFromURL('<bucket address>' + doc.data().filename)
gsReference.getDownloadURL().then(function (url) {
picURLS.push(url);
}).catch(function (error) {
console.log(error)
});
});
}
useEffect(() => {
getPics()
console.log('getPIcs() triggered.')
console.log(picURLS)
setPhoto(picURLS.map(postdata => (
<img className='photoOfOrder' key={postdata} src={postdata} alt={postdata} />)))
console.log(photo)
}, [])
return (
<div>
<div>{photo}</div>
<div className="menu pmd-floating-action" role="navigation">
<Link to='/upload-pic' className="pmd-floating-action-btn btn pmd-btn-fab pmd-btn-raised pmd-ripple-effect btn-primary" data-title="Splash New Image?" href="javascript:void(0);">
<span className="pmd-floating-hidden">Primary</span>
<i className="material-icons pmd-sm">add</i>
</Link>
</div>
</div>
)
}
export default Dashboard
It would be better to add picURLS to your state variables because useEffect runs only once with empty dependencies array and setPhoto(picURLS.map) surely would work with an empty picURLS array before it will be filled. So your photo var surely would be empty. You should call your map in the render function;
{picURLS.map(postdata => (
<img className='photoOfOrder' key={postdata} src={postdata} alt={postdata} />))}
Try this code
import React, { useRef, useState, useEffect } from 'react'
import { Card, Button, Alert } from 'react-bootstrap'
import { Link, useHistory } from 'react-router-dom'
import { UseAuth } from '../context/AuthContex'
import app from './../firebase'
import { db } from './../firebase'
import './Dashboard.css'
function Dashboard() {
const [error, setError] = useState('')
const { currentUser, logout } = UseAuth()
const history = useHistory()
const [picURLS, setPicURLS] = useState([])
async function getPics() {
const DBRef = db.collection('pics');
const snapshot = await DBRef.where('author', '==', currentUser.uid).get();
if (snapshot.empty) {
console.log('No matching documents.');
return;
}
const newUrls = [];
snapshot.forEach(doc => {
var storage = app.storage();
var gsReference = storage.refFromURL('<bucket address>' + doc.data().filename)
gsReference.getDownloadURL().then(function (url) {
newUrls.push(url);
}).catch(function (error) {
console.log(error)
});
});
setPicURLS(newUrls);
}
useEffect(() => {
getPics()
console.log('getPIcs() triggered.')
console.log(picURLS)
}, [])
return (
<div>
<div>{
picURLS.map(postdata => (
<img className='photoOfOrder' key={postdata} src={postdata} alt={postdata} />))}
</div>
<div className="menu pmd-floating-action" role="navigation">
<Link to='/upload-pic' className="pmd-floating-action-btn btn pmd-btn-fab pmd-btn-raised pmd-ripple-effect btn-primary" data-title="Splash New Image?" href="javascript:void(0);">
<span className="pmd-floating-hidden">Primary</span>
<i className="material-icons pmd-sm">add</i>
</Link>
</div>
</div>
)
}
export default Dashboard
photo is array of React components. you need to loop over photo using map array method again to show in render.
{photo.map((img) => { img }}

Correct way to use useEffect() to update when data changes

The useEffect below renders, fetches data, and displays it once (using an empty array for 2nd parameter in useEffect).
I need it to rerun useEffect everytime the user changes data to the database (when user uses axios.post).
What i've tried
using [tickets], but that just causes the useEffect to run infinitly
also using [tickets.length] and [tickets, setTickets]
trying to use props as parameter but didnt find anything useful
import React, { useState, createContext, useEffect } from "react";
import axios from "axios";
export const TicketContext = createContext();
export const TicketProvider = (props) => {
console.log(props);
const [tickets, setTickets] = useState([]);
useEffect(() => {
getTickets();
console.log("1", { tickets });
}, []);
const getTickets = async () => {
const response = await axios.get("http://localhost:4000/tickets/");
setTickets(response.data);
};
return <TicketContext.Provider value={[tickets, setTickets]}>{props.children}
</TicketContext.Provider>;
};
import React from "react";
import { useState, useEffect, useContext } from "react";
import Ticket from "../Ticket";
import { TicketContext } from "../contexts/TicketContext";
import AddBacklog from "../addData/AddBacklog";
const TicketDisplay = (props) => {
const [tickets, setTickets] = useContext(TicketContext);
return (
<div className="display">
<p>Antony Blyakher</p>
<p>Number of Tickets: {tickets.length}</p>
<div className="backlog">
<h1>Backlog</h1>
{tickets.map((currentTicket, i) => (
<div className="ticketBlock">
<Ticket ticket={currentTicket} key={i} />
</div>
))}
</div>
</div>
);
const AddBacklog = (props) => {
const [tickets, setTickets] = useState("");
...
axios.post("http://localhost:4000/tickets/add", newTicket).then((res) => console.log(res.data));
setTickets((currentTickets) => [...currentTickets, { name: name, status: "backlog", id: uuid() }]);
};
You'll need to watch for tickets and return if it has data to not cause infinite loop:
useEffect(() => {
if (tickets.length) return // so, we call just once
getTickets();
console.log("1", { tickets });
}, [tickets]);
const fetchData = () => {
axios.get("http://localhost:7000/api/getData/").then((response) => {
console.log(response.data);
if (response.data.success) {
SetIsLoading(false);
}
setDataSource(response.data.data);
});
};
useEffect(() => {
fetchData();
if (fetchData.length) fetchData();
}, [fetchData]);
by this you can fetch the data in real-time as any change in data occurs.

Resources