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.
Related
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>
React newcomer here.
I'm loading Astronomy Picture of the Day in a component using a loading spinner.
I want the page to get data every time I call it from navbar but it's flashing old data before showing the spinner.
How to avoid this behavior? I don't want to use ComponentWillMount because it's deprecated and I'm using functions.
The component code:
import { useEffect, useContext } from 'react'
import { getApod } from '../context/nasa/NasaActions'
import NasaContext from '../context/nasa/NasaContext'
import Spinner from './layout/Spinner'
function Apod() {
const {loading, apod, dispatch} = useContext(NasaContext)
useEffect(() => {
dispatch({type: 'SET_LOADING'})
const getApodData = async() => {
const apodData = await getApod()
dispatch({type: 'SET_APOD', payload: apodData})
}
getApodData()
}, [dispatch])
const {
title,
url,
explanation,
} = apod
if (loading) { return <Spinner /> }
return (
<div>
<h2>{title}</h2>
<img src={url} className='apod' alt='apod'/>
<p>{explanation}</p>
</div>
)
}
export default Apod
Thanks for your time.
Edit: I deleted the repository. It's already answared correctly.
I suggest you another solution to keep your navbar clean.
You can declare an instance variable loaded using the useRef hook. This variable will be initialized to false and set to true as soon as the apod is dispatched to your store.
import { useContext, useRef } from 'react'
function Apod() {
const {apod, dispatch} = useContext(NasaContext)
const loaded = useRef(false);
const {title, url, explanation} = apod
useEffect(() => {
dispatch({type: 'SET_LOADING'})
const loadApod = async() => {
const apodData = await getApod()
loaded.current = true;
dispatch({type: 'SET_APOD', payload: apodData})
}
loadApod()
}, [dispatch])
if (!loaded.current) { return <Spinner /> }
return (
<div>
<h2>{title}</h2>
<img src={url} className='apod' alt='apod'/>
<p>{explanation}</p>
</div>
)
}
export default Apod;
I had an idea, to clean the object in Context using onClick on the navbar button.
Is this the best way? I don't know but it's working as I wanted.
import NasaContext from '../../context/nasa/NasaContext'
import { useContext } from 'react'
import { Link } from 'react-router-dom'
import logo from './assets/logo.png'
function Navbar() {
const {dispatch} = useContext(NasaContext)
const resetApod = () => {
const pathname = window.location.pathname
if ( pathname !== '/' ) {
dispatch({type: 'SET_APOD', payload: {}})
}
}
return (
<div className="navbar">
<div className="navbar-logo">
<img src={logo} alt='Experimentum'/>
</div>
<div className="navbar-menu">
<Link to='/' onClick={resetApod}>APOD </Link>
<Link to='/about'>ABOUT </Link>
</div>
</div>
)
}
export default Navbar
I'm trying to build a crypto tracker where you can add the items by clicking a button. Each time the button is clicked, the array should be added to the storage with the name "crypto" and then on another component where it is the portfolio one we should be able to get the items.
Here is where I set the item to an array whenever I click the add button:
import React, {useEffect, useState} from 'react'
import axios from 'axios'
import './tracker.css'
import Navigation from './Nav.js'
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
function Tracker() {
const [data, setData] = useState([])
const [portfolio, setPortfolio] = useState([])
useEffect(() => {
setInterval(() => {
const fetchData = async () => {
const result = await axios('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=100&page=1&sparkline=false' , {
'mode': 'no-cors',
'headers': {
'Access-Control-Allow-Origin': '*',
}
})
setData(result.data)
}
fetchData()
}, 1000)
}, [])
return (
<div>
<Navigation />
<div className="tracker__names">
<b>Coins</b>
<b>Symbol</b>
<b>Price</b>
<b>Market Cap</b>
</div>
{data.map((coins, i) => {
const addToPortfolio = () => {
setPortfolio([...portfolio, data[i]])
localStorage.setItem('crpyto', JSON.stringify(portfolio))
}
return (
<>
<div className="tracker__main">
<div className="tracker__img">
<img src={coins.image} className="tracker__image"/>
<button key={i} onClick={addToPortfolio}>{coins.id}</button>
</div>
<div className="tracker__symbol">
<p>{coins.symbol}</p>
</div>
<div className="tracker__price">
<p></p>
${coins.current_price}
</div>
<div className="tracker__market">
<p></p>
${coins.market_cap}
</div>
</div>
</>
)
})}
</div>
)
}
export default Tracker
Here is the component where I want to get the item:
import React, {useState, useEffect} from 'react'
import Navigation from './Nav.js'
function Portfolio() {
const [value, setValue] = useState(JSON.parse(localStorage.getItem('crypto')) || '')
useEffect(() => {
console.log(value)
}, )
return (
<div>
<Navigation />
{value}
</div>
)
}
export default Portfolio
It is because useState is executed before JSON.parse(localStorage.getItem('crypto')) and once you get the value from the localstorage, component doesn't re-render.
Instead do:
useEffect(() => {
const crypto = JSON.parse(localStorage.getItem('crypto'))
if(crypto) setValue(crypto)
}, [])
In React you can't set a state var and on the next line save it in localStorage (or even read it). This because setPortfolio is async!
To solve this you have I think 2 ways:
store value and not state variable:
localStorage.setItem('crpyto', JSON.stringify([...portfolio, data[i]]))
use an useEffect hook:
useEffect(() => {
localStorage.setItem('crpyto', JSON.stringify(portfolio))
}, [portfolio])
First of all, when yo uare setting state like this, in the next block of code, portfolio won't necessarily have the updated state.
setPortfolio([...portfolio, data[i]])
localStorage.setItem('crpyto', JSON.stringify(portfolio))
update the portfolio like this.
const newPortfolio = [...portfolio, data[i]];
setPortfolio(newPortfolio )
localStorage.setItem('crpyto', JSON.stringify(newPortfolio))
I'm trying to render the sorted array of objects using useMemo. Currently the last sorted array is rendering on the screen. But i want to use the select drop down where users can select different sort like title shown in code using useMemo. The users can sort by selecting title, author image.
I have used redux for sorting the array of objects.Could someone please help me with the best practice. Thanks.
I have added Post.js below the HomePage.js. Is my approach to it is wrong? Should i change the approach?
Any suggestions will be helpful.Could someone suggest me the best practies for it. Any suggestions on what am i doing wrong here?
HomePage.js
import React, { useState, useEffect, useMemo } from "react";
import Post from "../../Components/Post/Post";
import "./HomePage.css";
import axios from "axios";
const HomePage = () => {
const [posts, setPosts] = useState("");
let config = { Authorization: "................" };
const url = ".........................";
useEffect(() => {
AllPosts();
}, []);
const AllPosts = () => {
axios
.get(`${url}`, { headers: config })
.then((response) => {
const allPosts = response.data.articles;
console.log(response);
})
.catch((error) => console.error(`Error: ${error}`));
};
const newPostsByTitle = useMemo(() => {
allPosts.sort((a, b) => a.title.localeCompare(b.title)), [posts];
});
return (
<div className="home">
<div className="select">
<select
name="slct"
id="slct"
onChange={(e) => newPostsByTitle(e.target.value)}
></select>
</div>
<Post className="Posts" posts={posts} key={posts.title} />
</div>
);
};
export default HomePage;
Post.js
import React from "react";
import "./Post.css";
import { Fragment } from "react";
const Post = (props) => {
const displayPosts = (props) => {
const { posts } = props;
if (posts.length > 0) {
return posts.map((post) => {
return (
<Fragment>
<div className="Post" key={post.title}>
<img
src={post.urlToImage}
alt="covid"
width="100%"
className="img"
/>
<h5 className="title"> {post.title}</h5>
<p className="author"> {post.author}</p>
<p className="description"> {post.description}</p>
</div>
</Fragment>
);
});
}
};
return <div className="Posts">{displayPosts(props)}</div>;
};
export default Post;
There are few issues with the useMemo function.
There is no allPosts variable that will be available for that
function
There is no return inside useMemo
The dependency array syntax is wrong.
It should be something like the following.
const newPostsByTitle = useMemo(() => {
return [...posts].sort((a, b) => a.title.localeCompare(b.title));
}, [posts]);
I have been studying React for past few days. In my blog project, I am using Axios to get data from API. Here is my component:
import React, { useState, useEffect } from "react";
import axios from "axios";
import { apiConstants } from "../../constants";
import SinglePost from "./SinglePost";
const PostContent = props => {
const {
match: { params }
} = props;
const [post, setPost] = useState({});
useEffect(() => {
axios
.get(apiConstants.singlePost + `${params.post_slug}`)
.then(function(response) {
setPost(response.data);
})
.finally(function() {
// always executed
});
}, []);
return (
<React.Fragment>
<div className="container">
<div className="row">
<div className="col-lg-8 col-md-10 mx-auto">
<SinglePost post={post} />
</div>
</div>
</div>
</React.Fragment>
);
};
export default PostContent;
Above code works fine though I noticed the first time it tries to render the component with empty ({}) post object (Due to the default value in 'useState'). However, it causes issues in my child component cause it is directly using 'post' object properties. For example: 'post.content'. Here is my 'SinglePost' component's code:
const SinglePost = props => {
const { post } = props;
console.log(post);
return (
<div>{post.content}</div>
);
};
It returns undefined error for {post.content} object. To resolve the issue I had to use something like {post && <SinglePost post={post} />}, But it doesn't feel right. Is there any better way to handle such scenarios.
Consider revising the PostContent component's rendering logic to account for the case where no post data is present during the network request.
You could for instance initialise your post state to null, and then update the rendered result to prevent the SinglePost component from being rendered while post is null.
Once the network request is completed and the post state is set, the component will re-render causing SinglePost to be rendered with the non-null post state:
const PostContent = props => {
const {
match: { params }
} = props;
const [post, setPost] = useState(null); /* Update: "Initial data" set to null */
useEffect(() => {
axios
.get(apiConstants.singlePost + `${params.post_slug}`)
.then(function(response) {
setPost(response.data);
})
.finally(function() {
// always executed
});
}, []);
return (
{ /* <React.Fragment> Not needed */ }
<div className="container">
<div className="row">
<div className="col-lg-8 col-md-10 mx-auto">
{ /* Update: If post is null, assume network request is
still in progress so rendering loading message instead
of single post component */ }
{ post === null ? <p>Loading</p> : <SinglePost post={post} /> }
</div>
</div>
</div>
);
};
export default PostContent;
This approach is generally the simplest and more common pattern to async requests and rendering.
There are some other approaches that you might want to consider such as this declarative approach to data fetching, or the use of Reacts suspense feature for asynchronous rendering
You need to make post's initial value an array:
import React, { useState, useEffect } from "react";
import axios from "axios";
import { apiConstants } from "../../constants";
import SinglePost from "./SinglePost";
const PostContent = props => {
const {
match: { params }
} = props;
const [post, setPost] = useState([]);
useEffect(() => {
axios
.get(apiConstants.singlePost + `${params.post_slug}`)
.then(function(response) {
setPost(response.data);
})
.finally(function() {
// always executed
});
}, []);
return (
<React.Fragment>
<div className="container">
<div className="row">
<div className="col-lg-8 col-md-10 mx-auto">
<SinglePost post={post} />
</div>
</div>
</div>
</React.Fragment>
);
};
export default PostContent;
and in single post map through the array
const SinglePost = props => {
const { post } = props;
console.log(post);
return (
<div>
{post.map((post, key) => (
<div key={key}>{post.content}</div>
))}
</div>
);
};
You can do something like
import React, { useState, useEffect } from "react";
import axios from "axios";
import { apiConstants } from "../../constants";
import SinglePost from "./SinglePost";
const PostContent = props => {
const {
match: { params }
} = props;
const [posts, setPosts] = useState([]);
useEffect(() => {
axios
.get(apiConstants.singlePost + `${params.post_slug}`)
.then(function(response) {
setPosts(response.data);
})
.finally(function() {
// always executed
});
}, []);
return (
<React.Fragment>
<div className="container">
<div className="row">
<div className="col-lg-8 col-md-10 mx-auto">
{this.state.posts.map(post => (<SinglePost post={post} key={post.id} />))
</div>
</div>
</div>
</React.Fragment>
);
};
export default PostContent;