React - Context API w/ promise from Firebase - reactjs

First I want to say a) I am new to react, and b) I spent about 8hrs trying to dig around in stackoverflow for a fix, trying many different methods. I am stuck. Many thanks for your time.
The objective here is to wait for the request from firebase to create the value 'products', before rendering the page. What keeps happening is products gets loaded as null.
Here is my setup:
ProductContext.js
import React, {createContext, useState, useEffect} from 'react';
import { dummyProducts } from '../services/dummy';
import {collection, onSnapshot} from "firebase/firestore";
import {db} from "../firebase";
export const ProductsContext = createContext();
export function getProductData() {
const myArray =[];
return new Promise(function (resolve, reject) {
const querySnapshot = onSnapshot(collection(db, 'products'), (snapshot) => {
snapshot.forEach(doc => {
let productData = doc.data();
myArray.push(productData)
})
})
resolve(myArray);
});
}
const ProductsContextProvider = ({children}) => {
const [products, setProducts] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
let didCancel = false;
async function fetchMyAPI() {
let results = await getProductData();
console.log(results) // Outputs array with results
setProducts(results)
}
fetchMyAPI();
return () => { didCancel = true; }; // Remember if we start fetching something else
}, []);
return (
<ProductsContext.Provider value={{products}}>
{ children }
</ProductsContext.Provider>
);
}
export default ProductsContextProvider;
UseProducts.js
// eslint-disable-next-line
import React, { useContext } from 'react';
import { ProductsContext } from '../contexts/ProductsContext';
export const useProducts = () => {
const ctx = useContext(ProductsContext)
return {
...ctx
}
}
ProductsGrid.js
import React from 'react';
import ProductItem from './ProductItem';
import styles from './ProductsGrid.module.scss';
import { useProducts } from '../../hooks/useProducts';
const ProductsGrid = () => {
const {products} = useProducts();
console.log(products.length) // Outputs zero
console.log(products) // Outputs array with 1 object (correct)
return (
<div className={styles.p__container}>
<div className="row">
<div className="col-sm-8">
<div className="py-3">
{products.length} Products
</div>
</div>
<div className="col-sm-4">
</div>
</div>
<div className={styles.p__grid}>
{
products.map(product => (
<ProductItem key={product.id} product={product}/>
))
}
</div>
<div className={styles.p__footer}>
</div>
</div>
);
}
export default ProductsGrid;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Routes from './routes';
import * as serviceWorker from './serviceWorker';
import { HelmetProvider } from 'react-helmet-async';
import ProductsContextProvider from './contexts/ProductsContext';
import CartContextProvider from './contexts/CartContext';
ReactDOM.render(
<HelmetProvider>
<ProductsContextProvider>
<CartContextProvider>
<Routes />
</CartContextProvider>
</ProductsContextProvider>
</HelmetProvider>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
serviceWorker.unregister();

Got it working.
I changed
ProductsContext.js
import React, { createContext, useState } from 'react';
// import { dummyProducts } from '../services/dummy';
export const ProductsContext = createContext();
const ProductsContextProvider = ({children}) => {
const [products, setProducts] = useState([]);
return (
<ProductsContext.Provider value={{products, setProducts}} >
{ children }
</ProductsContext.Provider>
);
}
export default ProductsContextProvider;
And changed
ProductsGrid.js to:
import React, { useEffect, useState, createContext } from 'react';
import ProductItem from './ProductItem';
import styles from './ProductsGrid.module.scss';
import { useProducts } from '../../hooks/useProducts'
import {collection, onSnapshot, where} from "firebase/firestore";
import {db} from "../../firebase";
export const ProductsContext = createContext();
async function getProductData() {
const myArray =[];
const promise = new Promise(resolve =>
onSnapshot(collection(db, 'products'), (snapshot) => {
snapshot.forEach(doc => {
let productData = doc.data();
myArray.push(productData)
})
resolve(myArray)
}));
const productData = await promise;
//console.log(productData);
return productData;
}
const ProductsGrid = () => {
const { products, setProducts } = useProducts()
const [loading, setLoading]= useState(false);
const getProducts = async ()=>{
setLoading(true)
const newText = await getProductData();
setProducts(newText)
setLoading(false)
}
useEffect(()=> {
getProducts()
},[])
return (
loading ? (
<div className={styles.p__container}>
<div className="row">
<div className="col-sm-8"> <h4>Loading...</h4></div></div></div>) : (
<div className={styles.p__container}>
<div className="row">
<div className="col-sm-8">
<div className="py-3">
{products.length} Products
</div>
</div>
<div className="col-sm-4">
</div>
</div>
<div className={styles.p__grid}>
{
products.map(product => (
<ProductItem key={product.id} product={product}/>
))
}
</div>
<div className={styles.p__footer}>
</div>
</div>
));
}
export default ProductsGrid;
Notable changes are:
I moved the fetch request to ProductsGrid
I changed how the getProductData() function was structured. I suspect it was the 'return' on the new Promise which wasn't giving the desired effect.
I added Loading state

Related

How to store and access data from array [product] from context?

I have a context which contains a fetch() method used to retieve list of products from a server.I made this context so that I could reuse the fetched array values every webpage I might need.But I am unable to do so as it gives me an error in console.
this is the code for context
import React, { createContext, useState, useEffect } from 'react'
export const ProductContext = createContext()
const ProductContextProvider = (props) => {
const [product, setProduct] = useState([]);
const fetchData = () => {
fetch(`http://localhost:8080/product`)
.then((response) => response.json())
.then((actualData) => {
setProduct(actualData)
console.log(product);
})
};
useEffect(() => {
fetchData();
}, [])
return (
<ProductContext.Provider
value={{ product }}>
{props.children}
</ProductContext.Provider>
)
}
export default ProductContextProvider
and this is the error I am getting in console
enter image description here
I have done this too in index.js
enter image description here
and this is one page I want to call the product[]
import React from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import { useContext } from 'react'
import ProductContext from '../context/ProductContext';
function Product() {
const { product } = useContext(ProductContext)
console.log(product);
return (
<div className="products-row ">
{
product.map((data, num) => {
return (
<div className="product" key={num}>
<div className="card">
<a target="_blank" href="/design" >
<img src={data.thumbnail} alt={data.name} style={{ width: "100%" }} ></img>
</a>
<h1>{data.name}</h1>
<p className="price">${data.price}</p>
</div>
</div>
)
})
}
</div>
);
}
export default Product;
I believe it's an import issue. You probably meant to use the following:
import { ProductContext } from '../context/ProductContext';
Right now, your ProductContext is actually ProuductContextProvider, which is the default export as per your code.

Using the useContext Hook to create a simple To-Do List app in React

Can someone tell me if there is anything wrong with the way I have used the Context API in this code. And if there is something wrong can you explain why?
These two are my Contexts
import React from "react";
export const ItemListContext = React.createContext();
import React from "react";
export const ItemContext = React.createContext();
This is my App component
import "./styles.css";
import TodoList from "./Components/TodoList";
import { ItemContext } from "./Context/ItemContext";
import { ItemListContext } from "./Context/ItemListContext";
import { useState } from "react";
export default function App() {
const [inputs, setInput] = useState("");
const [itemList, setItemList] = useState([]);
return (
<div className="App">
<ItemContext.Provider value={[inputs, setInput]}>
<ItemListContext.Provider value={[itemList, setItemList]}>
<TodoList />
</ItemListContext.Provider>
</ItemContext.Provider>
</div>
);
}
After this I have a Todo List component that looks like this :-
import React, { useContext, useState } from "react";
import { ItemContext } from "../Context/ItemContext";
import { ItemListContext } from "../Context/ItemListContext";
const TodoList = () => {
const [input, setInput] = useContext(ItemContext);
const [itemList, setItemList] = useContext(ItemListContext);
const handleChange = (e) => {
setInput(e.target.value);
};
const hanleCLick = () => {
setItemList((prevList) => [...prevList, input]);
};
const handleDelete = (i) => {
let newList = itemList.filter((item, index) => index !== i);
setItemList(newList);
};
return (
<>
<input type="text" value={input} onChange={handleChange} />
<button onClick={hanleCLick}>Add Item</button>
{itemList.map((item, index) => {
return (
<div key={index}>
<p>{item}</p>
<button onClick={() => handleDelete(index)}>Delete</button>
</div>
);
})}
</>
);
};
export default TodoList;

I can save the information in local storage and retrieve the information and show it but I don't get the information when I refresh the page

This is my ctrl:
import React, { useState, useEffect } from 'react';
import './App.css';
import Header from "./components/Header"
import AddContact from "./components/AddContact"
import ContactList from "./components/ContactList"
function App() {
const LOCAL_STORAGE_KEY = "contacts";
const [contacts, setContacts] = useState([]);
const addContactHandler = (contact) => {
console.log(contact);
setContacts([...contacts, contact]);
};
useEffect(() => {
const retriveContacts = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY));
if (retriveContacts) setContacts(retriveContacts);
}, []);
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts));
}, [contacts]);
return (
<div>
<Header />
<AddContact addContactHandler={addContactHandler} />
<ContactList contacts={contacts} />
</div>
);
}
export default App;

useContext causing blank screen

The app uses useContext for state management and axios for a get request to an API to receive data. Originally I was not using useContext but later realized state will be needed in multiple components later down the road and props would be messy. The app was working perfectly prior to using useContext now I am receiving a blank screen and no error messages.
ThemeContext.js
import {useState, useEffect, createContext} from 'react'
import axios from 'axios'
const ThemeContext = createContext()
const ThemeContextProvider = props => {
const [students, setStudents] = useState([])
const [loading, setLoading] = useState(false)
useEffect(()=>{
getStudents()
},[])
const getStudents = async () => {
try {
const res = await axios.get('https://api.hatchways.io/assessment/students')
setStudents(res.data.students)
setLoading(true)
}
catch (err) {
console.log(err.message)
}
}
return (
<ThemeContextProvider.Provider value={{students, loading}}>
{props.children}
</ThemeContextProvider.Provider>
)
}
export {ThemeContextProvider, ThemeContext}
Students.js
import {useContext} from 'react'
import {ThemeContext} from './themeContext'
const Students = props => {
const {students, loading} = useContext(ThemeContext)
return (
<div>
{loading &&
students.map((student) =>(
<div className="student-profile-container">
<div className="student-profile-image">
<img key={student.id} src={student.pic} alt="student profile avatar"/>
</div>
<div className="student-profile-info">
<h1 className="student student-name">{student.firstName} {student.lastName}</h1>
<p className="student student-info">Email: {student.email}</p>
<p lassName="student student-info">Company: {student.company}</p>
<p className="student student-info">Skill: {student.skill}</p>
<p className="student student-info">Average: {student.average}%</p>
</div>
</div>
))
}
</div>
);
}
export default Students;
It appears you are mixing up ThemeContext and ThemeContextProvider. Changing the return value of ThemeContextProvider should fix your issue.
<ThemeContext.Provider value={{students, loading}}>
{props.children}
</ThemeContext.Provider>

Unable to pass down state using react context

I am trying to use React Context successfully but I a have had a lot of trouble with it. I can't even successfully pass anything one level to the provider's immediate children, as a result all I am getting at this stage is "x is undefined" errors in the console. I am using a separate class for the context an a custom hook to manage my state data.
App.js (where TodoProvider component warps around its children) -
import logo from './logo.svg';
import './App.css';
import React, {createContext, useContext} from "react"
import TodoItem from './Components/Other/TodoItem';
import TodoList from './Components/List/TodoList';
import TodoAdd from './Components/Forms/TodoAdd';
import CompletedTaskList from './Components/List/CompletedTaskList';
import useTodo from './libs/useTodo';
import {TodoContext, TodoProvider} from "./Contexts/TodoContext"
function App() {
const {
todoArray, setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem
} = useContext(TodoContext);
return (
<TodoProvider
value={
todoArray, setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem
}
>
<div className="App">
<div className='card' id='mainCard'>
<div className='card-header' id='mainCardHeader'><h4>Todo List</h4></div>
<TodoList/>
<TodoAdd
/>
<CompletedTaskList
/>
</div>
</div>
</TodoProvider>
)
}
export default App;
TodoContext.js (My Context) -
import React, {createContext} from "react";
import useTodo from "../libs/useTodo";
const TodoContext = createContext();
const TodoProvider = ({children}) => {
const {
todoArray, setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem
} = useTodo();
return (
<TodoContext.Provider
value={
todoArray, setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem
}
>
{children}
</TodoContext.Provider>
)
}
export {TodoContext, TodoProvider}
useTodo.js (My custom hook to manage state)
import React, {useState} from "react"
const useTodo = () => {
const [todoArray, setTodoArray] = useState([{id: 1,todoTitle: "Code", todoDescription: "Practice React"},{id: 2,todoTitle: "Clean", todoDescription: "Wash dishes, wipe surfaces"}]);
const [completedTaskArray, setCompletedTaskArray] = useState(["Wake up", "Make Bed"]);
const [currentId, setCurrentId] = useState(3);
const addTodoItem = (todoTitleInputItem, todoDescriptionInputItem) => {
let todoTitle = todoTitleInputItem;
let todoDescription = todoDescriptionInputItem;
let id = currentId;
setCurrentId(currentId+1)
setTodoArray(todoArray => [...todoArray, {id,todoTitle, todoDescription}]);
}
const addCompletedItem = ({todoTitle}) => {
setCompletedTaskArray(completedTaskArray => [...completedTaskArray, todoTitle]);
}
return {
todoArray, setTodoArray,
completedTaskArray, setCompletedTaskArray,
addTodoItem,
addCompletedItem
}
}
export default useTodo;
CompletedTasklist(An example of my implementation of using a the context in one of it's children) -
import { useContext } from "react";
import {TodoContext, TodoProvider} from "../../Contexts/TodoContext"
const CompletedTaskList = () => {
const {
completedTaskArray
} = useContext(TodoContext);
return (
<div className="card todo-item">
<div className="card-header">
<h3> Completed Task</h3>
</div>
<div className="card-body">
<ul className="list-group ">
{completedTaskArray.map((item,index) => {
return <li className="list-group-item list-group-item-success" key={index}>{item}</li>
})}
</ul>
</div>
</div>
)
}
export default CompletedTaskList;
I've been trying to resolve this for a while now and cannot wrap my mind around it.
App.js
import React, { createContext, useContext } from 'react';
import CompletedTaskList from './comp';
import { TodoProvider } from './context';
function App() {
// you dont need useTodo, or TodoContext here
return (
<TodoProvider>
{/** todo Provider is a wrapper, you dont need to pass value as prop again, you are already doing it */}
<div className="App">
<div className="card" id="mainCard">
<div className="card-header" id="mainCardHeader">
<h4>Todo List</h4>
</div>
<CompletedTaskList />
</div>
</div>
</TodoProvider>
);
}
export default App;
Context
import React, { createContext } from 'react';
import useTodo from './useTodo';
// Define default values of your context data.
// otherwise everything would be undefined and you need to handle it everywhere
// you are using context
const TodoContext = createContext({
todoArray: [],
setTodoArray: () => {},
completedTaskArray: [],
addCompletedItem: () => {},
addTodoItem: () => {},
});
const TodoProvider = ({ children }) => {
const {
todoArray,
setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem,
} = useTodo();
return (
<TodoContext.Provider
value={{
// <--- you were passing value incorrectly here, it should be an object
// you passed it as (......) instead of {...}
// curly braces not paranthesis
todoArray,
setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem,
}}
>
{children}
</TodoContext.Provider>
);
};
export { TodoContext, TodoProvider };
Repasting the answer from the link i shared above.

Resources