So Im making an ecommerce react app, and decided to integrate firebase database to my project. I already created my first project on firebase and loaded all my products there with a random ID.
I imported firebase into my project. Problem is that the general product container seems to be working good but when i do the detail container (which is my one product page view), everything breaks. I will leave the code to each sections in which i implemented firebase database.
This is my cardliscontainer which seems to be working fine
import { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import productos from "../../utils/productsMock";
import CardList from "../CardList/CardList";
import LinearProgress from '#mui/material/LinearProgress';
import Box from '#mui/material/Box';
import "./CardListContainer.css";
//Firestore
import { collection, getDocs } from "firebase/firestore";
import db from '../../utils/firebaseConfig';
const CardListContainer = () => {
const [products, setProducts] = useState([]);
const [spinner, setSpinner] = useState(false);
//
const { idCategory } = useParams();
const getProductoss = async () => {
const productSnapshot = await getDocs(collection(db, "products"));
const productList = productSnapshot.docs.map((doc) => {
let product = doc.data()
product.id = doc.id
return product
})
return productList
}
const getProducts = () => {
setSpinner(true);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(productos);
}, 1000);
});
};
useEffect(() => {
getProductoss()
.then((response) => {
console.log("productos", response)
setSpinner(false);
setProducts(
idCategory
? response.filter(
(item) => item.category === idCategory
)
: response
);
})
.catch((err) => {
console.log("Fallo.");
});
}, [idCategory]);
return (
<>
{
spinner
?
<Box sx={{ width: '100%' }}>
<LinearProgress />
</Box>
:
<div className="general-container">
<CardList products={products} />
</div>
}
</>
)
};
export default CardListContainer;
This is my detail container which is gving me blank page
import React from "react";
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import productos from "../../utils/productsMock";
import CardDetail from "../CardDetail/CardDetail";
import { doc, getDoc } from "firebase/firestore";
import db from "../../utils/firebaseConfig";
import "./CardDetailContainer.css";
const CardDetailContainer = () => {
/* DEFINIR PARAMETROS EN RUTA PARA QUE FUNCIONE */
const { id } = useParams();
const [product, setProduct] = useState({});
const productFilter = productos.find((product) => {
return product.id == id;
});
useEffect(() => {
getProduct()
.then( (prod) => {
console.log("Respuesta getProduct: ", prod)
setProduct(prod)
})
}, [id]);
const getProduct = async() => {
const docRef = doc(db, "productos", id)
const docSnapshot = await getDoc(docRef)
let product = docSnapshot.data()
product.id = docSnapshot.id
return product
}
return (
<div className="details-container">
<CardDetail data={product} />
</div>
);
};
export default CardDetailContainer;
Screenshot of console:
I would really appreciate some help, if you need more info please let me know.
Thanks
Related
Can I perform client-side data fetching inside a component that's being rendered on a server-side rendered page? I have a page located at pages/solution/[solutionId]/index.js, which is server-side rendered, and it contains three components that should be performing client-side fetching. However, I am not getting any data, and it is returning null.
index.js
const Solution = ({ solution }) => {
const [isOpen, setIsOpen] = useState(false)
const router = useRouter()
const { id } = router.query
const { user } = useAuthContext()
return (
<>
<div className="px-5 row-start-2 row-end-3 col-start-2 col-end-3 mb-4">
// doing client-side fetching
<ShowWebsite
url={solution?.liveWebsiteUrl}
github={solution?.githubUrl}
title={solution?.title}
isPlayground={solution?.isPlayground}
/>
<div className="grid grid-col-1 md:grid-cols-[1fr_160px] items-start gap-x-5 mt-10">
<SolutionComments /> // doing client side fetching
<EmojiSection /> // doing client side fetching
</div>
</div>
</>
)
}
export default Solution
export async function getServerSideProps({ query }) {
const { solutionId } = query
console.log(solutionId)
const solution = await getDocument("solutions", solutionId)
return {
props: {
solution,
},
}
}
SolutionsComment:
import { useState } from "react"
import { useRouter } from "next/router"
import { useCollection } from "../../hooks/useCollection"
import Comment from "./Comment"
import CommentForm from "./CommentForm"
const SolutionComments = () => {
const router = useRouter()
const { id } = router.query
const { documents } = useCollection(`solutions/${id}/comments`)
return (
<div className="mt-10 md:mt-0">
<CommentForm docID={id} />
<div className="mt-10">
{documents &&
documents.map((comment) => (
<Comment
key={comment.id}
comment={comment}
replies={comment.replies}
/>
))}
</div>
</div>
)
}
EmojiSection:
import React from "react"
import { useRouter } from "next/router"
import { useDocument } from "../../hooks/useDocument"
import Emoji from "./Emoji"
const EmojiSection = () => {
const router = useRouter()
const { id: docID } = router.query
const { document: reactions } = useDocument(`solutions/${docID}/reactions`, "emojis")
console.log(reactions)
return (
// JSX CODE
)
}
useCollection:
import { collection, onSnapshot} from "firebase/firestore"
export const useCollection = (c) => {
const [documents, setDocuments] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
let ref = collection(db, c)
const unsubscribe = onSnapshot(ref, (snapshot) => {
const results = []
snapshot.docs.forEach(
(doc) => {
results.push({ ...doc.data(), id: doc.id })
},
(error) => {
console.log(error)
setError("could not fetch the data")
}
)
// update state
setDocuments(results)
setIsLoading(false)
setError(null)
})
return () => unsubscribe()
}, [])
return { documents, error, isLoading }
}
I used this code to show the id of a product.
import { useState } from 'react';
import axios from 'axios';
const BASE_URL = "https://makeup-api.herokuapp.com/api/v1/products";
const useGetProduct = () => {
const [products, setProducts] = useState([]);
const [singleProduct, setSingleProduct] = useState(null);
const getTopProducts = () => {
axios.get(`${BASE_URL}.json`, {
params: {
product_tags: 'Canadian',
},
})
.then(Response => setProducts(Response.data));
};
const getSingleProduct = () => {
axios.get(`${BASE_URL}/1048.jason`)
.then(Response => setSingleProduct(Response.data));
};
return {
products,
getTopProducts,
singleProduct,
getSingleProduct,
}
};
export default useGetProduct;
import React, { useEffect } from "react";
import { useParams } from "react-router-dom";
import useGetProduct from "../hooks/useGetProduct";
const Product = () => {
const { id } = useParams();
const { singleProduct, getSingleProduct } = useGetProduct();
useEffect(() => {
getSingleProduct();
}, []);
return (
<div>
<p>Product: {id}</p>
</div>
);
};
export default Product;
app.js
<Route exact path="/product/:id" element={<Product />} />
But when I change <p>Product: {id}</p> to <p>Product: {singleProduct?.name}</p> the product name does not display. Instead, it just shows Product: without the details about the product on localhost and I am not sure why.
const Product = () => {
const { id } = useParams();
const { singleProduct, getSingleProduct } = useGetProduct();
useEffect(() => {
getSingleProduct();
}, []);
return (
<div>
<p>Product: {singleProduct?.name}</p>
</div>
);
};
export default Product;
I want to show details about a single product via a hook in react js.
const { id } = useParams();
const { singleProduct, getSingleProduct } = useGetProduct();
useEffect(() => {
getSingleProduct();
}, []);
You are not using the id from useParams. Shouldn't you pass it to getSingleProduct, I assume that singleProduct is undefined.
I'm developing a pokedex using pokeAPI through react, but I'm developing a feature where I can favorite pokemons and with that through a context, I can store the names of these pokemons in a global array. I've already managed to test and verify that the pokemon names are going to this "database" array inside the pokeDatabase const in my context, but my goal now is to pass this array to localstorage so that the browser recognizes these favorite pokemons instead of disappearing every time I refresh the page, my solution was to try to create a useEffect inside the context so that every time I refresh my application, this information is saved in localStorage, but without success. What better way to achieve this?
context:
import { createContext } from "react";
const CatchContext = createContext({
pokemons: null,
});
export default CatchContext;
provider
import React, { useEffect } from "react";
import CatchContext from "./Context";
const pokeDatabase = {
database: [],
};
const CatchProvider = ({ children }) => {
useEffect(() => {
const dataStorage = async () => {
await localStorage.setItem('pokemons', JSON.stringify(pokeDatabase.database));
}
dataStorage();
}, [])
return (
<CatchContext.Provider value={{ pokemons: pokeDatabase }}>
{children}
</CatchContext.Provider>
);
}
export default CatchProvider;
pageAddPokemon
import * as C from './styles';
import { useContext, useEffect, useState } from 'react';
import { useApi } from '../../hooks/useApi';
import { useNavigate, useParams } from 'react-router-dom';
import PokeInfo from '../../components/PokeInfo';
import AddCircleOutlineIcon from '#mui/icons-material/AddCircleOutline';
import DoNotDisturbOnIcon from '#mui/icons-material/DoNotDisturbOn';
import CatchContext from '../../context/Context';
const SinglePokemon = () => {
const api = useApi();
const { pokemons } = useContext(CatchContext);
const { name } = useParams();
const navigate = useNavigate();
const handleHompage = () => {
navigate('/');
}
const [loading, setLoading] = useState(false);
const [imgDatabase, setImgDatabase] = useState('');
const [infoPokemon, setInfoPokemon] = useState([]);
const [pokemonTypes, setPokemonTypes] = useState([]);
const [isCatch, setIsCatch] = useState(false);
useEffect(() => {
const singlePokemon = async () => {
const pokemon = await api.getPokemon(name);
setLoading(true);
setImgDatabase(pokemon.sprites);
setInfoPokemon(pokemon);
setPokemonTypes(pokemon.types);
setLoading(false);
console.log(pokemons.database);
}
singlePokemon();
verifyPokemonInDatabase();
}, []);
const verifyPokemonInDatabase = () => {
if (pokemons.database[infoPokemon.name]) {
return setIsCatch(true);
} else {
return setIsCatch(false);
}
}
const handleCatchAdd = async () => {
if (isCatch === false) {
if (!pokemons.database[infoPokemon.name]);
pokemons.database.push(infoPokemon.name);
setIsCatch(true);
}
}
const handleCatchRemove = async () => {
if (isCatch === true) {
if (!pokemons.database[infoPokemon.name]);
pokemons.database.splice(pokemons.database.indexOf(toString(infoPokemon.name)), 1);
setIsCatch(false);
}
}
return (
<C.Container>
<PokeInfo
name={infoPokemon.name}
/>
<C.Card>
<C.Info>
<C.Imgs>
<img src={imgDatabase.front_default} alt="" />
<img src={imgDatabase.back_default} alt="" />
</C.Imgs>
<h2 id='types'>Tipos</h2>
{pokemonTypes.map(type => {
return (
<C.Types>
<h2>{type.type.name}</h2>
</C.Types>
)
})}
{isCatch ? (
<DoNotDisturbOnIcon id='iconCatched' onClick={handleCatchRemove}/>
): <AddCircleOutlineIcon id='icon' onClick={handleCatchAdd}/>}
</C.Info>
</C.Card>
<C.Return>
<button onClick={handleHompage}>Retornar a Pokédex</button>
</C.Return>
</C.Container>
)
}
export default SinglePokemon;
I am trying to optimize my react application, while profiling my application I found that when I click on Add to cart page my whole page is getting re-rendered. Could anyone help me with, how to avoid that and why it is happening?
FYR, GitHub repo:https://github.com/sandeep8080/shopping-cart-assignment
import { useCallback, useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import SideBar from "../../components/sideBar/SideBar";
import { getProductsData } from "../../redux/action/products";
import ProductCard from '../../components/productCard/ProductCard';
import './products.css';
import { getCategoryData } from "../../redux/action/category";
import Cart from "../cart/cart";
import Modal from '../../components/modal/Modal';
import { useHistory, useParams } from "react-router";
const ProductsPage = () => {
const dispatch = useDispatch();
const router = useHistory();
const { id } = useParams();
console.log(` product comp : ${id}`);
const productsData = useSelector(data => data.Products.products);
const sideBarData = useSelector(data => {
const listItems = data.Categories.CategoriesItems;
const activeListItems = listItems.filter(item => item.enabled === true);
return activeListItems;
});
const openCart = useSelector(state => state.CartDetails.isOpen);
const [fProductData, setFProductData] = useState([]);
useEffect(() => {
dispatch(getProductsData());
dispatch(getCategoryData());
}, []);
useEffect(() => {
if (id) {
filterDataByCategory(id);
} else {
setFProductData(productsData);
}
}, [productsData, id]);
// Function to filter out the data based on category
const filterDataByCategory = (id) => {
console.log("Filter data function called")
const filterData = productsData.filter(item => item.category === id);
setFProductData(filterData);
};
const handleClickProduct = useCallback((id) => {
filterDataByCategory(id);
router.push(`/products/${id}`);
}, [id]);
return (
<div className='product-main'>
<SideBar
sideBarData={sideBarData}
handleClickProduct={handleClickProduct}
/>
<div className='product-container'>
<div className='product-row'>
{
(fProductData).map((product) => {
return (
<div key={product.id} className='card-wrapper' >
<ProductCard key={product.id} {...product} />
</div>
)
})
}
</div>
</div>
{
openCart &&
<Modal>
<Cart />
</Modal>
}
</div >
)
};
export default ProductsPage;
// Product Card component
import './ProductCard.css';
import Button from '../button/Button';
import React from 'react';
import { useDispatch } from 'react-redux';
import { updateCart } from '../../redux/action/cart';
import priceFromatter from '../../lib/priceFromatter';
const ProductCard = ({ name, price, description, imageURL, id }) => {
const dispatch = useDispatch();
const handleClick = () => {
console.log('product clicked', id);
dispatch(updateCart(id, 'add'));
};
let imgURL = `../../${imageURL}`;
// imgURL = imgURL.replace(/([^:]\/)\/+/g, "$1");
// const image = React.lazy(() => import (`${imgURL}`));
// console.log(image);
return (
<article className='card-container'>
<h6 className='card-header'>
{name}
</h6>
<div className='content-container'>
<img
className='content-img'
// src={require(`${imgURL}`).default}
src={imageURL}
/>
<div className='content'>
<p className='content-desc'>{description}</p>
<div className='content-footer'>
<p>{priceFromatter(price)}</p>
<Button btnText='Add To Cart' handleClick={() => handleClick(id)} />
</div>
</div>
</div>
</article>
)
};
export default ProductCard;
import { callApi } from "../../lib/api";
import { actions } from '../actionContants/actionConstant';
export const toggleCart = (isToggle) => {
return {
type: actions.OPEN_CART,
payload: isToggle,
}
};
export const updateCart = (id, operation) => {
return async (dispatch, getState) => {
const productList = getState().Products.products;
const cartItems = getState().CartDetails.cartItems;
const currItem = productList.find(({ id: currentItemId }) => currentItemId === id);
const isItemInCart = cartItems.find(({ id }) => id === currItem.id);
let finalItem = [];
if (!isItemInCart) {
finalItem = [...cartItems, { ...currItem, count: 1 }]
} else {
finalItem = cartItems.map(item => {
if (item.id === currItem.id) {
operation === 'add' ? item.count = item.count + 1 : item.count = item.count - 1
}
return item;
}).filter(({ count }) => count)
}
try {
const result = await callApi.post('/addToCart', id);
result && dispatch({
type: actions.UPDATE_TO_CART,
payload: finalItem
})
} catch (error) {
console.log(error)
}
}
};
In products.js change the following block of code:
const sideBarData = useSelector(data => {
const listItems = data.Categories.CategoriesItems;
const activeListItems = listItems.filter(item => item.enabled === true);
return activeListItems;
});
to:
const sideBarData = useSelector(data => {
const listItems = data.Categories.CategoriesItems;
const activeListItems = listItems.filter(item => item.enabled === true);
return activeListItems;
}, shallowEqual);
useSelector will force a component to re-render when the selector returns a new reference that is different than the previous reference (it uses the === operator). Ref: https://react-redux.js.org/api/hooks#equality-comparisons-and-updates. As you are filtering the array returned from the store, it will always be a different object reference to the one in the store.
The use of shallowEqual as the equalityFn to useSelector() can be used to change the comparison and prevent an unnecessary re-render of the <ProductsPage> component.
did you try using e.preventDefault() otherwise the answer above might work
I am using React's Context API to share data that most of my components need.
The Context is initially defined, but shortly receives data from the Firebase database (please see IdeaContext.tsx). I define the context in a functional component and the display component, which returns a small card based on the information received.
However, the component doesn't render when I start the development server with Yarn. Instead, in order to get it to render, I have to write console.log('something') inside the display component and then it suddenly re-renders. However, when I refresh the server, it again doesn't render.
How can I make my component render immediately (or at least after the context updates with the data from the database?)
Code:
Context Definition:
import React, { createContext, useEffect, useState } from "react";
import { IdeaContextType, Idea } from "../t";
import {ideasRef} from './firebase'
function getIdeas() {
var arr: Array<Idea> = [];
ideasRef.on('value', (snapshot) => {
let items = snapshot.val()
snapshot.forEach( (idea) => {
const obj = idea.val()
arr.push({
title: obj.title,
description: obj.description,
keyID: obj.keyID
})
console.log(arr)
})
})
return arr
}
const IdeaContextDefaultValues: IdeaContextType = {
ideas: [],
setIdeas: () => {},
};
const IdeaContext = createContext<IdeaContextType>(IdeaContextDefaultValues)
const IdeaContextProvider: React.FC = ({ children }) => {
const [ideas, setIdeas] = useState<Array<Idea>>(
IdeaContextDefaultValues.ideas);
useEffect( ()=> {
console.log('getting info')
setIdeas(getIdeas())
}, [])
useEffect( () => {
console.log('idea change: ', ideas)
}, [ideas])
return (
<IdeaContext.Provider value={{ ideas, setIdeas }}>
{children}
</IdeaContext.Provider>
);
};
Displayer and Card Component
import React, { FC, ReactElement, useContext } from "react";
import IdeaCreator from "./IdeaCreator";
import { IdeaContext } from "./IdeaContext";
import { Idea } from "../t";
import { Link } from "react-router-dom";
const IdeaPost:React.FC<Idea> = ({title, keyID, description}):ReactElement => {
console.log('Received',title,description,keyID)
return (
<div className="max-w-sm rounded overflow-hidden shadow-lg">
<img
className="w-full"
src="#"
alt="Oopsy daisy"
/>
<div className="px-6 py-4">
<div className="font-bold text-xl mb-2"> <Link to={"ideas/" + keyID} key= {keyID}> {title}</Link> </div>
<p className="text-gray-700 text-base">{description}</p>
</div>
</div>
);
};
const IdeaDisplay:FC<any> = (props:any):ReactElement => {
const { ideas, setIdeas } = useContext(IdeaContext)
console.log('Ideas in display: ', ideas)
console.log('test') //This is what I comment and uncommend to get it to show
return (
<div className="flex flex-wrap ">
{ideas.map((idea) => {
console.log(idea)
console.log('Sending',idea.title,idea.description,idea.keyID)
console.log(typeof idea.keyID)
return (
<IdeaPost
title={idea.title}
description={idea.description}
keyID = {idea.keyID}
key = {idea.keyID * 100}
/>
);
})}
</div>
);
};
export default IdeaDisplay;
Solution Code:
import React, { createContext, useEffect, useState } from "react";
import { IdeaContextType, Idea } from "../t";
import {ideasRef} from './firebase'
async function getIdeas() {
var arr: Array<Idea> = [];
const snapshot = await ideasRef.once("value");
snapshot.forEach((idea) => {
const obj = idea.val();
arr.push({
title: obj.title,
description: obj.description,
keyID: obj.keyID,
});
console.log(arr);
});
return arr
}
const IdeaContextDefaultValues: IdeaContextType = {
ideas: [],
setIdeas: () => {},
};
const IdeaContext = createContext<IdeaContextType>(IdeaContextDefaultValues)
const IdeaContextProvider: React.FC = ({ children }) => {
const [ideas, setIdeas] = useState<Array<Idea>>(
IdeaContextDefaultValues.ideas);
useEffect(() => {
console.log("getting info");
const setup = async () => {
const ideas = await getIdeas();
setIdeas(ideas);
};
setup()
}, []);
useEffect( () => {
console.log('idea change: ', ideas)
const updateDatabase = async () => {
await ideasRef.update(ideas)
console.log('updated database')
}
updateDatabase()
}, [ideas])
return (
<IdeaContext.Provider value={{ ideas, setIdeas }}>
{children}
</IdeaContext.Provider>
);
};
export {IdeaContext, IdeaContextProvider}
First of all you would need to use once and not on if you want to get the data only once. If you want to use a realtime listener you could send the setIdeas to your function. Also try to be carefull with async/away calls to the Firebase sdk. Your code could look like this:
import React, { createContext, useEffect, useState } from "react";
import { IdeaContextType, Idea } from "../t";
import { ideasRef } from "./firebase";
async function getIdeas() {
var arr: Array<Idea> = [];
const snapshot = await ideasRef.once("value");
let items = snapshot.val();
snapshot.forEach((idea) => {
const obj = idea.val();
arr.push({
title: obj.title,
description: obj.description,
keyID: obj.keyID,
});
console.log(arr);
});
return arr;
}
const IdeaContextDefaultValues: IdeaContextType = {
ideas: [],
setIdeas: () => {},
};
const IdeaContext = createContext < IdeaContextType > IdeaContextDefaultValues;
const IdeaContextProvider: React.FC = ({ children }) => {
const [ideas, setIdeas] =
useState < Array < Idea >> IdeaContextDefaultValues.ideas;
useEffect(() => {
console.log("getting info");
const getData = async () => {
const ideas = await getIdeas();
setIdeas(ideas);
};
}, []);
useEffect(() => {
console.log("idea change: ", ideas);
}, [ideas]);
return (
<IdeaContext.Provider value={{ ideas, setIdeas }}>
{children}
</IdeaContext.Provider>
);
};