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>
Related
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.
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
I´m getting an error when trying to render an image in component.
I paste the code here.
Is it possible that I need a babel or webpack plugin?
In this component, the image rendering works fine:
import React from "react";
function ProductItem({ product }) {
return product ? (
<div>
<div>
<img src={product.images[0]} alt={product.title} />
</div>
<div>
{product.title}
<br />
${product.price}
</div>
<p>{product.description}</p>
</div>
) : <p>Loading Product... </p>;
};
export default ProductItem;
In this other component is where I have the problem.
ProductDetail.js
import React from "react";
import useGetProducts from "../hooks/useGetProducts";
const API = 'https://api.escuelajs.co/api/v1/products';
function ProductDetail() {
const data = useGetProducts(`${API}/6`);
return (
<>
{data.products
?
<>
<h3>{data.products.title}</h3>
<p>{data.products.description}</p>
<div>
<img src={data.products.images[0]} alt="title" />
</div>
</>
: <h4>Loading...</h4>
}
</>
);
}
export default ProductDetail;
Custom Hook with useEffect, the useGetProducts function is responsible for bringing the data from the API with the Axios library
import { useEffect, useState } from "react";
import axios from "axios";
const useGetProducts = (API) => {
const [products, setProducts] = useState([])
const [error, setError] = useState("");
const [loaded, setLoaded] = useState(false);
useEffect(() => {
(async () => {
try {
const response = await axios(API);
setProducts(response.data);
} catch (error) {
setError(error.message);
} finally {
setLoaded(true);
}
})();
}, []);
return { products, error, loaded };
};
export default useGetProducts
Your default state for products is [], so the conditional render data.products in ProductDetail.js always return true so you can change default state for products is null
const [products, setProducts] = useState(null);
The first answer is correct, so I will not duplicate it, but I see room for improvement in your code/example.
Your useGetProducts hook is very easy to break and hard to reuse. If you will pass the wrong URL or the structure of the API will change it will break your code. Also, the hook is not very generic, cause you will need to create similar fn for each entity. My suggestion. Use react-query and separate functions for calling API. So it will look like this.
import { useQuery } from 'react-query'
import axios from 'axios'
export default function ProductPage() {
const productResponse = useQuery('exchanges', () => getProduct('6'))
const { isLoading, isError, data: product } = productResponse
return (
<div>
{isLoading && <div>Loading...</div>}
{isError && <div>Something went wrong :(</div>}
{product && (
<div>
<h1>Product title: {product.title}</h1>
<p>
{product.images.map(imageSrc => (
<img key={imageSrc} src={imageSrc} alt="" />
))}
</p>
</div>
)}
</div>
)
}
interface Product {
id: string
title: string
images: string[]
}
function getProduct(id: string): Promise<Product> {
return axios
.get(`https://api.escuelajs.co/api/v1/products/${id}`)
.then(r => r.data)
}
PS. react-query requires additional configuration ( context provider, config, etc ). Please look into docs on how to use it.
Hey still new to React but I'm grinding my way through it slowly by building my own personal app/platform. I have a quick question of passing down props to single page views. This is my overview page that will pull in all the teams from my database as such:
import React, { useState, useEffect } from 'react';
import firebase from '../../firebase/firebase.utils'
import Button from '../../Components/GeneralComponents/Button.component'
import * as GoIcons from 'react-icons/go';
import TeamList from '../../Components/Teams/TeamList.Component'
function TeamsPage() {
const [teams, setTeams] = useState([]);
const [loading, setLoading] = useState(false);
const ref = firebase.firestore().collection("teams");
function getTeams() {
setLoading(true);
ref.onSnapshot((querySnapshot) => {
const items = [];
querySnapshot.forEach((doc) => {
items.push(doc.data());
});
setTeams(items);
setLoading(false);
console.log(items);
});
}
useEffect(() => {
getTeams();
},[])
if(loading) {
return <h1>Loading...</h1>
}
return (
<div className="content-container">
<h2>Team Page</h2>
<div className="add-section">
<div className="actions">
<Button
className="bd-btn outlined add-team"
><GoIcons.GoGear/>
Add Team
</Button>
</div>
</div>
<TeamList teams={teams} />
</div>
)
}
export default TeamsPage;
This gets passed into my TeamList Component:
import React from 'react';
import { Link } from 'react-router-dom'
import { TeamCard } from './TeamCard.Component';
const TeamList = props => {
return(
<div className='teams-overview'>
{props.teams.map(team => (
<Link to={`/teams/${team.id}`}>
<TeamCard key={team.id} team={team}/>
</Link>
))}
</div>
)
}
export default TeamList;
Which maps through and then list the Team as a card component with a link that is supposed to route to their id and pass through their data.
Now in my single page view of a team I'm struggling to gain access to that prop data:
import React from 'react'
function TeamSinglePage(team) {
return (
<div className="content-container">
<h1>Single Page View</h1>
<p>Welcome, {team.teamName}</p>
</div>
)
}
export default TeamSinglePage;
"EDITED"
I'm trying to make a very minimalist bar chart.
It doesn't, render. It seems that the child component isn't rendering after the parent component's state has changed.
for example. if I make a change in my code in the MiniChart component, and save the changed through my IDE. the charts will render correctly. but as soon as navigate to another page in my app through my browser and come back to where the charts are, then they won't render.
Any help will be much appreciated.
Child component:
import React, {useState, useEffect} from 'react';
import axios from 'axios';
import {BarChart, Bar} from 'recharts';
const MiniChart = (props) => {
const [apiUrl] = useState("https://api.coingecko.com/api/v3/coins/"+props.id+"/market_chart?vs_currency=usd&days=30&interval=daily");
const [data, setData] = useState([]);
const [msg, setMsg] = useState([]);
const [r, setR] = useState([]);
// fetch data from api
useEffect(() => {
const fetchData = async () => {
if(parseInt(props.rank) < 5){
const result = await axios(apiUrl,);
setData(result.data.prices);
} else {setMsg("TEST : not loaded");}
}
setR(data.map(elem => ({ 'val': elem[1]})));
fetchData();
return()=>{
}
}, [apiUrl, props.rank]);
return (
<div>
<BarChart width={150} height={40} data={r}>
<Bar dataKey='val' fill="green" />
</BarChart>
</div>
)
}
export default MiniChart
Parent component:
import React, { useState} from 'react'
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faStar } from "#fortawesome/free-solid-svg-icons";
import { Link, useLocation } from 'react-router-dom';
import Cookies from 'universal-cookie';
import MiniChart from './MiniChart';
const Crypto = (props) => {
const location = useLocation();
const [starColor, setStarColor] = useState(props.defaultStarCol);
const cookies = new Cookies();
const getFavs = cookies.getAll();
// toggle color, re-render, remove or add to cookies
const handleFavToggle = (e) => {
if(starColor === '#ebc934'){
setStarColor('lightgrey');
cookies.remove(props.id, props.id, { path: '/' });
if(location.pathname === '/favorites'){
function refreshPage() {
window.location.reload(false);
}
refreshPage();
}
} else {
setStarColor('#ebc934');
cookies.set(props.id, props.id, { path: '/' });
//console.log(cookies.getAll());
}
}
return (
<div>
<li>
<div className="lidiv">
{props.id in getFavs? //ADD IF LOGGED IN !
<p className="pml"><FontAwesomeIcon style={{color:'#ebc934'}} onClick={handleFavToggle} className="star" icon={faStar}/></p>
: <p className="pml"><FontAwesomeIcon style={{color:'lightgrey'}} onClick={handleFavToggle} className="star" icon={faStar}/></p>}
<p className="pml">{props.rank}</p>
<img className="iconimg" src={props.img} alt=""/>
<p className="pxl coinName"><Link to="/crypto" style={{display: 'block'}}>{props.coin}</Link></p>
<p className="pml">{props.tag}</p>
<p className="pml4">{props.price}</p>
<p className="pml" style={{color: (props.oneday).charAt(0)==='-' ? 'red': 'green'}}>{props.oneday}%</p>
<p className="pxl daycash" style={{color: (props.oneday).charAt(0)==='-' ? 'red': 'green'}}>{props.onedaycurr} </p>
<p className="pxl-4">{props.mcap}M</p>
<MiniChart className="pxl" id={props.id} rank={props.rank}></MiniChart>
</div>
</li>
</div>
)
}
export default Crypto;