I am not very familiar with using props in function components and I want to basically connect two functions in one functional component, and use the data from it.
Here is my code. I want to fetch data from a database and display it in cards using the map method.
import {React, useEffect, useState} from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Card from '#material-ui/core/Card';
import CardActions from '#material-ui/core/CardActions';
import CardContent from '#material-ui/core/CardContent';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import clueData from './ClueData';
import client from '../api/client';
import { Chip, Container, Grid } from '#material-ui/core';
const RenderClues=(index)=> {
const classes= useStyles();
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await client.get('api/admin/mission')
setData(result.data.Missions);
}
fetchData();
}, []);
return (
<Card key={index} data={data} style={{ marginBottom: 10, padding: 10 }}>
<CardContent>
<div className={classes.container}>
<Typography className={classes.title} color="textSecondary" variant="ul" >
Clue : {data.cluename}
</Typography>
<Chip size="small" label={clue.isSolved? "solved" : "unsolved" } />
</div>
</CardContent>
<CardActions style={{ display: 'flex', justifyContent: 'space-between'}}>
<Button variant="contained" size="small" href="/photo-clue">View</Button>
<Typography color="textSecondary">
{data.points}
</Typography>
</CardActions>
</Card>
);
}
function Clues(props) {
return (
<Container maxWidth="md">
<Grid item xs={12} >
{props.data.map(RenderClues)}
</Grid>
</Container>
);
}
export default Clues;
const useStyles = makeStyles({
title: {
fontSize: 20,
color:"olive",
fontWeight: 'bold',
textAlign: 'left',
},
pos: {
marginBottom: 12,
},
points: {
float:"right",
},
container: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 10,
}
});
But there is something wrong in the way I am using props here because I m getting this error:
"Cannot read property 'map' of undefined."
Any help is appreciated. Thanks!
Issue is props.data being undefined at the point of time its rendered.. If asynchrounsly updated, then try this.
{props?.data?.map(RenderClues)}
Map function takes key, and items.
For Example:
props.data.map((key, items) => RenderClues)
First of all, you should run fetchData() inside Clues function. After fetching data, pass your data as props to RenderClues like below:
<Grid item xs={12} >
data.map((dataItem) => {
<RenderClues key={something uniq} data={dataItem} />
})
</Grid>
By the way, you have to change RenderClues to RenderClues = (props) => {your code} and use props inside RenderClues. And in the code above, i'm assuming you fetch data like that. You need to change that too
Related
I have data returned from the backend as an array that i want to populate on react component.
home.js
import Head from "next/head";
import Header from "../src/components/Header";
import * as React from 'react';
import { styled } from '#mui/material/styles';
import Box from '#mui/material/Box';
import Paper from '#mui/material/Paper';
import Grid from '#mui/material/Grid';
import TextField from '#mui/material/TextField';
import SendIcon from '#mui/icons-material/Send';
import Stack from '#mui/material/Stack';
import Button from '#mui/material/Button';
import getDupImages from "../src/services/getDupImages";
import { useState, useEffect } from 'react'
const Item = styled(Paper)(({ theme }) => ({
backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
...theme.typography.body2,
padding: theme.spacing(1),
textAlign: 'center',
color: theme.palette.text.secondary,
}));
export default function Home({data}) {
let _data;
const fetchData = async () => {
_data = await getDupImages();
console.log("DATA>>>", _data);
return _data;
};
const submit = (event) => {
event.preventDefault();
fetchData();
}
return (
<>
<Head>
<title>De-Dup</title>
<link rel="icon" type="image/ico" href="/img/goals.ico" />
</Head>
<Header />
<section>
<Box sx={{ flexGrow: 1 }}>
<Grid container spacing={2}>
<Grid item xs={5}>
<Box
component="form"
sx={{
'& > :not(style)': { m: 1, width: '75ch' },
}}
noValidate
autoComplete="off"
>
<TextField id="outlined-basic" label="location path" variant="outlined" />
<Stack direction="row" spacing={2}>
<Button variant="contained" onClick={submit} endIcon={<SendIcon />}>
Submit
</Button>
</Stack>
</Box>
</Grid>
<Grid item xs={7}>
{data.map((d) => {
return (
<div>
{d.title}
</div>
);
})}
</Grid>
</Grid>
</Box>
</section>
</>
);
}
Error
1 of 1 unhandled error
Server Error
TypeError: Cannot read property 'map' of undefined
This error happened while generating the page. Any console logs will be displayed in the terminal window.
Put the data in the component state and check if there actually is data before displaying it.
const [data, setData] = useState();
const fetchData = async () => {
setData(await getDupImages());
}
Then in your JSX:
{!!data && data.map(d => <div>{d.title}</div>}
You are trying to render the data before it is available. Use this instead
{data && data.map((d) => {
return (
<div>
{d.title}
</div>
);
})}
Either initialise the data state as an array or use the Optional chaining (?.) operator before the map function:
data?.map((d) => {
return <div>{d.title}</div>;
})
Hope this helps.
I want to add the macchines in machine array so I defined a specific component with add function in it. So when I add the "process" in "processes" array then it is reflecting on the console using useEffect. But when I add a machine it is reflected in MachineGround Component But not in App component. Overall I am planning to add a dashboard where if even a mcahine is added in machines array it should reflect in the processes in App Component and the dashboard should be updated.
I will appreciate your help.
App component
import React, { useEffect, useState } from 'react';
import { Container, Typography, Box, Button } from '#mui/material'
import MachineGround from './Components/MachineGround'
import { Process } from './types'
const App = () => {
const [processes, setProcesses] = useState<Process[]>([{
Name: 'Process-1', machines: [
{
Name: 'Machine-1', devices: [{
Name: 'device-1',
type: 'Timer'
}]
}]
}]) // dummy process
// const [processes, setProcesses] = useState<Process[]>([])
const [count, setCount] = useState<number>(1) // dummy process count.
// Add Process
const addProcess = () => {
if (processes.length < 10) {
setCount(count + 1)
const processNow: Process = {
Name: `Process-${count}`,
machines: []
}
setProcesses((process) => {
return (
[...process, processNow]
)
})
} else {
alert("Limit can't exceeds 10")
}
}
// Delete Process
const deleteProcess = (passProcess: Process) => {
setProcesses(processes.filter((process) => process.Name !== passProcess.Name))
}
useEffect(() => {
console.log(processes)
}, [processes])
return (
<>
<Container maxWidth='lg'>
<Typography variant='h3' mt={5} sx={{ fontWeight: 700 }} align={'center'} gutterBottom>
My DashBoard
</Typography>
<Box sx={{ bgcolor: '#F4F4F7', paddingInline: 5, borderRadius: 10 }} >
{/* here will be the renders of processes */}
{
processes.map((process) => (
<Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }} pb={2} pt={2}>
<Typography variant='h6' >
{process.Name}
</Typography>
<Button variant='contained' onClick={() => {
deleteProcess(process)
}}>
Delete
</Button>
</Box>
<MachineGround process={process} />
</Box>
))
}
</Box>
<Button variant='contained' color='primary' sx={{ marginBlock: 5, marginLeft: 10 }} onClick={addProcess}> Add Process</Button>
</Container>
</>
);
}
export default App;
import React, { useEffect, useState } from 'react'
import DeviceGround from './DeviceGround'
import { Box, Typography, Button } from '#mui/material'
//types
import { Machine, Process } from '../types'
type Props = {
process: Process
}
const MachineGround = ({ process }: Props) => {
const [count, setCount] = useState<number>(1)
const [machines, setMachines] = useState<Machine[]>(process.machines)
const handleAddMachine = () => {
if (machines.length < 10) {
const newMachine: Machine = { Name: `Machine-${count}`, devices: [] }
setMachines((machines) => [...machines, newMachine])
setCount(count + 1)
} else {
alert("You can't add more than 10 Machines.")
}
}
const handleDeleteMachine = (machine: Machine) => {
setMachines(machines.filter((current) => current.Name !== machine.Name))
}
useEffect(() => {
console.log('machines Array Changed')
}, [machines])
return (
<Box sx={{ bgcolor: '#00e676', borderRadius: 5 }} mt={2} ml={3} mr={3} pt={1} pb={1} mb={2}>
{machines.map((machine: Machine) => {
return (
<>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }} mt={2}>
<Typography paragraph ml={5} sx={{ fontWeight: 700 }}>
{machine.Name}
</Typography>
<Button variant='outlined' size='small' sx={{ marginRight: 5 }} onClick={() => {
handleDeleteMachine(machine)
}}>Delete Machine</Button>
</Box>
<Box>
{/* {
machine.devices.length !== 0 ?
<DeviceGround machine={machine}></DeviceGround>
: null we dont need conditional render
} */}
<DeviceGround machine ={machine} />
</Box>
</>
)
})}
<Button variant='contained' size='small' sx={{ marginLeft: 5 }} onClick={handleAddMachine}>Add Machine</Button>
</Box >
)
}
export default MachineGround
I am thinking that should I use Redux ? or another state management then what should I do? I messed up the states.
State management tools like Redux, Context-API can be a good option here but even if you do not want to use them, you can make use of normal JavaScript functions. Just pass them as props from your parent component to child component.
I will explain what I mean here. Write a function in your parent component which take a machine object and update the machines array. Now pass this component as props to your child component. Now inside your child component call this function with the machine object that you want to add to your machines array. And boom, your machines array in parent will be updated.
I'm in the process of building out a simple react act that display REST data from my localhost URL.
I keep getting this error and I'm not sure why, at first I thought it was the data within the API itself but I think that's not the case for this?
I am not getting any npm start errors, the error appears when I inspect a page with browser tools.
Here is the full error:
index.js:1 Warning: Encountered two children with the same key, `1`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.
at div
at Grid (http://localhost:4000/static/js/0.chunk.js:1556:35)
at WithStyles(ForwardRef(Grid)) (http://localhost:4000/static/js/0.chunk.js:6385:31)
at main
at Container (http://localhost:4000/static/js/0.chunk.js:1101:23)
at WithStyles(ForwardRef(Container)) (http://localhost:4000/static/js/0.chunk.js:6385:31)
at UserBuckets (http://localhost:4000/static/js/main.chunk.js:363:5)
at LoadingComponent (http://localhost:4000/static/js/main.chunk.js:999:5)
at div
at App (http://localhost:4000/static/js/main.chunk.js:173:89)
at Route (http://localhost:4000/static/js/0.chunk.js:48473:29)
at Switch (http://localhost:4000/static/js/0.chunk.js:48675:29)
at Router (http://localhost:4000/static/js/0.chunk.js:48108:30)
at BrowserRouter (http://localhost:4000/static/js/0.chunk.js:47728:35)
Could someone point out what is causing this error in my code? I haven't been able to solve it myself.
Here is my required code:
App.js
import React, { useEffect, useState } from 'react';
import './App.css';
import UserBuckets from './components/BucketLists';
import LoadingComponent from './components/Loading';
function App() {
const ListLoading = LoadingComponent(UserBuckets);
const [appState, setAppState] = useState({
loading: false,
buckets: null,
});
useEffect(() => {
setAppState({ loading: true });
const apiUrl = `http://127.0.0.1:8000/api/`;
fetch(apiUrl)
.then((data) => data.json())
.then((buckets) => {
setAppState({ loading: false, buckets: buckets });
});
}, [setAppState]);
return (
<div className="App">
<h1>Latest Buckets</h1>
<ListLoading isLoading={appState.loading} buckets={appState.buckets} />
</div>
);
}
export default App;
bucketList.js
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Card from '#material-ui/core/Card';
import CardContent from '#material-ui/core/CardContent';
import CardMedia from '#material-ui/core/CardMedia';
import Grid from '#material-ui/core/Grid';
import Typography from '#material-ui/core/Typography';
import Container from '#material-ui/core/Container';
const useStyles = makeStyles((theme) => ({
cardMedia: {
paddingTop: '56.25%', // 16:9
},
link: {
margin: theme.spacing(1, 1.5),
},
cardHeader: {
backgroundColor:
theme.palette.type === 'light'
? theme.palette.grey[200]
: theme.palette.grey[700],
},
bucketTitle: {
fontSize: '16px',
textAlign: 'left',
},
bucketText: {
display: 'flex',
justifyContent: 'left',
alignItems: 'baseline',
fontSize: '12px',
textAlign: 'left',
marginBottom: theme.spacing(2),
},
}));
const UserBuckets = (props) => {
const { buckets } = props;
const classes = useStyles();
if (!buckets || buckets.length === 0) return <p>Can not find any buckets, sorry</p>;
return (
<React.Fragment>
<Container maxWidth="md" component="main">
<Grid container spacing={5} alignItems="flex-end">
{buckets.map((buckets) => {
return (
// Enterprise card is full width at sm breakpoint
<Grid item key={buckets.owner} xs={12} md={4}>
<Card className={classes.card}>
<CardMedia
className={classes.cardMedia}
image="https://source.unsplash.com/random"
title="Image title"
/>
<CardContent className={classes.cardContent}>
<Typography
gutterBottom
variant="h6"
component="h2"
className={classes.bucketTitle}
>
{buckets.name.substr(0, 50)}...
</Typography>
<div className={classes.bucketText}>
<Typography
component="p"
color="textPrimary"
></Typography>
<Typography variant="p" color="textSecondary">
{buckets.stock_list}...
</Typography>
</div>
</CardContent>
</Card>
</Grid>
);
})}
</Grid>
</Container>
</React.Fragment>
);
};
export default UserBuckets;
Loading.js
import React from 'react';
function LoadingComponent(Component) {
return function LoadingComponent({ isLoading, ...props }) {
if (!isLoading) return <Component {...props} />;
return (
<p style={{ fontSize: '25px' }}>
We are waiting for the data to load!...
</p>
);
};
}
export default LoadingComponent;
Thank in advance...
The error came from this culprit and my mistake not seeing the important of the letter key in item key. This is how I solved my error:
original code
<Grid item key={buckets.owner} xs={12} md={4}>
fixed code
<Grid item key={buckets.id} xs={12} md={4}>
I am new to Next js, I want to call the news api in useEffect and dispatch the news array to my store. then filter it based on the user's input in the search bar in the header. problem is once the use effect data fetching is done and user starts typing rather than filtering the news array the screen gets re-render and the data fetching starts again. how to prevent this re-rendering and save the news array?
I tried to use getStaticprops but useDispatch is not allowed in there.
index.js
import { fetchNews } from "../store/actions/newsActions";
import NewsInfo from "../components/NewsInfo";
import { useDispatch, useSelector } from "react-redux";
import CircularProgress from "#material-ui/core/CircularProgress";
export default function Home() {
const dispatch = useDispatch();
const { news } = useSelector((state) => state.news);
React.useEffect(() => {
dispatch(fetchNews());
}, []);
return (
<>
{/* this wrapper cmp will make each headline uniquely accessible */}
{news?.articles ? (
news.articles.map((article) => (
<React.Fragment key={article.publishedAt}>
<NewsInfo headlines={article} />
</React.Fragment>
))
) : (
<CircularProgress />
)}
</>
);
}
Header.js
import React from "react";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import IconButton from "#material-ui/core/IconButton";
import Typography from "#material-ui/core/Typography";
import InputBase from "#material-ui/core/InputBase";
import { fade, makeStyles } from "#material-ui/core/styles";
import MenuIcon from "#material-ui/icons/Menu";
import SearchIcon from "#material-ui/icons/Search";
import { useDispatch } from "react-redux";
import { filterHeadlines } from "../store/actions/newsActions";
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
menuButton: {
marginRight: theme.spacing(2),
},
title: {
flexGrow: 1,
display: "none",
[theme.breakpoints.up("sm")]: {
display: "block",
},
},
search: {
position: "relative",
borderRadius: theme.shape.borderRadius,
backgroundColor: fade(theme.palette.common.white, 0.15),
"&:hover": {
backgroundColor: fade(theme.palette.common.white, 0.25),
},
marginLeft: 0,
width: "100%",
[theme.breakpoints.up("sm")]: {
marginLeft: theme.spacing(1),
width: "auto",
},
},
searchIcon: {
padding: theme.spacing(0, 2),
height: "100%",
position: "absolute",
pointerEvents: "none",
display: "flex",
alignItems: "center",
justifyContent: "center",
},
inputRoot: {
color: "inherit",
},
inputInput: {
padding: theme.spacing(1, 1, 1, 0),
// vertical padding + font size from searchIcon
paddingLeft: `calc(1em + ${theme.spacing(4)}px)`,
transition: theme.transitions.create("width"),
width: "100%",
[theme.breakpoints.up("sm")]: {
width: "12ch",
"&:focus": {
width: "20ch",
},
},
},
}));
function SearchAppBar() {
const classes = useStyles();
const dispatch = useDispatch();
const [input, setInput] = React.useState("");
return (
<div className={classes.root}>
<AppBar position="static">
<Toolbar>
<IconButton
edge="start"
className={classes.menuButton}
color="inherit"
aria-label="open drawer"
>
<MenuIcon />
</IconButton>
<Typography className={classes.title} variant="h6" noWrap>
Material-UI
</Typography>
<div className={classes.search}>
<div className={classes.searchIcon}>
<SearchIcon />
</div>
<InputBase
placeholder="Search…"
classes={{
root: classes.inputRoot,
input: classes.inputInput,
}}
inputProps={{ "aria-label": "search" }}
value={input}
onChange={(e) => setInput(e.target.value)}
onBlur={() => dispatch(filterHeadlines(input))}
/>
</div>
</Toolbar>
</AppBar>
</div>
);
}
export default SearchAppBar;
you can use store dispatch to getServersideProps, getstaticprops
from your store file
export const rootDispatch = store.dispatch;
I cant not use compose withRouter and withAlert
It's work only withRouter but I cant used this.props.alert.success...............................................................................................................................
It show error
Unhandled Rejection (TypeError): Cannot read property 'success' of undefined
import React from "react";
import Container from "#material-ui/core/Container";
import Typography from "#material-ui/core/Typography";
import Grid from "#material-ui/core/Grid";
import { withRouter } from "react-router-dom";
import Paper from "#material-ui/core/Paper";
import Button from "#material-ui/core/Button";
import { withAlert } from "react-alert";
import Select from "#material-ui/core/Select";
import InputLabel from "#material-ui/core/InputLabel";
import MenuItem from "#material-ui/core/MenuItem";
import FormControl from "#material-ui/core/FormControl";
import { compose } from "redux";
import { firestore } from "../../firebase/firebase.utils";
class Updatestatusproperty extends React.Component {
constructor(props) {
super(props);
this.state = {
id: this.props.match.params.id,
status:1
};
}
handleSubmit = async (event) => {
event.preventDefault();
this.props.alert.success("อัพเดทสถานะบ้านเสร็จสิ้น");
// firestore
// .collection("house")
// .doc(this.state.id)
// .update({status:this.state.status})
// .then(() => {
// this.props.alert.success("อัพเดทสถานะบ้านเสร็จสิ้น");
// })
// .catch((err) => {
// console.log(err)
// });
};
handleChange = (event) => {
this.setState({ status: event.target.value });
console.log( event.target.value )
};
render() {
return (
<Container
maxWidth="md"
style={{ paddingTop: "4%", paddingBottom: "4%" }}
>
<Paper
variant="outlined"
style={{
backgroundColor: "#f2f2f2",
backgroundPosition: "center",
backgroundSize: "cover",
backgroundRepeat: "no-repeat",
padding: "4%",
}}
>
<Grid container spacing={3}>
<Grid item xs={6}>
<Typography variant="h4">{"อัพเดทสถานะ"}</Typography>
</Grid>
<Grid item xs={12}>
<Typography variant="h6">{"อัพเดทสถานะบ้าน"}</Typography>
</Grid>
<Grid item xs={4}>
<FormControl fullWidth>
<InputLabel id="demo-simple-select-label">
อัพเดทสถานะบ้าน
</InputLabel>
<Select name="sizefamily" onChange={this.handleChange} value={this.state.status}>
<MenuItem value={1}>พร้อมขาย</MenuItem>
<MenuItem value={2}>อยู่ระหว่างเจรจา</MenuItem>
<MenuItem value={3}>ขายออกไปแล้ว</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={3}>
<Button
type="submit"
variant="contained"
color="primary"
fullWidth
size="large"
style={{ backgroundColor: "#55aa54", marginTop: "3.5%" }}
onClick={this.handleSubmit}
>
อัพเดทสถานะ
</Button>
</Grid>
</Grid>
</Paper>
</Container>
);
}
}
export default compose(withRouter(Updatestatusproperty),withAlert());
You are using the compose function incorrectly.
You need to pass the HOCs, withRoute as arguments to it without calling it like below
export default compose(withRouter,withAlert())(Updatestatusproperty);