I noticed this strange behavior of my App, that when I do anything on it (write something in the search field, create a new list, etc) my page gets rerendererd. Of course, I cannot find the source of it.
Below is the the look of my page, when it is loaded the first time, with default (blank) search results.
And now, the result in profiler, when I type something in the searchBar (or create a new list, or anything):
Here is my code of the App.js
import React, { useState, createContext, useEffect } from "react";
import NavBar from "../NavBar/NavBar";
import youtube from "../../apis/youtube";
import VideoList from "../VideoList/VideoList";
import VideoDetail from "../VideoDetail/VideoDetail";
import SideBar from "../SideBar/SideBar";
import "./App.css";
export const VideoContext = createContext();
export const FavoriteContext = createContext();
const API_KEY = process.env.REACT_APP_API_KEY;
const App = () => {
const [ videos, setVideos ] = useState([]);
const [ searchedValue, setSearchedValue ] = useState({
selectedVideo: null
});
const handleSelectedVideo = (singleRenderedVideo) => {
setSearchedValue((previous) => ({
...previous,
selectedVideo: singleRenderedVideo
}));
};
const handleSearch = async (inputText) => {
const response = await youtube.get("/search", {
params: {
q: inputText,
part: "snippet",
type: "video",
maxResults: 16,
key: API_KEY
}
});
setVideos(response.data.items);
setSearchedValue({
selectedVideo: response.data.items[0] //take the first search result and make it appear as a playable one
});
};
useEffect(() => {
handleSearch();
}, []);
//By the user newly created lists
const [ lists, setLists ] = useState([]);
const addList = (newList) => {
setLists((prevLists) => {
return [ ...prevLists, newList ];
});
};
const onDeleteList = (id) => {
setLists((prevLists) => {
return prevLists.filter((listItem, index) => {
return index !== id;
});
});
};
//Render(Play) Favorited Video
const [ favoritedItem, setFavoritedItem ] = useState({
clickedFavoritedVideo: null
});
const handleSelectedFavorite = (renderFavorite) => {
setFavoritedItem((previous) => ({
...previous,
clickedFavoritedVideo: renderFavorite
}));
};
//Add a newly favorited video to a, by user created, list (BUG: for now the favorited video is added to EVERY, by the user, created list)
const [ favoritedList, setFavoritedList ] = useState([]);
const handleFavoritedVideo = (favoritedElement, selectedList) => {
setFavoritedList((previousFavorited) => {
return [ { favoritedElement, selectedList }, ...previousFavorited ];
});
};
const deleteFavorited = (id) => {
setFavoritedList((prevLists) => {
return prevLists.filter((listItem, index) => {
return index !== id;
});
});
};
return (
<div className="container">
<NavBar handleSearch={handleSearch} />
<div className="content">
<SideBar
addList={addList}
lists={lists}
handleSelectedFavorite={handleSelectedFavorite}
favoritedList={favoritedList}
onDeleteList={onDeleteList}
onDeleteFavorited={deleteFavorited}
/>
<main className="video">
<VideoContext.Provider value={handleSelectedVideo}>
<FavoriteContext.Provider value={handleFavoritedVideo}>
<VideoDetail
selectedVideo={searchedValue.selectedVideo}
clickedFavoritedVideo={
favoritedItem.clickedFavoritedVideo
}
/>
<VideoList listOfVideos={videos} lists={lists} />
</FavoriteContext.Provider>
</VideoContext.Provider>
</main>
</div>
</div>
);
};
export default App;
I will not post my whole app here, because it is a lot of files. I just give a link to my gitHub:
GitHub LINK
I was trying to find a solution, as stated here:
Link to SO page
which is like my case, but it didn't help (maybe because I was not using memo):
import React, { useState, createContext, useEffect, useCallback } from "react";
import NavBar from "../NavBar/NavBar";
import youtube from "../../apis/youtube";
import VideoList from "../VideoList/VideoList";
import VideoDetail from "../VideoDetail/VideoDetail";
import SideBar from "../SideBar/SideBar";
import "./App.css";
export const VideoContext = createContext();
export const FavoriteContext = createContext();
const API_KEY = process.env.REACT_APP_API_KEY;
const App = () => {
const [ videos, setVideos ] = useState([]);
const [ searchedValue, setSearchedValue ] = useState({
selectedVideo: null
});
const handleSelectedVideo = useCallback((singleRenderedVideo) => {
setSearchedValue((previous) => ({
...previous,
selectedVideo: singleRenderedVideo
}));
}, []);
const handleSearch = async (inputText) => {
const response = await youtube.get("/search", {
params: {
q: inputText,
part: "snippet",
type: "video",
maxResults: 16,
key: API_KEY
}
});
setVideos(response.data.items);
setSearchedValue({
selectedVideo: response.data.items[0] //take the first search result and make it appear as a playable one
});
};
useEffect(() => {
handleSearch();
}, []);
//By the user newly created lists
const [ lists, setLists ] = useState([]);
const addList = useCallback((newList) => {
setLists((prevLists) => {
return [ ...prevLists, newList ];
});
}, []);
const onDeleteList = useCallback((id) => {
setLists((prevLists) => {
return prevLists.filter((listItem, index) => {
return index !== id;
});
});
}, []);
//Render(Play) Favorited Video
const [ favoritedItem, setFavoritedItem ] = useState({
clickedFavoritedVideo: null
});
const handleSelectedFavorite = useCallback((renderFavorite) => {
setFavoritedItem((previous) => ({
...previous,
clickedFavoritedVideo: renderFavorite
}));
}, []);
//Add a newly favorited video to a, by user created, list (BUG: for now the favorited video is added to EVERY, by the user, created list)
const [ favoritedList, setFavoritedList ] = useState([]);
const handleFavoritedVideo = useCallback((favoritedElement, selectedList) => {
setFavoritedList((previousFavorited) => {
return [ { favoritedElement, selectedList }, ...previousFavorited ];
});
}, []);
const deleteFavorited = useCallback((id) => {
setFavoritedList((prevLists) => {
return prevLists.filter((listItem, index) => {
return index !== id;
});
});
}, []);
return (
<div className="container">
<NavBar handleSearch={handleSearch} />
<div className="content">
<SideBar
addList={addList}
lists={lists}
handleSelectedFavorite={handleSelectedFavorite}
favoritedList={favoritedList}
onDeleteList={onDeleteList}
onDeleteFavorited={deleteFavorited}
/>
<main className="video">
<VideoContext.Provider value={handleSelectedVideo}>
<FavoriteContext.Provider value={handleFavoritedVideo}>
<VideoDetail
selectedVideo={searchedValue.selectedVideo}
clickedFavoritedVideo={
favoritedItem.clickedFavoritedVideo
}
/>
<VideoList listOfVideos={videos} lists={lists} />
</FavoriteContext.Provider>
</VideoContext.Provider>
</main>
</div>
</div>
);
};
export default App;
I also tried to give a type for my buttons (type="button"), which currently have no type, like as in:
CreateNewList.js
import React, { useState } from "react";
import iconSprites from "../../images/sprite.svg";
import shortid from "shortid";
const CreateNewList = ({ onAdd }) => {
const [ list, setList ] = useState({
id: shortid.generate(),
title: ""
});
const handleChange = (event) => {
const { value } = event.target;
setList((prevList) => {
return {
...prevList,
title: value
};
});
event.preventDefault();
};
const submitNewList = (event) => {
onAdd({ ...list });
setList({ id: shortid.generate(), title: "" });
event.preventDefault();
};
return (
<React.Fragment>
<li className="new-list__item">
<form>
<div className="new-list__link">
<button
onClick={submitNewList}
className="new-list__btn-plus btn"
>
<svg className="new-list__icon">
<use href={iconSprites + "#icon-circle-with-plus"} />
</svg>
</button>
<input
className="new-list__input"
name="title"
value={list.title}
onChange={handleChange}
placeholder="New List"
/>
</div>
</form>
</li>
</React.Fragment>
);
};
export default CreateNewList;
but it also didn't help. Maybe because they are not in <form>?
So that is it. Maybe someone can help me with my issue?
To have all of the questions in one place:
Why my app keeps rerendering?
Should I use memo with useCallback?
Should I put my buttons in a <form> and give them a type?
Related
I have a NextJS application that is using the ShopifyBuy SDK. I have been successfully able to implement a solution where I am able to fetch the products from Store and display them to the User. The user is also able to go to a product page and add the product to the cart.
However, when the user refreshes the page, the cart is reset, and the data does not persist. The code is below:
context/cart.js:
import { createContext, useContext, useEffect, useReducer } from "react";
import client from "../lib/client";
import Cookies from "js-cookie";
const CartStateContext = createContext();
const CartDispatchContext = createContext();
const SET_CART = "SET_CART";
const initalState = {
lineItems: [],
totalPrice: 0,
webUrl: "",
id: "",
};
const reducer = (state, action) => {
switch (action.type) {
case SET_CART:
return { ...state, ...action.payload };
default:
throw new Error(`Unknown action: ${action.type}`);
}
};
export const CartProvider = ({ children }) => {
const savedState = Cookies.get("cartState");
const [state, dispatch] = useReducer(reducer, savedState || initalState);
useEffect(() => {
Cookies.set("cartState", state, { expires: 7 });
}, [state]);
useEffect(() => {
getCart();
}, []);
const setCart = (payload) => dispatch({ type: SET_CART, payload });
const getCart = async () => {
try {
const cart = await client.checkout.create();
setCart(cart);
} catch (err) {
console.log(err);
}
};
return (
<CartDispatchContext.Provider value={{ setCart }}>
<CartStateContext.Provider value={{ state }}>
{children}
</CartStateContext.Provider>
</CartDispatchContext.Provider>
);
};
export const useCartState = () => useContext(CartStateContext);
export const useCartDispatch = () => useContext(CartDispatchContext);
products/[handle].tsx:
import React, { useState, useEffect } from "react";
import client from "../../lib/client";
import { useCartDispatch, useCartState } from "../../context/cart";
import Link from "next/link";
import cookie from "js-cookie";
export const getStaticPaths = async () => {
const res = await client.product.fetchAll();
const paths = res.map((product: any) => {
return {
params: { handle: product.handle.toString() },
};
});
return {
paths,
fallback: false,
};
};
export const getStaticProps = async (context: any) => {
const handle = context.params.handle;
const res = await client.product.fetchByHandle(handle);
const product = JSON.stringify(res);
return {
props: {
product,
},
};
};
function Product({ product }: any) {
const { state } = useCartState();
const { setCart } = useCartDispatch();
const addToCart = async () => {
const checkoutId = state.id;
const lineItemsToAdd = [
{
variantId: product.variants[0].id,
quantity: 1,
},
];
const res = await client.checkout.addLineItems(checkoutId, lineItemsToAdd);
setCart(res);
};
product = JSON.parse(product);
return (
<div>
<div className=" flex-col text-2xl font-bold m-8 flex items-center justify-center ">
<h1>{product.title}</h1>
<button onClick={addToCart}>Add to Cart</button>
<Link href="/cart">Checkout</Link>
</div>
</div>
);
}
export default Product;
pages/cart/index.tsx:
import React, { useEffect } from "react";
import { useCartState, useCartDispatch } from "../../context/cart";
import client from "../../lib/client";
function Cart() {
const { state } = useCartState();
return (
<div>
<h1>Cart</h1>
{state.lineItems &&
state.lineItems.map((item: any) => {
return (
<div key={item.id}>
<h2>{item.title}</h2>
<p>{item.variant.title}</p>
<p>{item.quantity}</p>
</div>
);
})}
</div>
);
}
export default Cart;
I have tried using a library called js-cookie and also localStorage. I'm not sure where the problem lies or if the solutions that I've tried are wrong.
P.S.: I'm fairly new to NextJS and Typescript so go easy on the syntax. This code is for a personal project. Thanks in advance!
Answering this because I ended up coming up with a solution that works for me, at least.
Here it is:
const getCart = async () => {
try {
const checkoutId = Cookies.get("checkoutId");
let cart;
if (checkoutId) {
cart = await client.checkout.fetch(checkoutId);
} else {
cart = await client.checkout.create();
Cookies.set("checkoutId", cart.id);
}
setCart(cart);
} catch (err) {
console.log(err);
}
};
From my understanding, what this does is the following:
Check the cookies to see if one exists called "checkoutId"
If it exists, fetch the cart using that checkoutId
Otherwise, create a new cart and create a cookie using the cart.id that is returned in the response
Then, inside my individual Product page ([handle].tsx), I'm doing the following:
const addToCart = async () => {
const checkoutId = state.id;
const lineItemsToAdd = [
{
variantId: product.variants[0].id,
quantity: 1,
},
];
const res = await client.checkout.addLineItems(checkoutId, lineItemsToAdd);
console.log(res);
if (cookie.get("checkoutId") === undefined) {
cookie.set("checkoutId", res.id);
}
setCart(res);
};
Using cookies to store your object cart, as far as I know, is not a good idea. You could use localStorage, like so:
import { createContext, useContext, useEffect, useReducer } from "react";
import client from "../lib/client";
const CartStateContext = createContext();
const CartDispatchContext = createContext();
const SET_CART = "SET_CART";
const initalState =
typeof localStorage !== "undefined" && localStorage.getItem("cartState")
? JSON.parse(localStorage.getItem("cartState"))
: {
lineItems: [],
totalPrice: 0,
webUrl: "",
id: "",
};
const reducer = (state, action) => {
switch (action.type) {
case SET_CART:
return { ...state, ...action.payload };
default:
throw new Error(`Unknown action: ${action.type}`);
}
};
export const CartProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initalState);
useEffect(() => {
localStorage.set("cartState", JSON.stringify(state));
}, [state]);
useEffect(() => {
getCart();
}, []);
const setCart = (payload) => dispatch({ type: SET_CART, payload });
const getCart = async () => {
try {
const cart = await client.checkout.create();
setCart(cart);
} catch (err) {
console.log(err);
}
};
return (
<CartDispatchContext.Provider value={{ setCart }}>
<CartStateContext.Provider value={{ state }}>{children}</CartStateContext.Provider>
</CartDispatchContext.Provider>
);
};
export const useCartState = () => useContext(CartStateContext);
export const useCartDispatch = () => useContext(CartDispatchContext);
DonationList.js
import React, { useEffect, useState } from "react";
import { getDocs, collection } from "firebase/firestore";
import { auth, db } from "../firebase-config";
import Donation from "./Donation";
const DonationList = () => {
const [donations, setDonations] = useState([]);
//loadDonations
const loadDonations = async () => {
const donationsCollectionRef = collection(db, "donations");
const querySnapshot = await getDocs(donationsCollectionRef);
querySnapshot.forEach(async (donationSnap) => {
const items = [];
const itemsCollectionRef = collection(db, "donations", donationSnap.id, "items");
const itemquerySnapshot = await getDocs(itemsCollectionRef);
itemquerySnapshot.forEach((itemSnap) => {
items.push([
{
itemType: itemSnap.data().itemType,
quantity: itemSnap.data().quantity,
},
]);
});
setDonations((prevDonation) => {
return [
...prevDonation,
{
type: donationSnap.data().type,
donor: donationSnap.data().donor,
requestor: donationSnap.data().requestor,
items: items,
},
];
});
items = [];
});
};
useEffect(() => {
loadDonations();
}, []);
return (
<div className="grid">
<div className="col-12">
<div className="card">
<h5>Donation List</h5>
{donations.map((donation) => (
<Donation donation={donation} />
))}
</div>
</div>
</div>
);
};
const comparisonFn = function (prevProps, nextProps) {
return prevProps.location.pathname === nextProps.location.pathname;
};
export default React.memo(DonationList, comparisonFn);
Donation.js
import React, { useState } from "react";
const Donation = (props) => {
return (
<div className="card">
<h5>Donation Type</h5>
{props.donation.type}
<h5>Requestor</h5>
{props.donation.requestor}
<h5>Donor</h5>
{props.donation.donor}
<h5>Items</h5>
{props.donation.items.map((item) => (
<Item item={item} />
))}
</div>
);
};
const Item = (props) => {
console.log(props.item)
return (
<React.Fragment>
ItemType: {props.item.itemType}
Quantity: {props.item.quantity}
</React.Fragment>
);
};
export default Donation;
These two code snippets is me trying to take data from firebase and display it.
When I console.log(props.item) in the Item component function, the item seems to be an array.
screenshot of the log
Shouldn't it be an object like props.donation?
Its my first time using reactJS so I can't seem to find the problem
Check this block in your code.
const items = [];
itemquerySnapshot.forEach((itemSnap) => {
items.push([
{
itemType: itemSnap.data().itemType,
quantity: itemSnap.data().quantity,
},
]);
});
You have already defined an array and added the item as an array. That is why you are accessing item in Donations; it's returning as an array type.
{props.donation.items.map((item) => (
<Item item={item} />
))}
Change your loadDonations method like this, and it will work correctly.
itemquerySnapshot.forEach((itemSnap) => {
items.push(
{
itemType: itemSnap.data().itemType,
quantity: itemSnap.data().quantity,
},
);
})
I'm trying to display the response from the API into my react component but it's not working. If I try to use it in the console, I can see the data and its value but not in the react component, it's empty when I try to show the value in a div.
Here is the code where I'm trying to display it in my react component:
const CharacterListing = () => {
const characters = useSelector(getAllCharacters);
console.log("Hello", characters);
const renderCharacters = Object.entries(characters).map(([key, value]) => {
console.log(value.name);
<div>{value.name}</div>
})
return (
<div>
{renderCharacters}
</div>
);
};
export default CharacterListing;
This is the code for my Character Slice Component
const initialState = {
characters: {},
};
const characterSlice = createSlice({
name: 'characters',
initialState,
reducers: {
addCharacters: (state, { payload }) => {
state.characters = payload;
},
},
});
export const { addCharacters } = characterSlice.actions;
export const getAllCharacters = (state) => state.characters.characters;
export default characterSlice.reducer;
This is the code for my Home Component:
const Home = () => {
const dispatch = useDispatch();
useEffect(() => {
const fetchCharacters = async () => {
const response = await baseURL.get(`/characters`)
.catch(error => {
console.log("Error", error);
});
dispatch(addCharacters(response.data));
console.log("Success", response);
};
fetchCharacters();
}, [])
return (
<div>
Home
<CharacterListing />
</div>
);
};
export default Home;
Thank you
You forgot to return item into your map func
Try this :
const renderCharacters = Object.entries(characters).map(([key, value]) => {
console.log(value.name);
return <div key={key}>{value.name}</div>
})
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>
);
};