How to sync firebase with react functional components - reactjs

I would like to sync my component React with Firebase but i use a functional component i found somes answers in internet but it's seems complicated and doesn't worked for me
and i can't use this : useEffect(() => { base.syncState('/', { context: this, state: 'messages' }) });
my code is
import React,{useEffect, useState} from 'react'
import Formulaire from './component/Formulaire'
import Message from './component/Message'
import {useParams} from 'react-router-dom'
import database from './Base'
import { getDatabase, ref, set,onValue } from "firebase/database";
function App() {
const [state , setState] = useState({
messages : {},
pseudo : useParams().pseudo,
})
const addMessage = (message) =>{
state.messages[`message-${Date.now()}`] = message
setState({pseudo : state.pseudo ,messages : state.messages})
}
const msg = Object.keys(state.messages)
const lastMessages = msg.map(key=>{
return <Message key={key} pseudo={state.messages[key].pseudo} message = {state.messages[key].message} />
})
return (
<div className="container-lg col-4 mt-5">
<div>
{lastMessages}
</div>
<Formulaire length={150} addMessage ={addMessage} pseudo={state.pseudo} />
</div>
)
}
export default App
And my firebaseApp code :
import { initializeApp } from 'firebase/app';
import { getDatabase } from "firebase/database";
// TODO: Replace with your app's Firebase project configuration
const firebaseConfig = {
apiKey: "AIzaSyB2CFjr32PoNdsnfvEgt_AijgE18lNKz2c",
authDomain: "chat-app-42ed5.firebaseapp.com",
projectId: "chat-app-42ed5",
storageBucket: "chat-app-42ed5.appspot.com",
messagingSenderId: "880643875911",
appId: "1:880643875911:web:9d04114b45bb40c2627d62",
measurementId: "G-MP1VZCGRDP"
};
const app = initializeApp(firebaseConfig);
// Get a reference to the database service
const database = getDatabase(app);
export default database

When you mount the App component, you can open a snapshot listener to RTDB, see docs. You do that in useEffect and also return a function from useEffect to destroy the listener when your component gets unmounted. It's called a "cleanup function", see React docs
It would look something like that:
function App() {
useEffect(() => {
const unsubListener = onValue(ref(rtdb_instance, '/YOUR_PATH'), (snapshot) => {
// Probably iterate over `snapshot.val()` and store it in your state
})
// Return cleanup function
return unsubListener;
}, [])
}
Note that the dependency array is empty here, meaning it won't change. Hence, it will only be executed on inital mount.
Also please note that the code above is not tested, so your milage might vary.

I use my function writeUserData() in AddMessage function after submit message to send to REALTIME DATABASE not in UseEffect() and after to get message after refresh page i use onValue() as you say in UseEffect() and in the same time i update my state with setState() ex: if (data) {
setState({messages : data.messages , pseudo : state.pseudo})
} return
import React,{useState,useEffect} from 'react'
import Formulaire from './component/Formulaire'
import Message from './component/Message'
import {useParams} from 'react-router-dom'
import database from './Base'
import { ref, set,onValue } from "firebase/database";
function App() {
const [state , setState] = useState({
messages : {},
pseudo : useParams().pseudo,
})
useEffect(()=>{
const resultDb = ref(database);
onValue(resultDb, (snapshot) => {
const data = snapshot.val();
if (data) {
setState({messages : data.messages , pseudo : state.pseudo})
} return
})
return resultDb
},[])
function writeUserData(message) {
set(ref(database), {
messages :message
});
}
const addMessage = (message) =>{
state.messages[`message-${Date.now()}`] = message
setState({pseudo : state.pseudo ,messages : state.messages})
writeUserData(state.messages)
}
const msg = Object.keys(state.messages)
const lastMessages = msg.map(key=>{
return <Message key={key} pseudo={state.messages[key].pseudo} message = {state.messages[key].message} />
})
return (
<div className="container-lg col-4 mt-5">
<div>
{lastMessages}
</div>
<Formulaire length={150} addMessage ={addMessage} pseudo={state.pseudo} />
</div>
)
}
export default App

Related

How to conditionally render a Next.js component based on whether a request comes from a (Firebase) authenticated user?

Note: I'm using Next.js 13 with the app/ directory.
I'm learning Firebase and Next.js and I'm struggling to understand how to solve a toy problem. Suppose I have a Home() component like this
/app/page.jsx
export default function Home() {
return (
<main>
<h1>Hello World</h1>
<p>This text should only be visible to authenticated users</p>
</main>
)
}
My goal is to conditionally render everything in the <p>...</p> based on whether the user who requested the page is a logged in user. Firebase uses JWT, and Next.js 13 renders this component server side, so I believe this should be possible, but I can't figure out how to do it.
I'm aware of onAuthStateChanged, but to my knowledge, this can only be used client-side. (A savvy user could still view this protected content.) How do I protect this content, server side?
To check if the user is logged in you can use 'onAuthStateChanged' method.
Store this information in the component's state or use it to conditionally render parts of the component.
import React, { useEffect, useState } from 'react';
import firebase from 'firebase/app';
import 'firebase/auth';
export default function Home() {
const [user, setUser] = useState(null);
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => {
setUser(user);
});
return () => unsubscribe();
}, []);
return (
<main>
<h1>Hello World</h1>
{user ? (
<p>authenticated user</p>
) : null}
</main>
);
}
To perform user authentication on server side Next.js gives us 'getServerSideProps' to fetch the user's authentication status
import firebase from 'firebase/app';
import 'firebase/auth';
export default function Home({ user }) {
return (
<main>
<h1>Hello World</h1>
{user ? (
<p>authenticated user</p>
) : null}
</main>
);
}
export async function getServerSideProps(context) {
const user = await new Promise((resolve, reject) => {
firebase.auth().onAuthStateChanged(user => {
resolve(user);
});
});
return {
props: {
user,
},
};
}
updated solution:
Create a server-side route
import firebase from 'firebase/app';
import 'firebase/auth';
import express from 'express';
const app = express();
app.get('/api/user', async (req, res) => {
const user = await new Promise((resolve, reject) => {
firebase.auth().onAuthStateChanged(user => {
resolve(user);
});
});
res.send(user);
});
app.listen(3000, () => {
console.log('Server started at http://localhost:3000');
});
Client side
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export default function Home() {
const [user, setUser] = useState(null);
useEffect(() => {
const fetchUser = async () => {
const res = await axios.get('http://localhost:3000/api/user');
const user = res.data;
setUser(user);
};
fetchUser();
}, []);
return (
<main>
<h1>Hello World</h1>
{user ? (
<p>authenticated user</p>
) : null}
</main>
);
}
I would recommend using NextAuth with Firebase adapter (https://next-auth.js.org/adapters/firebase).
Simply create middleware to catch/filter authenticated paths:
// middleware.ts on the same level as your api/pages directory
import { withAuth } from "next-auth/middleware"
export const config = { matcher: ["/dashboard/:path*"] }
export default withAuth({})
then follow the NextAuth documentation by creating an api route under:
/pages/api/auth/[...nextauth].ts (at the time of writing it is not possible to create apis under the app directory)
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
import { FirestoreAdapter } from "#next-auth/firebase-adapter"
// For more information on each option (and a full list of options) go to
// https://next-auth.js.org/configuration/options
export default NextAuth({
// https://next-auth.js.org/providers
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
}),
],
adapter: FirestoreAdapter({
apiKey: process.env.FIREBASE_API_KEY,
appId: process.env.FIREBASE_APP_ID,
authDomain: process.env.FIREBASE_AUTH_DOMAIN,
databaseURL: process.env.FIREBASE_DATABASE_URL,
projectId: process.env.FIREBASE_PROJECT_ID,
storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
// Optional emulator config (see below for options)
emulator: {},
}),
// ...
});
optionally you can also use useSession to render individual elements based on if the user is logged in or not like this:
import { useSession, signOut } from "next-auth/react"
...
export default function LoginButton() {
const { data: session } = useSession()
if (session) {
return (
<div className={"flex items-center gap-3"}>
<button onClick={() => signOut()}>Sign out</button>
<Link href={"/workspace"}>Dashboard</Link>
</div>
)
}
...
}

Trigger toast from outside React component

I am using PrimeReact's toast component, whose API looks like this:
function App() {
const toast = useRef(null);
useEffect(() => {
toast.current.show({
severity: 'info',
detail: 'Hellope'
});
});
return (
<div className='App'>
<Toast ref={toast} />
</div>
);
}
I would now like to call toast.current.show() from a non-React context. In particular, I have an http() utility function through which all HTTP calls are made. Whenever one fails, I would like to show a toast. What are clean/idiomatic ways to achieve this?
Initialize the toast on the window object.
useLayoutEffect(() => {
window.PrimeToast = toast.current || {};
}, []);
On your fetch or axios handler, use the above object on your error handler
const fakeUrl = "https://api.afakeurl.com/hello";
fetch(fakeUrl)
.then((res) => res.data)
.catch((err) => {
console.error("error fetching request", err);
if (window.PrimeToast) {
window.PrimeToast.show({
severity: "error",
summary: "Error calling https",
detail: "hello"
});
}
});
Updated Sandbox
Reference:
https://www.primefaces.org/primereact/toast/
I would create a toast context that would allow showing toasts
toast-context.js
import "primereact/resources/themes/lara-light-indigo/theme.css";
import "primereact/resources/primereact.css";
import { Toast } from "primereact/toast";
import { createContext, useContext, useRef } from "react";
// create context
const ToastContext = createContext(undefined);
// wrap context provider to add functionality
export const ToastContextProvider = ({ children }) => {
const toastRef = useRef(null);
const showToast = (options) => {
if (!toastRef.current) return;
toastRef.current.show(options);
};
return (
<ToastContext.Provider value={{ showToast }}>
<Toast ref={toastRef} />
<div>{children}</div>
</ToastContext.Provider>
);
};
export const useToastContext = () => {
const context = useContext(ToastContext);
if (!context) {
throw new Error(
"useToastContext have to be used within ToastContextProvider"
);
}
return context;
};
index.js
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import { ToastContextProvider } from "./toast-context";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<StrictMode>
<ToastContextProvider>
<App />
</ToastContextProvider>
</StrictMode>
);
App.js
import { useToastContext } from "./toast-context";
export default function App() {
// use context to get the showToast function
const { showToast } = useToastContext();
const handleClick = () => {
http(showToast);
};
return (
<div className="App">
<button onClick={handleClick}>show toast</button>
</div>
);
}
// pass showToast callback to your http function
function http(showToast) {
showToast({
severity: "success",
summary: "Success Message",
detail: "Order submitted"
});
}
Codesanbox example: https://codesandbox.io/s/beautiful-cray-rzrfne?file=/src/App.js
Here is one solution I have been experimenting with, although I have the impression it isn't very idiomatic. I suppose one could look at it as a "micro-frontend" responsible exclusively for showing toasts.
import ReactDOM from 'react-dom/client';
import { RefObject, useRef } from 'react';
import { Toast, ToastMessage } from 'primereact/toast';
class NotificationService {
private toast?: RefObject<Toast>;
constructor() {
const toastAppRoot = document.createElement('div');
document.body.append(toastAppRoot);
const ToastApp = () => {
this.toast = useRef<Toast>(null);
return <Toast ref={this.toast} />;
};
ReactDOM.createRoot(toastAppRoot).render(<ToastApp />);
}
showToast(message: ToastMessage) {
this.toast!.current!.show(message);
}
}
export const notificationService = new NotificationService();
The simplicity of its usage is what's really nice of an approach like this. Import the service, call its method. It used to be that simple.

Nextjs error - TypeError: Cannot read property 'useState' of null

I keep getting this error
TypeError: Cannot read property 'useState' of null
whenever I try pulling data from the firestore database. The error points to when I am using the useState and this is how my code looks like
import React from 'react'
import { useState } from 'react';
import { db } from '../firebaseconfig'
import { collection, getDocs } from "firebase/firestore";
const reference = collection(db, "students");
const [students, setstudents] = useState([]);
export const getStaticProps = async () => {
const data = await getDocs(reference);
setstudents(data.docs.map(doc => ({...doc.data(), id: doc.id})));
return {
props: students
}
}
function Card({students}) {
return (
<div>
{students.map(student => (
<h1>{student.name}</h1>
))}
</div>
)
}
export default Card
Where could I be going wrong?
In the code snippet you have provided, the state students does not have any other purpose than being sent as props to Card component.
Based on the nextjs docs (https://nextjs.org/docs/basic-features/data-fetching/get-static-props#using-getstaticprops-to-fetch-data-from-a-cms) , modify the code like this and try
import React from 'react'
import { db } from '../firebaseconfig'
import { collection, getDocs } from "firebase/firestore";
const reference = collection(db, "students");
export const getStaticProps = async () => {
const data = await getDocs(reference);
const students=data.docs.map(doc => ({...doc.data(), id: doc.id}));
return {
props: {
students
}
}
}
function Card({students=[]}) {
return (
<div>
{students.map(student => (
<h1>{student.name}</h1>
))}
</div>
)
}
export default Card
From React Docs there are rules to using Hooks.
React Hooks Rules
Your code should look like this:
function Card({students}) {
const [students, setstudents] = useState([]);
return (
<div>
{students.map(student => (
<h1>{student.name}</h1>
))}
</div>
)
}
This should solve your bug.
Change setstudents to setStudents and I suggest simplify your code:
import React, { useState } from 'react'
import { db } from '../firebaseconfig'
import { collection, getDocs } from "firebase/firestore";
function Card() {
const [students, setStudents] = useState([]);
const fetchStudents=async()=>{
const response=db.collection('students');
const data=await response.get();
setStudents(data.docs.map(doc => ({...doc.data(), id: doc.id}))
);
}
useEffect(() => {
fetchStudents();
}, [])
return (
<div>
{students && students.map(student => (
<h1>{student.name}</h1>
))}
</div>
)
}
export default Card
you are using 2 react imports. Make it one
try this:
import React, { useState} from 'react'

React Cloud Firestore Not Fetching Data Properly

I need to fetch all data from the collection but instead only getting one document. I will be grateful for you support. Below, I present the screenshots and code snippet regarding my concern.
enter image description here
enter image description here
import './App.css';
import db from './firebase';
import React,{useState,useEffect} from 'react';
function App() {
const [accounts,setAccounts]=useState([])
const fetchAccounts=async()=>{
const response=db.collection('accounts');
const data=await response.get();
data.docs.forEach(item=>{
setAccounts([...accounts,item.data()])
})
}
useEffect(() => {
fetchAccounts();
}, [])
return (
<div className="App">
{
accounts && accounts.map(account=>{
return(
<div>
<h1>Example</h1>
<h4>{account.date}</h4>
<p>{account.email}</p>
</div>
)
})
}
</div>
);
}
export default App;
Set state functions in React are async. It means that your values are not updated immediately.
So when you update your state in a loop, each time it updates the initial value of the state and because of that at the end of the loop, only 1 item is added to the array.
In order to fix the bug, you should use another variable and set your state after the loop:
import React, { useState, useEffect } from 'react';
import './App.css';
import db from './firebase';
function App() {
const [accounts, setAccounts] = useState([]);
const fetchAccounts = async () => {
const response = db.collection('accounts');
const data = await response.get();
const newAccounts = data.docs.map(item => item.data());
setAccounts(newAccounts);
}
useEffect(() => {
fetchAccounts();
}, [])
return (
<div className="App">
{
accounts && accounts.map(account => {
return(
<div>
<h1>Example</h1>
<h4>{account.date}</h4>
<p>{account.email}</p>
</div>
)
})
}
</div>
);
}
export default App;

Firebase - collection.doc is not a function

I'm getting the following error when trying to update a document in my Firestore database.
Uncaught TypeError: machinesCollectionRef.doc is not a function
I'm reading the data just fine in another component of my React app, so I know it's not an issue with accessing the db, just probably an issue with my understanding of the documentation. Can anyone let me know where I'm going wrong?
import React, { useState } from 'react'
import {db} from'../firebase'
import {collection} from 'firebase/firestore'
export const UpdateMachine = ({machine}) => {
const [name, setName] = useState(machine.name)
const onUpdate = () => {
const machinesCollectionRef = collection(db, "Regions/Alberta/Machines")
machinesCollectionRef.doc(machine.id).update({...machine, name})
}
return (
<>
<input value={name} onChange={(e) => {setName(e.target.value)}}/>
<button onClick={onUpdate}>Update</button>
</>
)
}
EDIT: This is where I'm defining db
import firebase from 'firebase/compat/app'
import "firebase/compat/auth"
import {getFirestore} from '#firebase/firestore'
const app = firebase.initializeApp({
apiKey: "AIzaSyBhoMyfDx98mIrm_brf1Zm0MZTs7tjUTUA",
authDomain: "erg-app-dev.firebaseapp.com",
projectId: "erg-app-dev",
storageBucket: "erg-app-dev.appspot.com",
messagingSenderId: "389918287574",
appId: "1:389918287574:web:a53db3a285a8540b094b77"
})
export const db = getFirestore(app)
export const auth = app.auth()
export default app
Since you're using the new modular API doc is now a top-level function, rather than a method on a collection. The same applies to updateDoc. So:
import {collection, doc, updateDoc} from 'firebase/firestore'
export const UpdateMachine = ({machine}) => {
const [name, setName] = useState(machine.name)
const onUpdate = () => {
const machinesCollectionRef = collection(db, "Regions/Alberta/Machines")
updateDoc(doc(machinesCollectionRef, machine.id), {...machine, name});
}
...
I recommend keeping the following Firebase documentation handy:
adding and updating data
upgrading to the modular SDK.

Resources