My app has two buttons, one that displays information and another that hides it. This works as intended, however, some of the images loaded in from the API don't have any data available, and when the button to display the info is pressed, it returns a []
How would I target those that load with an empty array, and add in a string. For example "no information for this breed is available?"
App.js
import './App.css';
import './Dog.js';
import './index.css';
import "./Grid.js";
import NestedGrid from './Grid.js';
import Album from './Grid.js';
import AppLayout from './Grid.js';
function DogApp() {
return (
<div className="dogApp">
<AppLayout />
</div>
);
}
export default DogApp;
Grid.js
import * as React from 'react';
import AppBar from '#mui/material/AppBar';
import Button from '#mui/material/Button';
import Card from '#mui/material/Card';
import CardActions from '#mui/material/CardActions';
import CardContent from '#mui/material/CardContent';
import CardMedia from '#mui/material/CardMedia';
import CssBaseline from '#mui/material/CssBaseline';
import Grid from '#mui/material/Grid';
import Stack from '#mui/material/Stack';
import Box from '#mui/material/Box';
import Toolbar from '#mui/material/Toolbar';
import Typography from '#mui/material/Typography';
import Container from '#mui/material/Container';
import Link from '#mui/material/Link';
import { createTheme, ThemeProvider } from '#mui/material/styles';
import FetchAPI from './FetchAPI';
const cards = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const theme = createTheme();
export default function AppLayout() {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<AppBar position="relative">
</AppBar>
<main>
{/* Hero unit */}
<Box
sx={{
bgcolor: 'background.paper',
pt: 8,
pb: 6,
}}
>
<Container maxWidth="sm">
<Stack
sx={{ pt: 4 }}
direction="row"
spacing={2}
justifyContent="center"
>
</Stack>
</Container>
</Box>
<Container sx={{ py: 8 }} maxWidth="md">
{/* End hero unit */}
<Grid container spacing={4}>
{cards.map((card) => (
<Grid item key={card} xs={12} sm={6} md={4}>
<Card
sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}
>
<FetchAPI />
<CardContent sx={{ flexGrow: 1 }}>
</CardContent>
</Card>
</Grid>
))}
</Grid>
</Container>
</main>
</ThemeProvider>
);
}
FetchAPI.js
import React, { useState, useEffect } from 'react'
const FetchAPI = () => {
const [show, setShow] = useState({});
const [data, setData] = useState([]);
const apiGet = () => {
const API_KEY = "";
fetch(`https://api.thedogapi.com/v1/images/search?limit=2&page=10&order=Desc?API_KEY=${API_KEY}`)
.then((response) => response.json())
.then((json) => {
console.log(json);
setData([...data, ...json]);
});
};
useEffect(() => { //call data when pagee refreshes/initially loads
apiGet();
}, []);
return (
<div>
{data.map((item, id) => (
<div>
<img alt="dog photos" className="dogImg" src={item.url}></img>
{show[id] === false ? <p>{JSON.stringify(item.breeds, null, '\t')}</p> : null}
<button onClick={() => setShow((prev) => ({ ...prev, [id]: false }))}>Learn more about this dog!</button>
<button onClick={() => setShow((prev) => ({ ...prev, [id]: true }))}>Hide information</button>
</div>
))}
</div>
)
}
export default FetchAPI;
You can compare the length of array like:
if(arr.length===0){
return "no information for this breed is available"
}
The above condition is just for understanding you can change the logic as well either you want to return or save in some state.
Related
I have a component, GameGrid which makes an axios call to get the data that I pass as props to another component, GameCard. I currently have a Header component that is in the same file as GameGrid. The header has buttons which when clicked, changes the endpoint that the GameGrid is calling. I am trying to figure out a way to separate the two components, Header and GameGrid, but still have the buttons on the header determine which endpoint the GameGrid should call. Also, is the GameGrid the right place to have the data being called?
GameGrid.jsx
import React, { useEffect, useState } from "react";
import AppBar from '#mui/material/AppBar';
import Toolbar from '#mui/material/Toolbar';
import Box from '#mui/material/Box';
import IconButton from '#mui/material/IconButton';
import Grid from '#mui/material/Grid';
import axios from "axios";
import GameCard from "./GameCard";
export default function GameGrid() {
const [data, setData] = useState(); // Data Hook
const [route, setRoute] = useState('/')
useEffect(() => {
const getData = async () => {
try {
const { data } = await axios.get(`${process.env.REACT_APP_URL}${route}`);
console.log(data);
setData(data);
} catch (err) {
console.log(err);
}
};
getData();
}, [route]);
const createCards = (data) => {
return <GameCard
key = {data.id}
id = {data.id}
url = {data.game_url}
thumbnail = {data.thumbnail}
title = {data.title}
platform = {data.platform}
description = {data.short_description}
genre = {data.genre}
publisher = {data.publisher}
developer = {data.developer}
/>;
}
const Header = () => {
return (
<Box sx ={{ flexGrow: 1 }}>
<AppBar position="fixed">
<Toolbar>
<Grid sx={{ flexGrow: 1,
border: '1px solid white'}} container spacing={2}>
<Grid sx={{
}}>
<IconButton onClick={() => setRoute('/')}>
<img src='computer-mouse.png' alt='Logo' id='icon'/>
</IconButton>
<IconButton onClick={() => setRoute('/shooter')}>
<img src="shooter.png" alt="Shooter Games" />
</IconButton>
</Grid>
<Grid>
</Grid>
</Grid>
</Toolbar>
</AppBar>
</Box>
)
}
return (
<>
<Header/>
<div className="card-container">
{data?.map(createCards)}
</div>
</>
)
}
GameCard.jsx
import React from "react";
import Card from "#mui/material/Card";
import CardContent from "#mui/material/CardContent";
import CardMedia from "#mui/material/CardMedia";
import Typography from "#mui/material/Typography";
import { CardActionArea, Chip } from "#mui/material";
import Stack from '#mui/material/Stack';
export default function GameCard(props) {
return (
<Card sx={{ width: 550, margin: 2}} key={props.id} id={props.id} >
<CardActionArea href={props.url}>
<CardMedia
component="img"
height="fit-content"
image={props?.thumbnail}
/>
<CardContent>
<Stack direction="row" justifyContent="space-between">
<Typography gutterBottom variant="h6" component="div">
{props.title}
</Typography>
<Chip label={props.platform} size="small"/>
</Stack>
<Typography gutterBottom variant="body1" id="description-text"
color="text.secondary"
sx={{
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap'
}}>
{props.description}
</Typography>
<Stack direction="row" justifyContent="space-between">
<Chip label={props.genre} size="small" />
<Chip label={props.publisher} size="small" />
<Chip label={props.developer} size="small" />
</Stack>
</CardContent>
</CardActionArea>
</Card>
);
}
To separate Header, simply define it with all the props it requires. In this instance, that appears to be the setRoute function though you can call it whatever you like.
For example
// Header.jsx
const Header = ({ onRouteClick }) => (
<Box sx={{ flexGrow: 1 }}>
{/* ... */}
<IconButton onClick={() => onRouteClick("/")}>
<img src="computer-mouse.png" alt="Logo" id="icon" />
</IconButton>
<IconButton onClick={() => onRouteClick("/shooter")}>
<img src="shooter.png" alt="Shooter Games" />
</IconButton>
{/* ... */}
</Box>
);
export default Header;
and in your GameGrid component, import Header from "./Header" and use...
<Header onRouteClick={setRoute} />
If you were using Typescript (highly recommended), the API for Header would look something like this
interface HeaderProps {
onRouteClick: (route: string) => void;
}
const Header = ({ onRouteClick }: HeaderProps) => (
// ...
);
I have data returned from the backend now i am trying to map it to react component which is throwing an error Cannot read property of null what is correct way to print keys in the div or card , i have also attached the data
index.js
import React, { Component } from 'react';
import './app.css';
import ReactImage from './react.png';
import AppBar from '#mui/material/AppBar';
import Box from '#mui/material/Box';
import Toolbar from '#mui/material/Toolbar';
import Typography from '#mui/material/Typography';
import IconButton from '#mui/material/IconButton';
import MenuIcon from '#mui/icons-material/Menu';
import Paper from '#mui/material/Paper';
import Grid from '#mui/material/Grid';
import { styled } from '#mui/material/styles';
export default class JOB_DESC extends Component {
state = { data: null };
async componentDidMount() {
try {
const response = await fetch('/api/getUsername');
const data = await response.json();
this.setState({ data: data });
} catch (e) {
if (e.name != "AbortError") this.setState({ error: e.message });
}
}
render() {
const data = this.state.data;
console.log("DATA", data);
return (
<Box sx={{ flexGrow: 1 }}>
<AppBar position="static">
<Toolbar>
<IconButton
size="large"
edge="start"
color="inherit"
aria-label="menu"
sx={{ mr: 2 }} >
<MenuIcon />
</IconButton>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
Job.com
</Typography>
</Toolbar>
</AppBar>
<div style={{ padding: 20 }}>
<Grid container spacing={4}>
<Grid item xs={12}>
<p>`{data["User Account"]}`</p>
<p>`{data["User Location"]}`</p>
<p> `{data["Company Name"]}`</p>
</Grid>
</Grid>
</div>
</Box>
);
}
}
data
{ ‘User Account': Admin’,
‘User Location': ': New York, NY',
'Company Name': ': Millennium’ }
It looks like by the time your component gets rendered, data will still be null as you've set it to null. So attempting to index null will give you Cannot read property of null.
What you'd want to do is wrap your component with a condition to check if data exists by the time it renders.
{ data && (
<div style={{ padding: 20 }}>
<Grid container spacing={4}>
<Grid item xs={12}>
<p>`{data["User Account"]}`</p>
<p>`{data["User Location"]}`</p>
<p> `{data["Company Name"]}`</p>
</Grid>
</Grid>
</div>
)}
I have been trying to use material ui backdrop element in my code. I am referring to this example: https://mui.com/components/backdrop/#example
As soon as I introduce this code into my component, it fails with error message as below.
Unhandled Runtime Error Error: Invalid hook call. Hooks can only be
called inside of the body of a function component. This could happen
for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug
and fix this problem.
My code is as below.
import React, { useState } from 'react'
import { makeStyles } from '#material-ui/core/styles'
import { Box,Typography } from '#material-ui/core'
import CategoryCard from '../shop-categories/category-card'
import { useSelector } from 'react-redux';
//import NavigateBeforeIcon from '#material-ui/icons/NavigateBefore';
//import ProductsInCategory from '../../../pages/shops/product-in-category';
import {useRouter} from 'next/router'
import Backdrop from '#mui/material/Backdrop'
import CircularProgress from '#mui/material/CircularProgress';
import { withStyles } from '#material-ui/core/styles';
const useStyles = makeStyles({
heading : {
fontFamily: 'Roboto',
fontSize: 14,
fontStyle: 'normal',
fontWeight: 700,
lineHeight: 1,
color: props => props.pColor
}
})
export default function CategoryGrid(props) {
const { categories, shopId, title} = props;
const shopColors = useSelector(state => state.main.currentShop)
const classes = useStyles(shopColors);
const router = useRouter()
const [open, setOpen] = React.useState(false);
const handleClose = () => {
setOpen(false);
};
const gotoSubCategories = (shopId, id,cat_Name) => {
setOpen(true)
router.push(`/shops/shop-sub-categories?shop=${shopId}&category=${id}&categoryName=${cat_Name}`)
}
return (
<>
<div>
<Backdrop sx={{color: '#fff', zIndex: (theme) => theme.zIndex.drawer +1}}
open={open}
onClick={handleClose}
>
<CircularProgress color='inherit' />
</Backdrop>
</div>
<Typography variant="body2" style={{ fontWeight: 'bold' }}
className={classes.heading}>
{title}
</Typography>
<Box display="flex" mb={2} mx={1} flexDirection="column">
<Box display="flex" fontWeight='fontWeightBold' width={1/2} mb={1}>
</Box>
<Box display="flex" flexWrap="wrap">
{
categories.map((category, index) => {
return <CategoryCard key={index}
name={category.name}
onClick={() => gotoSubCategories(shopId, category.id,category.name)}
imageURL={category.url}
shopId={shopId} callParent={props.callParent}
/>
})
}
</Box>
</Box>
</>
)
}
If I remove out this portion, then it works fine.
<div>
<Backdrop sx={{color: '#fff', zIndex: (theme) => theme.zIndex.drawer +1}}
open={open}
onClick={handleClose}
>
<CircularProgress color='inherit' />
</Backdrop>
</div>
How to fix this?
I have a page ("/paymentsucess"). In this page, I am using the react-countdown-circle-timer component (https://github.com/vydimitrov/react-countdown-circle-timer#props-for-both-reactreact-native). Upon reaching this page ("/paymentsuccess"), countdown beings. After countdown reaches zero, I want to redirect user back to home page ("/").
To achieve this, for the CountdownCircleTimer component, when onComplete, I set the state initialMount to false.
<CountdownCircleTimer
onComplete={() => {
setInitialMount(false);
return [true, 1500];
}}
isPlaying
duration={2}
colors={[
["#004777", 0.33],
["#F7B801", 0.33],
["#A30000", 0.33],
]}
>
{renderTime}
</CountdownCircleTimer>
Given that initialMount is changed, my useEffect (in the paymentsuccess component) will kick in and redirect users to "/". I am using history.push("/") here.
useEffect(() => {
if (initialMount !== true) {
console.log("On change in status,");
console.log(initialMount);
history.push("/");
}
}, [initialMount, history]);
I was able to redirect user back to "/" successfully. But upon reaching "/", they got redirected back to /paymentsuccess again. And then from /paymentsuccess it goes back to / and then the loops continue. Infinite loop.
Any idea what I am doing wrong here? :( I need to stop this loop and lands user back to / and stops there.
I am using Router from react-router-dom and createBrowserHistory from history.
Below is the full code for my paymentsuccess component
import React, { useEffect, useStatem} from "react";
import { useHistory } from "react-router-dom";
import { makeStyles } from "#material-ui/core/styles";
import Grid from "#material-ui/core/Grid";
import Paper from "#material-ui/core/Paper";
import { CountdownCircleTimer } from "react-countdown-circle-timer";
function PaymentSuccess() {
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
}));
const classes = useStyles();
const history = useHistory();
const [initialMount, setInitialMount] = useState(true);
useEffect(() => {
console.log("On component mount, status is");
console.log(initialMount);
}, []);
useEffect(() => {
return () => {
console.log("On component unmount, status is");
setInitialMount(true);
console.log(initialMount);
};
}, []);
useEffect(() => {
if (initialMount !== true) {
console.log("On change in status,");
console.log(initialMount);
history.push("/");
}
}, [initialMount, history]);
const renderTime = ({ remainingTime }) => {
if (remainingTime === 0) {
return <div className="timer">Starting...</div>;
}
return (
<div className="timer">
<div className="value">{remainingTime}</div>
</div>
);
};
return (
<div className={classes.root}>
<Grid container spacing={0}>
<Grid item xs={6} sm={6}>
<Paper
className="paymentSuccessLeftPaper"
variant="outlined"
square
></Paper>
</Grid>
<Grid item xs={6} sm={6}>
<Paper className="paymentSuccessRightPaper" variant="outlined" square>
<h1>Payment Success</h1>
<CountdownCircleTimer
onComplete={() => {
setInitialMount(false);
return [true, 1500];
}}
isPlaying
duration={2}
colors={[
["#004777", 0.33],
["#F7B801", 0.33],
["#A30000", 0.33],
]}
>
{renderTime}
</CountdownCircleTimer>
</Paper>
</Grid>
</Grid>
</div>
);
}
export default PaymentSuccess;
I have checked my "/" page and I don't think there is any logic there redirecting to "/paymentsuccess". The page ("/") code is as below.
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import Grid from "#material-ui/core/Grid";
import Paper from "#material-ui/core/Paper";
import Button from "#material-ui/core/Button";
import Link from "#material-ui/core/Link";
function LandingPage() {
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
paper: {
minHeight: "100%",
padding: theme.spacing(0),
textAlign: "center",
color: theme.palette.text.secondary,
},
}));
const classes = useStyles();
return (
<div className={classes.root}>
<Grid container spacing={0}>
<Grid item xs={4} sm={4}>
<Paper className={classes.paper} variant="outlined" square>
<h1>Photo Strips</h1>
<Button variant="contained" color="primary">
<Link href="/photo10">SELECT</Link>
</Button>
</Paper>
</Grid>
<Grid item xs={4} sm={4}>
<Paper className={classes.paper} variant="outlined" square>
<h1>Photo Strips and GIF</h1>
<Button variant="contained" color="primary">
<Link href="/photogif12">
SELECT
</Link>
</Button>
</Paper>
</Grid>
<Grid item xs={4} sm={4}>
<Paper className={classes.paper} variant="outlined" square>
<h1>Photo Strips and Boomerang</h1>
<Button variant="contained" color="primary">
<Link href="/photoboomerang12">
SELECT
</Link>
</Button>
</Paper>
</Grid>
</Grid>
</div>
);
}
export default LandingPage;
Thank you all in advance! Appreciate all the help and advise
UPDATE
Below is Router code
import React from "react";
import { Router, Switch } from "react-router-dom";
import LandingPage from "./components/LandingPage";
import Photo10 from "./components/Photo10";
import PhotoGIF12 from "./components/PhotoGIF12";
import PhotoBoomerang12 from "./components/PhotoBoomerang12";
import PaymentSuccess from "./components/PaymentSuccess";
import DynamicLayout from "./router/DynamicLayout";
import { history } from "./helpers/history";
const App = () => {
return (
<Router history={history}>
<div className="App">
<Switch>
<DynamicLayout
exact
path="/"
component={LandingPage}
layout="LANDING_NAV"
/>
<DynamicLayout
exact
path="/photo10"
component={Photo10}
layout="PHOTO10_PAGE"
/>
<DynamicLayout
exact
path="/photogif12"
component={PhotoGIF12}
layout="PHOTOGIF12_PAGE"
/>
<DynamicLayout
exact
path="/photoboomerang12"
component={PhotoBoomerang12}
layout="PHOTOBOOMERANG12_PAGE"
/>
<DynamicLayout
exact
path="/paymentsuccess"
component={PaymentSuccess}
layout="PAYMENTSUCCESS_PAGE"
/>
</Switch>
</div>
</Router>
);
};
export default App;
Below is the code for the DynamicLayout component
import React from "react";
const DynamicLayout = (props) => {
const { component: RoutedComponent, layout } = props;
const actualRouteComponent = <RoutedComponent {...props} />;
switch (layout) {
case "LANDING_NAV": {
return <>{actualRouteComponent}</>;
}
case "PHOTO10_PAGE": {
return <>{actualRouteComponent}</>;
}
case "PHOTOGIF12_PAGE": {
return <>{actualRouteComponent}</>;
}
case "PHOTOBOOMERANG12_PAGE": {
return <>{actualRouteComponent}</>;
}
case "PAYMENTSUCCESS_PAGE": {
return <>{actualRouteComponent}</>;
}
default: {
return (
<>
<h2>Default Nav</h2>
{actualRouteComponent}
</>
);
}
}
};
export default DynamicLayout;
This problem occurs when you changes value of variable in useEffect and that variable also added in useEffect dependency array, So when ever your useEffect change the value due to presence of that variable in dependency array, it again called the useEffect thus a infinite loop occurs
so just remove 'history' variable from your dependency array
I want to access component according to the id of the product.
Here is my Code.
List.jsx
import { Switch, Route, Redirect, withRouter } from "react-router-dom";
import CategoryList from "./CategoryList.jsx";
import ProductList from "./ProductList.jsx";
import React from "react";
import axios from "axios";
const CategoryWithId = ({ match }) => {
console.log("Category", match.params.listId);
<ProductList listId={match.params.listId}></ProductList>;
};
function List() {
const [state, setState] = React.useState([]);
const getList = () => {
axios.get("https://api.growcify.com/dev/category/list").then((res) => {
console.log(res.data);
setState(res.data);
});
};
React.useEffect(() => {
getList();
}, []);
return (
<div className="App">
<Switch>
<Route path="/" component={() => <CategoryList state={state} />} />
<Route path="/product/:listId" component={CategoryWithId} />
<Redirect to="/" />
</Switch>
</div>
);
}
export default withRouter(List);
CategoryList.jsx
import React from "react";
import { Link } from "react-router-dom";
import Grid from "#material-ui/core/Grid";
import Paper from "#material-ui/core/Paper";
import { makeStyles } from "#material-ui/core/styles";
import { Typography } from "#material-ui/core";
const ShowCategory = (props) => {
const classes = useStyles();
const { list } = props;
return (
<Link to={`/product/${list._id}`}>
<Paper className={classes.paper} key={list._id}>
<p style={{ position: "relative", top: "40%" }}>{list.name}</p>
</Paper>
</Link>
);
};
function CategoryList(props) {
const { state } = props;
const classes = useStyles();
return (
<>
<div className={classes.header}>
<Typography variant="h3" style={{ padding: "10px" }}>
List of Categories
</Typography>
</div>
<div className={classes.container}>
<Grid container className={classes.root} spacing={2}>
<Grid item xs={12}>
<Grid container spacing={2}>
{state.map((list, index) => (
<ShowCategory list={list}></ShowCategory>
))}
</Grid>
</Grid>
</Grid>
</div>
</>
);
}
export default CategoryList;
I want that on clicking on Particular category, i should be redirect to /produce/listId.
In the url section, I can see the url with listId but I'm not getting redirected to the desired page related to that url.
Use exact keyword!
<Route exact path="/" component={() => <CategoryList state={state} />} />