How to map data to react component? - javascript

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>
)}

Related

Can a component change the data of another component?

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) => (
// ...
);

How to pass data from input field to parent component using context API in React?

I have an input field in child component. If you type in valid wallet address it should display wallet balance in parent component.
I don't know how to pass that e.target.value we are reading from input field.
I tried to do this function in parent component - Dashboard.
const [walletBalance, setWalletBalance] = useState('');
const getWalletBalance = async () => {
try {
const web3 = new Web3(Web3.givenProvider || 'ws://localhost:3000');
for (const wallet of dummyWallets) {
const balance = await web3.eth.getBalance(wallet.address);
console.log(balance);
const balanceConvertedFromWeiToEth = await Web3.utils.fromWei(balance, 'ether');
const shortenedBalance =
balanceConvertedFromWeiToEth.length >= 10
? balanceConvertedFromWeiToEth.slice(0, 10)
: balanceConvertedFromWeiToEth;
setWalletBalance(shortenedBalance);
}
} catch (error) {
console.error(error);
throw new Error('No wallet found');
}
};
render (
<p>Wallet balance: {walletBalance}</p>
and then pass this function down to child component with input field (need to prop drill 3 times for that) and in child component say:
const [inputText, setInputText] = useState('');
const handleInputChange = (e) => {
setInputText(e.target.value);
if (inputText === wallet.address) {
getWalletBalance()
}
};
render (
<input value={inputText} onChange={handleInputChange} />
)
It didn't work. No errors. Just nothing happens.
Then I thought that I need to do the other way around: Instead of trying to pass getWalletBalance() down as props, I need to read data from input field and pass that data to parent component, because parent needs to have access to the input field and read from it to be able to display wallet balance in parent.
I've also tried to create context:
import React, { useState, createContext } from 'react';
import { dummyWallets } from '../mocks/dummyWallets';
import Web3 from 'web3';
export const WalletContext = createContext();
// eslint-disable-next-line react/prop-types
export const TransactionProvider = ({ children }) => {
const [inputText, setInputText] = useState('');
const [walletBalance, setWalletBalance] = useState('');
const handleInputChange = async (e) => {
setInputText(e.target.value);
const web3 = new Web3(Web3.givenProvider || 'ws://localhost:3000');
for (const wallet of dummyWallets) {
if (inputText === wallet.address) {
const balance = await web3.eth.getBalance(wallet.address);
console.log(balance);
const balanceConvertedFromWeiToEth = await Web3.utils.fromWei(balance, 'ether');
const shortenedBalance =
balanceConvertedFromWeiToEth.length >= 10
? balanceConvertedFromWeiToEth.slice(0, 10)
: balanceConvertedFromWeiToEth;
setWalletBalance(shortenedBalance);
}
}
};
return (
<WalletContext.Provider
value={{ walletBalance, handleInputChange, inputText, setInputText,
setWalletBalance }}>
{children}
</WalletContext.Provider>
);
};
Then pass handleInputChange to onChange in child component and inputText as value, then use walletBalance in parent component.
All context things walletBalance, handleInputChange, inputText, setInputText and setWalletBalance threw undefined error.
Here is child component consuming data from context:
import React, { FC, useState, useContext } from 'react';
import { Formik, Form, Field } from 'formik';
import { Button, InputAdornment, Box, Grid } from '#material-ui/core';
import TextField from '#eyblockchain/ey-ui/core/TextField';
import { Search } from '#material-ui/icons';
import SvgEthereum from '../../../images/icon/Ethereum';
import { makeStyles } from '#material-ui/styles';
import { Theme } from '#material-ui/core';
import { dummyWallets } from '../../../shared/mocks/dummyWallets';
import Web3 from 'web3';
import { WalletContext } from '../../../shared/context/WalletContext';
const etherIconBorderColor = '#eaeaf2';
const useStyles = makeStyles((theme: Theme) => ({
etherIconGridStyle: {
padding: '16px 0 0 0'
},
etherIconStyle: {
fontSize: '220%',
textAlign: 'center',
color: theme.palette.primary.light,
borderTop: `solid 1px ${etherIconBorderColor}`,
borderBottom: `solid 1px ${etherIconBorderColor}`,
borderLeft: `solid 1px ${etherIconBorderColor}`,
padding: '5px 0 0 0',
[theme.breakpoints.down('sm')]: {
fontSize: '150%',
lineHeight: '43px'
}
},
buttonStyle: {
paddingTop: theme.spacing(2)
},
searchIconStyle: {
color: theme.palette.primary.light
}
}));
const onSubmitHandler = () => {
return;
};
const SearchInputForm: FC<any> = (props) => {
const { walletBalance, handleInputChange, inputText } = useContext(WalletContext);
const classes = useStyles();
return (
<Formik
onSubmit={onSubmitHandler}
initialValues={{ name: '' }}
render={({ values }) => {
return (
<Form>
<Grid container>
<Grid item xs={1} className={classes.etherIconGridStyle}>
<Box className={classes.etherIconStyle}>
<SvgEthereum />
</Box>
</Grid>
<Grid item xs={11}>
<Field
required
name="name"
placeholder={props.placeholdervalue}
value={inputText}
component={TextField}
onChange={handleInputChange}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<Search fontSize="large" className={classes.searchIconStyle} />
</InputAdornment>
)
}}
/>
</Grid>
<Grid item xs={12}>
<Box
display="flex"
justifyContent="flex-end"
alignItems="flex-end"
className={classes.buttonStyle}>
<Button type="submit" variant="contained" color="secondary" size="large" disabled>
Create Alert
</Button>
</Box>
</Grid>
</Grid>
</Form>
);
}}
/>
);
};
export default SearchInputForm;
Here is parent component where I pull walletBalance from context:
const Dashboard: FC<any> = () => {
const { walletBalance } = useContext(WalletContext);
const classes = useStyles();
return (
<>
<Container maxWidth={false} className={classes.dashboardContainer}>
<Grid>
<Box data-testid="alert-page-heading">
<Typography variant="h3">Alerts</Typography>
</Box>
<Box data-testid="alert-page-subheading">
<Typography className={classes.subHeadingStyle}>
Monitor the Web 3.0 for interesting blockchain events
</Typography>
</Box>
</Grid>
<Grid container spacing={4}>
<Grid item xs={12} sm={8}>
<Box className={classes.dashboardCard}>
<Box className={classes.searchBox}>
<SearchTabs />
</Box>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between'
}}>
<Box
sx={{
height: '60%',
padding: '1%',
fontSize: 20
}}>
<a href="http://localhost:3000/" className={classes.backToAlertsLink}>
Back to my alerts
</a>
<p className={classes.boldText}>Recent transactions</p>
</Box>
<Box sx={{ height: '60%', padding: '4%', fontSize: 20 }}>
<span>Wallet Balance</span>
<span className={classes.balanceNumber}>{walletBalance} ETH</span>
</Box>
</Box>
</Box>
</Grid>
<Grid item xs={12} sm={4}>
<Box className={classes.dashboardCard}>
<Box sx={{ height: '60%', padding: '2%', fontSize: 20, fontWeight: 'bold' }}>
Top 10 alerts
</Box>
</Box>
</Grid>
</Grid>
</Container>
</>
);
};
export default Dashboard;
The problem is that I pull handleChange from context and apply to input field in child component to read the value from the input and then pull walletBalance from context in parent but that walletBalance is not updated (doesn't contain input value in it).
Can someone please share knowledge on how to read data from child component input field and display something from that in parent component?
Any advice is appreciated. Thank you.

How to display a message instead of an empty array

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.

Encountered comments is undefined error in react

I'm trying to learn react I keep encountering this error while rendering the post dialog box in the website.
comments.js file:
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import withStyles from '#material-ui/core/styles/withStyles';
import { Link } from 'react-router-dom';
import dayjs from 'dayjs';
// MUI
import Grid from '#material-ui/core/Grid';
import Typography from '#material-ui/core/Typography';
const styles = (theme) => ({
...theme,
commentImage: {
maxWidth: '100%',
height: 100,
objectFit: 'cover',
borderRadius: '50%'
},
commentData: {
marginLeft: 20
}
});
class Comments extends Component {
render() {
const { comments, classes } = this.props;
return (
//Here is the error
<Grid container>
{comments.map((comment, index) => {
const { body, createdAt, userImage, userHandle } = comment;
return (
<Fragment key={createdAt}>
<Grid item sm={12}>
<Grid container>
<Grid item sm={2}>
<img
src={userImage}
alt="comment"
className={classes.commentImage}
/>
</Grid>
<Grid item sm={9}>
<div className={classes.commentData}>
<Typography
variant="h5"
component={Link}
to={`/users/${userHandle}'}
color="primary"
>
{userHandle}
</Typography>
<Typography variant="body2" color="textSecondary">
{dayjs(createdAt).format('h:mm a, MMMM DD YYYY')}
</Typography>
<hr className={classes.invisibleSeparator} />
<Typography variabnt="body1">{body}</Typography>
</div>
</Grid>
</Grid>
</Grid>
{index !== comments.length - 1 && (
<hr className={classes.visibleSeparator} />
)}
</Fragment>
);
})}
</Grid>
);
}
}
Comments.propTypes = {
comments: PropTypes.array.isRequired
};
export default withStyles(styles)(Comments);

Passing Parameter from Child to Parent trough Input React

I try to pass some information from an input field in the child to the parent.
What i have so far is this:
Parent
import React from "react";
import PropTypes from "prop-types";
import Grid from "#material-ui/core/Grid";
import Paper from "#material-ui/core/Paper";
import Typography from "#material-ui/core/Typography";
import { withStyles } from "#material-ui/core/styles";
import TimelineWidget from "./timeline-widget/timeline-widget.component";
import ContainerTable from "./container-table/container-table.component";
import HistoryTable from "./history-table/history-table.component";
import ShippingOverview from "./shipping-overview/shipping-overview.component";
import MapWidget from "./map-widget/map-widget.component";
import styles from "./shippingInformation.style";
class shippingInformation extends React.Component {
constructor() {
super();
this.inputChange = this.inputChange.bind(this);
}
state = {
searchString: null
};
inputChange(input){
this.setState({ searchString: input });
};
render() {
const { classes } = this.props;
return (
<div className={classes.DashboardPageWrapper}>
<Grid item xs={12}>
<Grid container justify="center" spacing={16}>
<Grid
key={1}
item
xs={12}
sm={12}
md={9}
className={classes.Widget}
>
<Typography
variant="subheading"
className={classes.WidgetHeading}
>
Timeline of Container #
</Typography>
<Paper className={classes.WidgetContent}>
<TimelineWidget />
</Paper>
</Grid>
<Grid
key={2}
item
xs={12}
sm={12}
md={3}
className={classes.Widget}
>
<Typography
variant="subheading"
className={classes.WidgetHeading}
>
Shipping Overview
</Typography>
<Paper className={classes.WidgetContent}>
<ShippingOverview />
</Paper>
</Grid>
</Grid>
<Grid container justify="center" spacing={16}>
<Grid item xs={12} sm={12} md={9}>
<Grid container justify="center" spacing={16}>
<Grid key={3} item xs={12} className={classes.Widget}>
<Typography
variant="subheading"
className={classes.WidgetHeading}
>
Containers
</Typography>
<Paper className={classes.WidgetContent}>
<ContainerTable />
</Paper>
</Grid>
<Grid key={4} item xs={12} className={classes.Widget}>
<Typography
variant="subheading"
className={classes.WidgetHeading}
>
Status History
</Typography>
<Paper className={classes.WidgetContent}>
<HistoryTable />
</Paper>
</Grid>
</Grid>
</Grid>
<Grid
key={5}
item
xs={12}
sm={12}
md={3}
className={classes.Widget}
>
<Typography
variant="subheading"
className={classes.WidgetHeading}
>
Latest Position
</Typography>
<Paper className={classes.WidgetContent}>
<MapWidget onShippingOverview={this.inputChange.bind(this)} />
</Paper>
</Grid>
</Grid>
</Grid>
</div>
);
}
}
shippingInformation.propTypes = {
classes: PropTypes.shape({}).isRequired
};
export default withStyles(styles, { withTheme: true })(shippingInformation);
Child
import React from "react";
import PropTypes from "prop-types";
import { withStyles } from "#material-ui/core/styles";
import Typography from "#material-ui/core/Typography";
import TextField from "#material-ui/core/TextField";
import { Bar } from "react-chartjs-2";
import CountUp from "react-countup";
import classNames from "classnames";
import themeStyles from "./shipping-overview.theme.style";
const styles = theme => ({
container: {
display: "flex",
flexWrap: "wrap"
},
textField: {
marginLeft: theme.spacing.unit,
marginRight: theme.spacing.unit,
width: 200
},
menu: {
width: 200
}
});
export class ShippingOverview extends React.Component {
state = {
searchString: null
};
handleChange(event){
this.setState ({ searchString: event.target.value}, () => {
this.props.onShippingOverview(this.state.searchString);
})
// this.props.onShippingOverview(input);
};
render() {
const { classes } = this.props;
return (
<div className={classes["shipping-overview-widget"]}>
<div>
<form className={classes.container} noValidate autoComplete="off">
<TextField
ref="result"
id="full-width"
label="Tracking ID"
InputLabelProps={{
shrink: true
}}
placeholder="Placeholder"
fullWidth
margin="normal"
onChange={this.handleChange.bind(this)}
value={this.state.input}
/>
</form>
</div>
</div>
);
}
}
ShippingOverview.propTypes = {
theme: PropTypes.shape({
palette: PropTypes.shape({
primary: PropTypes.shape({
dark: PropTypes.string,
main: PropTypes.string,
light: PropTypes.string,
contrastText: PropTypes.string
}),
secondary: PropTypes.shape({
main: PropTypes.string
}),
common: PropTypes.shape({
white: PropTypes.string
})
})
}).isRequired,
classes: PropTypes.shape({}).isRequired
};
export default withStyles(themeStyles, { withTheme: true })(ShippingOverview);
When i now check in the child file only to check the state of searchString it (with console.log()) it seems to work. But as soon as i let it run trough the handleChange function in the child it gives me this error:
> > TypeError: _this2.props.onChild is not a function
33 | handleChange(event){
34 | this.setState ({ searchString: event.target.value}, () => {
> 35 | this.props.onChild(this.state.searchString);
hope someone can help. btw im a noob...
You are using the wrong component in your parent component. Your child component is imported as ShippingOverview but you are using MapWidget. Change to ShippingOverview and it will work.
<ShippingOverview onShippingOverview={this.inputChange} />

Categories

Resources