I'm trying to play with Coingecko API just to get better at React but I'm already stuck ahah
The general app is very simple for now, index is displaying cards of all tokens with ticker and price. And each card is clickable to go on a more detailed page.
App.js
import axios from "axios";
import React, { useEffect, useState } from "react";
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
import SingleCoin from "./pages/SingleCoin";
function App() {
const [coinsData, setCoinsData] = useState([]);
useEffect(() => {
axios
.get(
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=250&page=1&sparkline=false&price_change_percentage=1h%2C24h%2C7d%2C14d%2C30d%2C200d%2C1y"
)
.then((res) => setCoinsData(res.data));
}, []);
const Home = () => {
return (
<>
<h2>Home</h2>
<div className="app">
{coinsData.map((coin) => {
return (
<div className="coin-card" key={coin.id}>
<h2>{coin.id}</h2>
<p>
Ticker: <span className="symbol">{coin.symbol}</span>
</p>
<p>Prix: {coin.current_price} $</p>
<Link to={`/pages/${coin.id}`}>Plus d'infos</Link>
</div>
);
})}
</div>
</>
);
};
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/pages/">Single Coin</Link>
</li>
</ul>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route
path="/pages/:id"
element={<SingleCoin />}
handler={coinsData.id}
/>
</Routes>
</div>
</Router>
);
}
export default App;
SingleCoin.jsx
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import axios from "axios";
import SinglePrice from "../components/SinglePrice";
const SingleCoin = () => {
const { id } = useParams();
const [SingleCoin, setSingleCoin] = useState({});
useEffect(() => {
axios
.get(
`https://api.coingecko.com/api/v3/coins/${id}`
)
.then((res) => setSingleCoin(res.data));
}, [id]);
return (
console.log(SingleCoin),
<article>
{/* <div className="img">
<img src={SingleCoin.image.thumb} alt="logo" />
</div> */}
<h1>{SingleCoin.symbol}</h1>
<p>{SingleCoin.coingecko_rank}</p>
<p>{SingleCoin.market_data.current_price.usd}</p>
{/* <SinglePrice /> */}
<br />
</article>
);
};
When I write SingleCoin.market_data.current_price.usd for the first time, it works perfectly. But if I just go back to Home and go to the exact same token, it shows this error:
Uncaught TypeError: Cannot read properties of undefined (reading
'current_price')
I don't understand why the first time it works ans then it bugs. I'm probably missing something with my weak logic ahah.
A big thanks in advance guys!
PS : Little update: I see that my SingleCoin const is empty when I refresh, even when I leave the page and come back. So I'm probably doing something wrong with my useEffect but I'm definitely too dumb to see it
Ok, that's crazy how much it helps me to ask questions sometimes ahah.
I think I found the way (not sure it is the best way but it works)
I just add optional chaining operator to the price and it works :
<p>{SingleCoin?.market_data?.current_price?.usd}</p>
Not sure this is the best way to proceed but if it can helps others, here is a solution!
Related
I'm new to react-router v6
I have 4 components, App, CardList, Card and CardInfo. There is data (an array of objects, each object represents a movie) coming from an API that gets saved in App.js with useState hook.
Within CardList, I use map to iterate over the array to generate a bunch of Card components and passing in data via props.
What I want now is to be able to click on any Card component and for it to navigate to a different route, e.g. localhost:3000/1 (for Card with the id of 1), localhost:3000/2 (for Card with the id of 2) etc. and within each route that corresponds to the Card id, there would be a box/modal (CardInfo.js) component with further information about the movie.
I'm trying to accomplish this with react-router-dom (version 6).
It looks like within the CardList.js or Card.js component you would need to create links (<Link>) and routes (<Route>) (both which are equal to the number of movies in the data) on the fly with the .map function and wrapping the Card component in <Link> and <Route> tags. Something like
{items.map(movie => (
<Route path="/:id" element={<Card items={movies} />} exact>
<Link to={`/${movie.id}`}>
<Card
key={movie.id}
id={movie.id}
name={movie.name}
description={movie.description}
img={movie.image_url}
/>
</Link>
</Route>
))}
Obviously that doesn't work.
App.js:
import './App.css';
import CardList from './features/Card/CardList';
import CardInfo from './features/Card/CardInfo';
import { useState, useEffect } from "react";
import { BrowserRouter, Route, Routes } from 'react-router-dom';
function App() {
const [movies, setMovies] = useState([]);
useEffect(() => {
const getData = fetch('https://api.com/movies')
.then(data => data.json())
.then(items => { setMovies(items) })
}, [])
return (
<BrowserRouter>
<Routes>
<Route path="/:id" element={<CardInfo items={movies} />} exact></Route>
</Routes>
<CardList items={movies} />
</BrowserRouter>
);
}
export default App;
CardList.js:
import React from 'react'
import Card from './Card'
import "./CardList.css";
const CardList = ({ items }) => {
return (
<div className="cardList">
{items.map(movie => (
<Card
key={movie.id}
id={movie.id}
name={movie.name}
description={movie.description}
img={movie.image_url}
/>
))}
</div>
)
}
export default CardList
Card.js:
import React from 'react'
import "./Card.css";
import { Link } from 'react-router-dom';
function Card(props) {
return (
<Link to={`/${props.id}`}>
<div className="card">
<div>
<img src={props.img} />
<h2>{props.name}</h2>
</div>
</div>
</Link>
)
}
export default Card
CardInfo.js:
import React from 'react'
function CardInfo(props) {
return (
<div>
<p>{props.description}</p>
</div>
)
}
export default CardInfo
your code structure is correct. You just need to useLink, you don't need en external Route component for every card you map.
Here is a link for further information. Hope you find it helpful.
https://stackoverflow.com/a/57059249/17715977
Yo-yo everyone,
along my path of practicing the art of React, I noticed a bug that I couldn't seem to find a good source to help me understand what causes the problem.
My array in a child component takes too long to load, resulting in an error.
The data is fetched from "jsonplaceholder," users list.
Data is set as a state.
Sent to "UserProfilePage".
Sent to "UserProfileComponent".
Trying to reach the URL "/user/1" will not succeed since the object is undefined.
*) Commenting the "UserProfileComponent," and then uncomment without refreshing will successfully load the page.
*) Coping (not fetching) the data to the App.js, assigning it to the state, will not crush the system.
APP.js
import { Component } from "react";
import { Redirect, Route, Switch } from "react-router-dom";
import "./App.css";
import Navigation from "./components/header/Navigation";
import PostsLog from "./components/Posts/PostsLog";
import UserProfileCollection from "./pages/UserProfileCollection";
import UserProfilePage from "./pages/UserProfilePage";
const POST_ENDPOINT = "https://jsonplaceholder.typicode.com/posts";
const USER_ENDPOINT = "https://jsonplaceholder.typicode.com/users";
class App extends Component {
constructor() {
super();
this.state = {
exUsersArray: [],
exPostsArray: [],
};
}
async componentDidMount() {
try {
const responseUser = await fetch(USER_ENDPOINT);
const responsePost = await fetch(POST_ENDPOINT);
const dataResponseUser = await responseUser.json();
const dataResponsePost = await responsePost.json();
this.setState({ exUsersArray: dataResponseUser });
this.setState({ exPostsArray: dataResponsePost });
} catch (error) {
console.log(error);
}
}
render() {
const { exUsersArray, exPostsArray } = this.state;
console.log(exUsersArray);
return (
<div className="app">
<Navigation />
<main>
<Switch>
{/* REROUTES */}
<Route path="/" exact>
<Redirect to="/feed" />
</Route>
<Route path="/users" exact>
<Redirect to="/user" />
</Route>
{/* REAL ROUTES */}
<Route path="/feed">
<PostsLog usersInfo={exUsersArray} usersPosts={exPostsArray} />
</Route>
<Route path="/user" exact>
<UserProfileCollection usersInfo={exUsersArray} />
</Route>
{/* DYNAMIC ROUTES */}
<Route path="/user/:userId">
<UserProfilePage usersInfo={exUsersArray} />
</Route>
</Switch>
</main>
</div>
);
}
}
export default App;
UserProfilePage.js
import { useParams } from "react-router-dom"
import UserProfileComponent from "../components/UserProfileComponent";
const UserProfilePage = ({usersInfo}) => {
const params = useParams();
const foundUser = usersInfo.find((user) => Number(user.id) === Number(params.userId))
console.log("found user ", foundUser);
// console.log(usersInfo);
console.log(params, " is params");
return(
<div>
<UserProfileComponent userProfile={foundUser}/>
<p>Yo YO</p>
</div>
)
}
export default UserProfilePage;
UserProfileComponent
const UserProfileComponent = ({userProfile}) => {
console.log(userProfile)
return (
<div className="text-group">
<div className="wrap-post">
<p>
<strong>Info</strong>
</p>
<img
src={`https://robohash.org/${userProfile.Id}.png`}
id="small-profile"
alt="user profile in circle"
/>
<p><u><strong>ID</strong></u> : {userProfile.id}</p>
<p>Name: {userProfile.name}</p>
<p>#{userProfile.username}</p>
<p>Email: {userProfile.email}</p>
<p>
{userProfile.address.street} {userProfile.address.suite}<br/>
{userProfile.address.zipcode} {userProfile.address.city}
</p>
<p>Global position</p>
<p>{userProfile.address.geo.lat}, {userProfile.address.geo.lang}</p>
<p>{userProfile.phone}</p>
<p>{userProfile.website}</p>
<p>Company</p>
<p>{userProfile.company.name}</p>
<p>{userProfile.company.catchPhrase}</p>
<p>{userProfile.company.bs}</p>
</div>
</div>
);
};
export default UserProfileComponent;
Complete repository here.
I will be happy to any tips to help me understand what happened here.
Appreciation will be given to any tip that will help me be a better programmer.
Best wishes y'all.
it seems like usersInfo hasn't loaded a quick way to fix it is to just add this to the users component.
UserProfilePage.js
import { useParams } from "react-router-dom"
import UserProfileComponent from "../components/UserProfileComponent";
const UserProfilePage = ({usersInfo}) => {
const params = useParams();
if(!usersInfo) {
return <p>Loading...</p>
}
const foundUser = usersInfo.find((user) => Number(user.id) === Number(params.userId))
console.log("found user ", foundUser);
// console.log(usersInfo);
console.log(params, " is params");
return(
<div>
<UserProfileComponent userProfile={foundUser}/>
<p>Yo YO</p>
</div>
)
}
export default UserProfilePage;
UserProfileComponent.js
const UserProfileComponent = ({userProfile}) => {
if(!userProfile) {
return <p>Loading...</p>
}
console.log(userProfile)
return (
<div className="text-group">
<div className="wrap-post">
<p>
I see that you're rendering your compoonent without doing any null check in UserProfileComponent. Actually to be a better programmer or doing better work, you have to control every null case in order not to crash your app.
<p><u><strong>ID</strong></u> : {userProfile.id}</p>
<p>Name: {userProfile.name}</p>
<p>#{userProfile.username}</p>
<p>Email: {userProfile.email}</p>
<p>
{userProfile.address.street} {userProfile.address.suite}<br/>
{userProfile.address.zipcode} {userProfile.address.city}
</p>
<p>Global position</p>
<p>{userProfile.address.geo.lat}, {userProfile.address.geo.lang}</p>
<p>{userProfile.phone}</p>
<p>{userProfile.website}</p>
<p>Company</p>
<p>{userProfile.company.name}</p>
<p>{userProfile.company.catchPhrase}</p>
<p>{userProfile.company.bs}</p>
You'll see that there's no null check. It would be better if you have some null check on your userProfile
Also, my suggestion is, you can create a loading in your state.
Before sending your request, you can set the loading to true.
And when your loading is true, you can show some spinner or sth like that. When your request finishes, you can set the loading variable to false and you can show your data.
The main point is, always use a loading variable to check the loading state instead of checking the null | undefined state of your data.
I want to redirect the page if the length of props.fields is zero. But the problem it is getting rendered twice so when the it is empty it gets redirected and doesn't wait for the second time.
I am new to React, it would be great if someone could help me.I have spent hours on fixing this.
import React, { Component, useEffect, useState } from 'react'
import Field from './field'
import {Redirect} from 'react-router-dom'
export default function FieldList(props) {
const [redirect,setRedirect] = useState(false);
useEffect(()=>{
if(props.fields.length===0) {
setRedirect(true)
}
else
setRedirect(false)
},[props.fields])
return (
<div>
<h1 style={{textAlign:"center", margin:"20px 0"}}>{props.text}</h1>
<div className='field-list'>
{props.fields.map((name) => (
<Field name={name} />
))}
</div>
{redirect && <Redirect to={'/'} />}
</div>
)
}
you can instead of Redirect either use a custom browser history or use react-router-dom's useHistory hook
to use useHistory hook
import {useHistory} from 'react-router-dom'
and then at theuseEffect
useEffect(()=>{
if(props.fields.length===0) {
useHistory().push('/')
}
},[props?.fields]
)
if you don't want to use that, here is what you can do to create a custom browser history
first create a file and call it History.js
add this line of code to it
import { createBrowserHistory } from "history";
export default createBrowserHistory();
and then import it to the file
import history from "FILE_PATH"
useEffect(()=>{
if(props.fields.length===0) {
history.push('/')
}
}
)
You should change your jsx to
<div>
<h1 style={{textAlign:"center", margin:"20px 0"}}>{props.text}</h1>
<div className='field-list'>
{props.fields.map((name) => (
<Field name={name} />
))}
</div>
{redirect ? <Redirect to={'/'} /> : null}
</div>
This displays a card component that the user sees if the user clicks the <Link>read</Link> it should re-render a new page.
import React from "react";
import { Button } from "react-bootstrap";
import "./CardComponent.css";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import Content from "./ContentFolder/Content";
function CardComponent(props) {
return (
<Router>
<div class="card">
<div className="uppercard">
<img
className="bookCover"
src={props.img}
alt=""
width="120px"
height="150px"
/>
<h3>{props.title}</h3>
<h6>By{props.author}</h6>
</div>
<div className="lowerCard">{props.points}</div>
<Link to={"/" + props.title + props.author}>Read</Link>
</div>
<Switch>
<Route
exact
path={`/${props.title+props.author}`}
component={Content}
>
<Content title={props.title} author={props.author}
points={props.points}
/>
</Route>
</Switch>
</Router>
);
}
export default CardComponent;
On clicking read I want to render this content component on a different page.
In summary, the goal is to display all the information on a new page when the user clicks on one of the card components.
import React from "react";
import Mynavbar from "../Partials/Mynavbar";
import MyFooter from "../Partials/Footer";
import { Container } from "react-bootstrap";
import "./Content.css";
function Content(props) {
return (
<div>
<Mynavbar />
<Container className="main">
<h4>{props.title}</h4>
<h6>By {props.author}</h6>
<ul>
{props.points.map((point, i) => {
return <li>{point}</li>;
})}
</ul>
</Container>
<MyFooter />
</div>
);
}
export default Content;
Problem: Router inside of CardComponent
The Router needs to exist at the highest level of the App. Everything that is inside of the Router and outside of the Switch will be rendered on every page. So right now your card code will show up even on the Content route. We want the Card and the Content to be separate Routes.
Problem: Ambiguous URL Structure
Do you need for your urls to look like "/${props.title+props.author}"? This is a very bad structure because you cannot possibly work backwards from the URL to the content. What is the content for "/Harry PotterJ.K. Rowling"? Which part is the title and which part is the author? There is no separator so you don't know. You would have to loop through a list of all books, joining their title and author and comparing it to your string.
A typical URL would be based on an id, like "/book/5". I don't see any mention of an id here so we can use the title.
Solution
An app routing might look like this:
function App() {
return (
<Router>
<Switch>
<Route path="/book/:title" component={BookDetails}/>
<Route path="/" component={BookList}/>
</Switch>
</Router>
)
}
Let's get rid of all the routing in CardComponent and make it just show a card for a book with a link to the book details.
function CardComponent(props: Book) {
return (
<div className="card">
<div className="uppercard">
<img
className="bookCover"
src={props.img}
alt=""
width="120px"
height="150px"
/>
<h3>{props.title}</h3>
<h6>By{props.author}</h6>
</div>
<div className="lowerCard">{props.points}</div>
<Link to={"/book/" + props.title}>Read</Link>
</div>
);
}
Our home page might show a list of these cards.
function BookList() {
// get books from somewhere -- a database? a json file?
const books = ???;
return (
<ul className="bookList">
{books.map((book) => (
<CardComponent {...book} key={book.title} />
))}
</ul>
);
}
BookDetails is a separate route, so we need to get the book from the URL.
function BookDetails(props: RouteComponentProps) {
// get the title from the URL
// is automatically encoded and needs to be decoded
const title = decodeURIComponent(props.match.params.title);
// find the book object from your data source
const book = ???
// from a JSON array: BOOKS.find(book => book.title.toLowerCase() === title.toLowerCase() );
// redirect to error page if no matching book
if ( ! book ) {
return <Redirect to="/404" />
}
// can render your Content component, but only after we get the book
return (
<Content {...book} />
)
}
I am working in React.I have created a button ,which on click should lead the user to the newpage.I made a component About and imported it as well.
I created a function routeChange which would direct to a new page on Clicking the button.But when the button is clicked I am not being directed to any page .
Instead I get an error.
Probably there is not any error with folders.
I imported my About Component as:
import React from 'react';
import {Navbar,NavbarBrand, Jumbotron, Button} from 'reactstrap';
import './App.css';
import Description from './Description';
import './description.css';
import {useHistory,withRouter} from "react-router-dom";
import About from './About';
function App() {
const history=useHistory();
routeChange = () =>{
this.history.push('/About');
}
return (
<withRouter>
<Navbar color="dark">
<div className="container">
<NavbarBrand className="navbar-brand abs" href="/">
Cheat Sheet
</NavbarBrand>
</div>
</Navbar>
<Jumbotron>
<p className="lead">Quick Review ,Revision And Mnemonic Are Always Good</p>
<hr my-2/>
<p className="lead">Page is still under Construction</p>
<Button onClick={routeChange} className="About"color="primary">About Us</Button>
</Jumbotron>
<div className="img-thumbnail">
<Description/>
</div>
<div className="footer">
©Abhilekh Gautam all right reserved.
<p>Follow<a rel="noopener noreferrer"href="https://www.quora.com/profile/Abhilekh-Gautam-1" target="_blank">Abhilekh Gautam</a> On quora</p>
</div>
</withRouter>
)
}
export default App;
a couple issues here.
change function App (){} to const App = () => {} its going to help with your binding later because arrow functions are interpreted differently from declarative functions
this function needs some help
routeChange = () =>{
this.history.push('/About');
}
first of all you have to declare the function as a constant because App is a functional component not a class component.
second of all because App is a functional component you don't need the this keyword because routeChange is an arrow function and is bound to App
your final function should look like this:
const routeChange = () => {
history.push('/About');
}
make your button onClick handler an anonymous function so it is called on click only and not on render
<Button onClick={routeChange}/>
this code makes the route change function get called when the button renders. Instead change it to
<Button onClick={() => routeChange()}
make sure /About is a route to another component in your router or else you will get a 404 error or hit your no match component (if you have one)
your final product should look something like this
in app.js
import React from 'react';
import {Navbar,NavbarBrand, Jumbotron, Button} from 'reactstrap';
import './App.css';
import Description from './Description';
import './description.css';
import {useHistory,withRouter, BrowserRouter, Route, Switch} from "react-router-dom";
import About from './About';
function App() {
return (
<>
<Navbar color="dark">
<div className="container">
<NavbarBrand className="navbar-brand abs" href="/">
Cheat Sheet
</NavbarBrand>
</div>
</Navbar>
<BrowserRouter>
<Switch>
<Route exact path='/' component={Home}/>
<Route exact path='/About' component={About}
</Switch>
</BrowserRouter>
</>
)
}
then your home component would look like this:
import {useHistory} from 'react-router-dom'
const Home = () => {
const history = useHistory();
const routeChange = () => {
history.push('/About');
}
return (
<>
<Jumbotron>
<p className="lead">Quick Review ,Revision And Mnemonic Are Always Good</p>
<hr my-2/>
<p className="lead">Page is still under Construction</p>
<Button onClick={() => routeChange()} className="About"color="primary">About Us</Button>
</Jumbotron>
<div className="img-thumbnail">
<Description/>
</div>
<div className="footer">
©Abhilekh Gautam all right reserved.
<p>Follow<a rel="noopener noreferrer"href="https://www.quora.com/profile/Abhilekh-Gautam-1" target="_blank">Abhilekh Gautam</a> On quora</p>
</div>
</>
)
}
export default Home