too many renders react hooks, useEffect, map - firebase

hey guys I'm trying to add to sum a simple product price for each iteration and notice its runs too manytimes. in the use effect I have to listen to the UserCart. can't do it with an empty array/[userCart].
also I get the following error in more components which i couldnt fix for the same reason with the last argument in the useEffect:
update: I did divide the useEffect as suggusted but it with [userCart ] it enter infinite loop
the code:
import React, { useState, useEffect } from 'react'
import firebase from 'firebase';
import { useAuth, useStoreUpdate } from '../contexts/FirebaseContext';
export default function Cart() {
const [userMail, setUserMail] = useState(undefined)
const [userCart, setUserCart] = useState(undefined)
const [totalAmmout, setTotalAmmout] = useState(0)
const user = useAuth()
const userDoc = firebase.firestore().collection("cart").doc(userMail)
const updateStore = useStoreUpdate()
const updateCart = () => {
userDoc.get().then((doc) => {
if (doc.exists) {
let cart = doc.data()
setUserCart(cart)
}
})
}
async function removeFromCart(itemId, name, url, price, category, type, description) {
const cartItem = { itemId, name, url, price, category, type, description }
await userDoc.update({
item: firebase.firestore.FieldValue.arrayRemove(cartItem)
})
await updateCart()
await updateStore()
}
useEffect(() => {
if (user.currentUser) {
setUserMail(user.currentUser.email)
updateCart()
updateStore()
}
},userCart)
if (!userCart) return <h1>hold</h1>
let total = 0
return (
<main className="main-cart">
<div className="container">
{userCart.item && userCart.item.length >= 1 && userCart.item.map((item, i, arr) => {
console.log(item); //it runs more than 20 times
return (
< div className="item-container" key={item.itemId} >
<h3>{item.name}</h3>
<p>{item.price}</p>
<img height="150px" width="150px" src={item.url} alt="" />
<button onClick={async () => {
await removeFromCart(item.itemId, item.name, item.url, item.price, item.category, item.type, item.description)
}}>X</button>
</div>
)
})}
</div>
<div className="fixed-bottom-link">
<button>finish purchase</button>
</div>
</main >
)
}
edit :
I found the major component, but still couldnt make it work
the 2nd useEffect [userCart] wont run, it works only when not in the array, but it enter a loop:
import React, { useContext, useState, useEffect } from 'react';
import { auth } from '../firebase/firebase';
import firebase from '../firebase/firebase';
const FirebaseContext = React.createContext()
const StoreContext = React.createContext()
const StoreContextUpdate = React.createContext()
export function useAuth() {
return useContext(FirebaseContext)
}
export function useStore() {
return useContext(StoreContext)
}
export function useStoreUpdate() {
return useContext(StoreContextUpdate)
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true)
const [userMail, setUserMail] = useState(undefined)
const [userCart, setUserCart] = useState(undefined)
const userDoc = firebase.firestore().collection("cart").doc(userMail)
const updateCart = () => {
userDoc.get().then((doc) => {
if (doc.exists) {
let cart = doc.data()
setUserCart(cart)
}
})
}
function signup(email, password) {
return auth.createUserWithEmailAndPassword(email, password)
}
function login(email, pasword) {
return auth.signInWithEmailAndPassword(email, pasword)
}
function logout() {
return auth.signOut()
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(async user => {
await setCurrentUser(user)
await setLoading(false)
})
return unsubscribe
}, [userCart])
useEffect(()=>{
async function refreshCart(){
await setUserMail(currentUser.email);
await updateCart()
}
if (currentUser && currentUser.email) {
refreshCart()
}
return
},[userCart])
const value = {
currentUser,
signup,
login,
logout
}
return (
<FirebaseContext.Provider value={value}>
<StoreContext.Provider value={userCart && userCart.item}>
<StoreContextUpdate.Provider value={updateCart}>
{!loading && children}
</StoreContextUpdate.Provider>
</StoreContext.Provider>
</FirebaseContext.Provider>
)
}

useEffect(() => {
if (user.currentUser) {
setUserMail(user.currentUser.email);
updateCart();
updateStore();
}
}, [userCart]); //<-- userCart needs to be inside array
and the reason for too many rerenders is you're calling updateCart function when the userCart changes and then userCart change will cause the call of updateCart again and this loop will repeat itself until you're out of memory
I think adding another useEffect might help you
useEffect(() => {
if (user.currentUser) {
setUserMail(user.currentUser.email);
updateStore();
}
}, [userCart]); //<-- userCart needs to be inside array
useEffect(() => {
if (user.currentUser) {
updateCart();
}
}, []);

Related

Next.js reference error "client" is not defined

I am having an error when trying to fetch product data from sanity with getStaticPaths
here is my code:
import React, { useState } from "react";
function ProductPage({ product, products }) {
const { image, name, details, price } = product;
return (
<div className="pdPage">
<div className="container">{name}</div>
</div>
);
}
export default ProductPage;
export const getStaticPaths = async () => {
const query = `*[_type == "product"] {
slug {
current
}
}
`;
const products = await client.fetch(query);
const paths = products.map((product) => ({
params: {
slug: product.slug.current,
},
}));
return {
paths,
fallback: "blocking",
};
};
export const getStaticProps = async ({ params: { slug } }) => {
const query = `*[_type == "product" && slug.current == '${slug}'][0]`;
const productsQuery = '*[_type == "product"]';
const product = await client.fetch(query);
const products = await client.fetch(productsQuery);
console.log(product);
return {
props: { products, product },
};
};
And then I get reference error client is not defined from getstatic client.fetch
I even delete my code and replace from tutor github repository, but get the same error
After figure it out sometimes I found that I forgot to import client from

How to update page when data is changed in nextjs?

When I delete one of the notes, it deletes from the DB. And to see the effect, I need to reload the page every time I delete a note.
How do I see the not deleted notes without reloading the page?
Here's the code for my page:
export default function Home(notes) {
const [notesData, setNotesData] = useState(notes);
const deleteNote = async (note) => {
const res = await fetch(`http://localhost:3000/api/${note}`, {
method: "DELETE",
});
};
return (
<div>
<h1>Notes:</h1>
{notesData.notes.map((note) => {
return (
<div className="flex">
<p>{note.title}</p>
<p onClick={() => deleteNote(note.title)}>Delete</p>
</div>
);
})}
</div>
);
}
export async function getServerSideProps() {
const res = await fetch(`http://localhost:3000/api`);
const { data } = await res.json();
return { props: { notes: data } };
}
If you're fetching the data with getServerSideProps you need to recall that in order to get the updated data like this :
import { useRouter } from 'next/router';
const router = useRouter()
const refreshData = () => router.replace(router.asPath);
But also you can store the data from getServerSideProps in a state and render that state and trigger a state update after a note is deleted like this :
export default function Home(notes) {
const [notesData, setNotesData] = useState(notes);
const deleteNote = async (note) => {
const res = await fetch(`http://localhost:3000/api/${note}`, {
method: "DELETE",
});
};
return (
<div>
<h1>Notes:</h1>
{notesData.notes.map((note) => {
return (
<div className="flex">
<p>{note.title}</p>
<p onClick={() => deleteNote(note.title).then(()=>{
const res = await fetch(`http://localhost:3000/api`);
const { data } = await res.json();
setNotesData(data)
})
}>Delete</p>
</div>
);
})}
</div>
);
}
export async function getServerSideProps() {
const res = await fetch(`http://localhost:3000/api`);
const { data } = await res.json();
return { props: { notes: data } };
}

Solution to prefetch in a component on nextjs

I'm looking for a solution/module where I don't need to inject inital/fallback data for swr/react-query things from getServerSideProps. Like...
from
// fetcher.ts
export default fetcher = async (url: string) => {
return await fetch(url)
.then(res => res.json())
}
// getUserData.ts
export default function getUserData() {
return fetcher('/api')
}
// index.tsx
const Page = (props: {
// I know this typing doesn't work, only to deliver my intention
userData: Awaited<ReturnType<typeof getServerSideProps>>['props']
}) => {
const { data } = useSWR('/api', fetcher, {
fallbackData: props.userData,
})
// ...SSR with data...
}
export const getServerSideProps = async (ctx: ...) => {
const userData = await getUserData()
return {
props: {
userData,
},
}
}
to
// useUserData.ts
const fetcher = async (url: string) => {
return await fetch(url)
.then(res => res.json())
};
const url = '/api';
function useUserData() {
let fallbackData: Awaited<ReturnType<typeof fetcher>>;
if (typeof window === 'undefined') {
fallbackData = await fetcher(url);
}
const data = useSWR(
url,
fetcher,
{
fallbackData: fallbackData!,
}
);
return data;
}
// index.tsx
const Page = () => {
const data = useUserData()
// ...SSR with data...
}
My goal is making things related to userData modularized into a component.

'GiftedChat' API not rendering and unable to send/receive texts

So I'm basically developing a react-native Chat app using firebase as the backend and I'm stuck at this point where I am unable to send messages and not render the GiftedChat Api even. I'm pasting the Home component here can you please help me out. I'm trying to append the message to the giftedchat component and render it whenever i press the send button.
import React, { Component } from 'react' import { Text, View, Button }
from 'react-native' import { GiftedChat } from
'react-native-gifted-chat' import firebase from '../database/Firebase'
import AsyncStorage from '#react-native-community/async-storage'
class Home extends Component {
state = {
messages: [],
user: 'true',
userData: null
}
componentDidMount() {
const db = firebase.firestore()
const chatsRef = db.collection('chats')
this.readUser()
const unsubscribe = chatsRef.onSnapshot((querySnapshot) => {
const messagesFirestore = querySnapshot
.docChanges()
.filter(({ type }) => type === 'added')
.map(({ doc }) => {
const message = doc.data()
return { ...message, createdAt: message.createdAt.toDate() }
})
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
this.appendMessages(messagesFirestore)
})
return () => unsubscribe()
}
handleSend(messages) {
const writes = messages.map((m) => chatsRef.add(m))
Promise.all(writes)
}
appendMessages = (messages) => {
this.setState((previousMessages) => GiftedChat.append(previousMessages, messages))
}
async readUser() {
const userData = await AsyncStorage.getItem('userData')
if (userData) {
setUser(JSON.parse(userData))
}
}
async handlePress() {
const _id = Math.random().toString(36).substring(7)
const userData = { _id, name }
await AsyncStorage.setItem('userData', JSON.stringify(userData))
}
handleLogout = () => {
this.setState(() => ({
user: false
}))
}
render() {
if (this.state.user === 'true') {
return (<View>
<Button title="logout" style={{ width: 200 }} onPress={() => this.handleLogout()}></Button>
<GiftedChat messages={this.state.messages} user={this.state.userData}
onSend={() => this.handleSend()} />
</View>)
} else {
return (<View>
{this.props.navigation.navigate("Login")}
</View>)
}
} }
export default Home

Redux actions without return or dispatch

I am implementing Oauth from google with redux, and I wanted to have all google API calls handled from my redux and ended up writing helper functions in my actions file that doesn't return anything or call dispatch. I ended up with code where I only dispatch once from my JSX file and wondering if this is okay or there is another better way to do it?
The code is as follows:
authActions.js
const clientId = process.env.REACT_APP_GOOGLE_OAUTH_KEY;
let auth;
export const authInit = () => (dispatch) => {
window.gapi.load('client:auth2', () =>
window.gapi.client.init({ clientId, scope: 'email' }).then(() => {
auth = window.gapi.auth2.getAuthInstance();
dispatch(changeSignedIn(auth.isSignedIn.get()));
auth.isSignedIn.listen((signedIn) => dispatch(changeSignedIn(signedIn)));
})
);
};
export const signIn = () => {
auth.signIn();
};
export const signOut = () => {
auth.signOut();
};
export const changeSignedIn = (signedIn) => {
const userId = signedIn ? auth.currentUser.get().getId() : null;
return {
type: SIGN_CHANGE,
payload: { signedIn, userId },
};
};
GoogleAuth.jsx
import { useSelector, useDispatch } from 'react-redux';
import classNames from 'classnames';
import { authInit, signIn, signOut } from '../../actions/authActions';
function GoogleAuth() {
const { signedIn } = useSelector((state) => state.auth);
const dispatch = useDispatch();
useEffect(() => {
dispatch(authInit());
}, [dispatch]);
const onClick = () => {
if (signedIn) {
signOut();
} else {
signIn();
}
};
let content;
if (signedIn === null) {
return null;
} else if (signedIn) {
content = 'Sign Out';
} else {
content = 'Sign In';
}
return (
<div className="item">
<button
className={classNames('ui google button', {
green: !signedIn,
red: signedIn,
})}
onClick={onClick}
>
<i className="ui icon google" />
{content}
</button>
</div>
);
}
export default GoogleAuth;
The code works fine, but it feels like it might be misleading having action calls in JSX but not dispatching it, is it okay?

Resources