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);
Related
i am trying to query data for dbUser restaurant and basket but im getting error type possible unhandled promise rejection (id:17)
type error: o.userID is not a function. (o.userID('eq', dbUser.id)
type error null is not an object ( evaluating restaurant.id).
type here
import { createContext, useState, useEffect, useContext } from "react";
import { DataStore } from "aws-amplify";
import { Basket, BasketDish } from "../models";
import { useAuthContext } from "./AuthContext";
const BasketContext = createContext({});
const BasketContextProvider = ({ children }) => {
const { dbUser } = useAuthContext();
const [restaurant, setRestaurant] = useState(null);
const [basket, setBasket] = useState(null);
const [basketDishes, setBasketDishes] = useState([]);
const totalPrice = basketDishes.reduce(
(sum, basketDish) => sum + basketDish.quantity * basketDish.Dish.price,
restaurant?.deliveryFee
);
useEffect(() => {
DataStore.query(Basket, (b) =>
b.restaurantID("eq", restaurant.id).userID("eq", dbUser.id)
).then((baskets) => setBasket(baskets[0]));
}, [dbUser, restaurant]);
useEffect(() => {
if (basket) {
DataStore.query(BasketDish, (bd) => bd.basketID("eq", basket.id)).then(
setBasketDishes
);
}
}, [basket]);
const addDishToBasket = async (dish, quantity) => {
// get the existing basket or create a new one
let theBasket = basket || (await createNewBasket());
// create a BasketDish item and save to Datastore
const newDish = await DataStore.save(
new BasketDish({ quantity, Dish: dish, basketID: theBasket.id })
);
setBasketDishes([...basketDishes, newDish]);
};
const createNewBasket = async () => {
const newBasket = await DataStore.save(
new Basket({ userID: dbUser.id, restaurantID: restaurant.id })
);
setBasket(newBasket);
return newBasket;
};
return (
<BasketContext.Provider
value={{
addDishToBasket,
setRestaurant,
restaurant,
basket,
basketDishes,
totalPrice,
}}
>
{children}
</BasketContext.Provider>
);
};
export default BasketContextProvider;
export const useBasketContext = () => useContext(BasketContext);
please help me out. https://www.youtube.com/live/WFo_IxhBxF4?feature=share I'm doing ubereat by Vadim
and also useEffect also not working in my file. i tried many way but useEffect not working. if i comments everything file and try then useEffect working else not working.
and also i am getting null value from
const { currentVideo } = useSelector((state) => state.video);
this is my useEffect ->
useEffect(() => {
const fetchData = async () => {
try {
const videoRes = await axiosInstance.get(`/videos/find/${path}`, {
withCredentials: true,
});
// console.log(videoRes);
const updatedView = await axiosInstance.put(`videos/view/${path}`);
// console.log(updatedView.data, "view is updating");
const channelRes = await axiosInstance.get(
`/users/find/${videoRes.data.userId}`,
{ withCredentials: true }
);
setChannel(channelRes.data);
dispatch(fetchSuccess(videoRes.data));
} catch (err) {
console.log(err);
return "opps something went wrong!";
}
};
fetchData();
}, [path, dispatch]);
here i am getting null value
const Video = () => {
const { currentUser } = useSelector((state) => state.user);
const { currentVideo } = useSelector((state) => state.video);
const dispatch = useDispatch();
const path = useLocation().pathname.split("/")[2];
// console.log(currentVideo); // getting null
const [channel, setChannel] = useState({});
and my full video.jsx is
import React, { useEffect, useState } from "react";
import styled from "styled-components";
import ThumbUpOutlinedIcon from "#mui/icons-material/ThumbUpOutlined";
import ThumbDownOffAltOutlinedIcon from "#mui/icons-material/ThumbDownOffAltOutlined";
import ReplyOutlinedIcon from "#mui/icons-material/ReplyOutlined";
import AddTaskOutlinedIcon from "#mui/icons-material/AddTaskOutlined";
import ThumbUpIcon from "#mui/icons-material/ThumbUp";
import ThumbDownIcon from "#mui/icons-material/ThumbDown";
import Comments from "../components/Comments";
import { useDispatch, useSelector } from "react-redux";
import { useLocation } from "react-router-dom";
import axios from "axios";
import { fetchSuccess, like, dislike } from "../redux/videoSlice";
import axiosInstance from "../axios";
import { subscription } from "../redux/userSlice";
import Recommendation from "../components/Recommendation";
import { format } from "timeago.js";
const Video = () => {
const { currentUser } = useSelector((state) => state.user);
const { currentVideo } = useSelector((state) => state.video);
const dispatch = useDispatch();
const path = useLocation().pathname.split("/")[2];
// console.log(currentVideo); // getting null
const [channel, setChannel] = useState({});
/*
// console.log(path); ok hai video user id aa rahi hai.
// its working its getting all the data.
const test = async () => {
const isWorking = await axios.get(
"http://localhost:5000/api/videos/find/63931e44de7c22e61c4ffd6c"
);
console.log(isWorking.data);
console.log(isWorking.data.videoUrl);
};
const videoRes = test();
console.log(videoRes);
*/
// {withCredentials: true}
useEffect(() => {
const fetchData = async () => {
try {
const videoRes = await axiosInstance.get(`/videos/find/${path}`, {
withCredentials: true,
});
// console.log(videoRes);
const updatedView = await axiosInstance.put(`videos/view/${path}`);
// console.log(updatedView.data, "view is updating");
const channelRes = await axiosInstance.get(
`/users/find/${videoRes.data.userId}`,
{ withCredentials: true }
);
setChannel(channelRes.data);
dispatch(fetchSuccess(videoRes.data));
} catch (err) {
console.log(err);
return "opps something went wrong!";
}
};
fetchData();
}, [path, dispatch]);
const handleLike = async () => {
try {
await axiosInstance.put(`/users/like/${currentVideo._id}`, {
withCredentials: true,
});
dispatch(like(currentUser._id));
} catch (err) {
console.log(err);
return "opps something went wrong!";
}
};
const handleDislike = async () => {
try {
await axiosInstance.put(`/users/dislike/${currentVideo._id}`, {
withCredentials: true,
});
dispatch(dislike(currentUser._id));
} catch (err) {
console.log(err);
}
};
const handleSub = async () => {
currentUser.subscribedUsers.includes(channel._id)
? await axiosInstance.put(`/users/unsub/${channel._id}`, {
withCredentials: true,
})
: await axiosInstance.put(`/users/sub/${channel._id}`, {
withCredentials: true,
});
dispatch(subscription(channel._id));
};
if (!currentUser) return "Loading....";
return (
<Container>
<Content>
<VideoWrapper>
<VideoFrame src={currentVideo?.videoUrl} controls />
</VideoWrapper>
<Title>{currentVideo?.title}</Title>
<Details>
<Info>
{currentVideo?.views} views •{format(currentVideo?.createdAt)}
</Info>
<Buttons>
<Button onClick={() => handleLike()}>
{currentVideo?.likes.includes(currentUser._id) ? (
<ThumbUpIcon />
) : (
<ThumbUpOutlinedIcon />
)}{" "}
{currentVideo?.likes.length}
</Button>
<Button onClick={() => handleDislike()}>
{currentVideo?.dislikes.includes(currentUser._id) ? (
<ThumbDownIcon />
) : (
<ThumbDownOffAltOutlinedIcon />
)}
Dislike
</Button>
<Button>
<ReplyOutlinedIcon /> Share
</Button>
<Button>
<AddTaskOutlinedIcon /> Save
</Button>
</Buttons>
</Details>
<Hr />
<Channel>
<ChannelInfo>
<Image src={channel.img} />
<ChannelDetail>
<ChannelName>{channel.name}</ChannelName>
<ChannelCounter>{channel.subscribers} subscribers</ChannelCounter>
<Description>{currentVideo?.desc}</Description>
</ChannelDetail>
</ChannelInfo>
<Subscribe onClick={handleSub}>
{currentUser.subscribedUsers.includes(channel._id)
? "SUBSCRIBED"
: "SUBSCRIBE"}
</Subscribe>
</Channel>
<Hr />
<Comments videoId={currentVideo?._id} />
</Content>
<Recommendation tags={currentVideo?.tags} />
</Container>
);
};
export default Video;
and this is my redux videoslice file videoSlice.js
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
currentVideo: null,
loading: false,
error: false,
};
export const videoSlice = createSlice({
name: "video",
initialState,
reducers: {
fetchStart: (state) => {
state.loading = true;
},
fetchSuccess: (state, action) => {
state.loading = false;
state.currentVideo = action.payload;
},
fetchFailure: (state) => {
state.loading = false;
state.error = true;
},
like: (state, action) => {
if (!state.currentVideo.likes.includes(action.payload)) {
state.currentVideo.likes.push(action.payload);
state.currentVideo.dislikes.splice(
state.currentVideo.dislikes.findIndex(
(userId) => userId === action.payload
),
1
);
}
},
dislike: (state, action) => {
if (!state.currentVideo.dislikes.includes(action.payload)) {
state.currentVideo.dislikes.push(action.payload);
state.currentVideo.likes.splice(
state.currentVideo.likes.findIndex(
(userId) => userId === action.payload
),
1
);
}
},
views: (state, action) => {
if (state.currentVideo.views.includes(action.payload)) {
state.currentVideo.views.push(action.payload);
}
},
},
});
export const { fetchStart, fetchSuccess, fetchFailure, like, dislike, views } =
videoSlice.actions;
export default videoSlice.reducer;
in browser i am also getting null
i am trying to call my api and render data. but useeffect not working in my code. also from useSelector i am getting null value.
I want to know if that is the correct way i do it.
Firstly i fetch the hard coded data from my API and display it into the screen. I also have a form from which i send data and i want the axios.get method to instantly fetch the newest updated data that was sent from a form. I made a helper state that is put in to "useEffect array dependencies" and whenever that state changes its value, app reloads and fetches again.
Context file with useEffect hook:
import { createContext, useState, useEffect } from "react";
import { apiService } from "../services/api/api.service";
const Context = createContext({
footer: false,
subjectForm: false,
storedSubjects: [],
footerVisibilityHandler: () => {},
subjectFormVisibilityHandler: () => {},
});
export const ContextProvider = ({ children }) => {
const [footer, setFooter] = useState(false);
const [subjectForm, setSubjectForm] = useState(false);
const [storedSubjects, setStoredSubjects] = useState([]);
const [send, setSend] = useState(false);
useEffect(() => {
const getData = async () => {
try {
const getSubjects = await apiService.getSubjects();
const tableRow = getSubjects.data.map((subject) => {
return {
name: subject.name,
};
});
setStoredSubjects(tableRow);
} catch (err) {
console.log(err);
}
};
getData();
}, [send]);
const footerVisibilityHandler = () => {
setFooter((previousState) => !previousState);
setSubjectForm(false);
};
const subjectFormVisibilityHandler = () => {
setSubjectForm((previousState) => !previousState);
};
const context = {
footer,
subjectForm,
storedSubjects,
footerVisibilityHandler,
subjectFormVisibilityHandler,
setSend,
};
return <Context.Provider value={context}>{children}</Context.Provider>;
};
export default Context;
Form from which i send data:
import Context from "../../store/context";
import FormContainer from "../UI/FormContainer";
import { apiService } from "../../services/api/api.service";
import { useContext, useRef } from "react";
const AddSubject = () => {
const ctx = useContext(Context);
const subject = useRef("");
const sendData = (e) => {
e.preventDefault();
ctx.setSend((prevState) => !prevState);
apiService.addSubject(subject.current.value);
};
return (
<FormContainer show={ctx.subjectForm} send={sendData}>
<label htmlFor="subject">Subject Name: </label>
<input type="text" id="subject" ref={subject} />
<button type="submit">Add</button>
</FormContainer>
);
};
export default AddSubject;
Api endpoints file:
import axios from "axios";
const api = () => {
const baseUrl = "https://localhost:5001/api";
let optionAxios = {
headers: {
"Content-Type": "multipart/form-data",
},
};
return {
getSubjects: () => axios.get(`${baseUrl}/subjectcontroller/getsubjects`),
addSubject: (subjectName) =>
axios.post(
`${baseUrl}/subjectcontroller/createsubject`,
{
name: subjectName,
},
optionAxios
),
};
};
export const apiService = api();
This is the file that I retrieved data from Firebase's table. I have no problem here. Here I have logged the console to see all the data. My problem is in the quiz.js file
useQuestion.js
import { getDatabase, ref, query, orderByKey, get } from "firebase/database";
import { useEffect, useState } from "react";
export default function useQuestions(yID) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState();
const [questions, setQuestions] = useState();
useEffect(() => {
// get data from database
async function fetchQuestion() {
const db = getDatabase();
const questionRef = ref(db, "quiz/" + yID + "/questions");
const questionQuery = query(questionRef, orderByKey());
try {
setError(false);
setLoading(true);
const snapshot = await get(questionQuery);
setLoading(false);
if (snapshot.exists()) {
setQuestions((prevQuestions) => {
return [...prevQuestions, ...Object.values(snapshot.val())];
});
}
} catch (e) {
setLoading(false);
console.log(e);
setError("You are Press Wrong Questions.");
}
}
fetchQuestion();
}, [yID]);
return {
loading,
error,
questions,
};
}
The problem with this file is useReducer. I set the dispatch value but showing action.value is undefined. why this problem in my project.
quiz.js
import { useEffect, useReducer, useState } from "react";
import useQuestions from "../../hooks/useQuestion";
import { useParams } from "react-router";
import Answare from "../../answare";
import MiniPlayer from "../../miniPlayer";
import PogressBar from "../../pogressBar";
import _ from "lodash";
const initialState = null;
const reducer = (state, action) => {
switch (action.type) {
case "questions":
action.value.forEach((question) => {
question.options.forEach((option) => {
option.checked = false;
});
});
return action.value;
case "answer":
const questions = _.cloneDeep(state);
questions[action.questionID].options[action.optionIndex].checked =
action.value;
return questions;
default:
return state;
}
};
function Quiz() {
const { id } = useParams();
const [currentQuestions, setCurrentQuestions] = useState(0);
const { loading, error, questions } = useQuestions(id);
const [qna, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
dispatch({
type: "questions",
value: questions,
});
}, [questions]);
const handleAnswareChange = (e, index) => {
dispatch({
type: "answer",
QuestionsID: currentQuestions,
optionIndex: index,
value: e.target.checked,
});
};
return (
<>
{loading && <div>Loading.....</div>}
{error && <div>There was a problem.</div>}
{!loading && !error && qna && qna.length > 0 && (
<>
<h1>{qna[currentQuestions].title}</h1>
<h4>Question can have multiple answers</h4>
<Answare
options={qna[currentQuestions].options}
handleChange={handleAnswareChange}
/>
<PogressBar />
<MiniPlayer />
</>
)}
</>
);
}
export default Quiz;
At my point of view you should console out e.target.checked before dispatching it. it may be undefined
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>
);
};