I have API that returns some tabular data. I need to display these data in a Table. It's not clear to me how to achieve this goal.
Let's assume that I want to display the fields id and name stored in groups. How can I show them in the Material-UI Table?
Please see my current code below. It does not throw any error. But neither is shows a Table with the data.
import '../../App.css';
import React, { useEffect } from 'react'
import { makeStyles } from '#material-ui/core/styles';
import Grid from '#material-ui/core/Grid';
import Table from '#material-ui/core/Table';
import TableBody from '#material-ui/core/TableBody';
import TableCell from '#material-ui/core/TableCell';
import TableContainer from '#material-ui/core/TableContainer';
import TableHead from '#material-ui/core/TableHead';
import TableRow from '#material-ui/core/TableRow';
import Paper from '#material-ui/core/Paper';
import axios from 'axios'
import config from '../../config/config.json';
const useStyles = makeStyles((theme) => ({
root: {
width: '100%',
},
heading: {
fontSize: theme.typography.pxToRem(18),
fontWeight: theme.typography.fontWeightBold,
},
content: {
fontSize: theme.typography.pxToRem(14),
fontWeight: theme.typography.fontWeightRegular,
textAlign: "left",
marginTop: theme.spacing.unit*3,
marginLeft: theme.spacing.unit*3,
marginRight: theme.spacing.unit*3
},
table: {
minWidth: 650,
},
tableheader: {
fontWeight: theme.typography.fontWeightBold,
color: "#ffffff",
background: "#3f51b5"
},
tableCell: {
background: "#f50057"
},
button: {
fontSize: "12px",
minWidth: 100
},
}));
export function Main() {
const [groups, setGroup] = React.useState('');
const classes = useStyles();
const options = {
'headers': {
'Authorization': `Bearer ${localStorage.getItem('accessToken')}`
}
}
useEffect(() => {
axios.get(config.api.url + '/api/test', options)
.then( (groups) => {
this.setState({response: groups})
})
.catch( (error) => {
console.log(error);
})
}, [])
return (
<div className={classes.root}>
<Grid container spacing={3}>
<Grid item xs={12} className={classes.content}>
<TableContainer component={Paper}>
<Table id="split_table" size="small">
<TableHead>
</TableHead>
<TableBody>
{Object.keys(groups).map( (row, index) => (
<TableRow key={index} selected="false">
<TableCell>Test</TableCell>
<TableCell>Test</TableCell>
</TableRow>))}
</TableBody>
</Table>
</TableContainer>
</Grid>
</Grid>
</div>
)
}
Update:
As I mentioned in comments, I followed the recommendations from answers, but I still see an empty table, while I can see a correct value in console.
useEffect(() => {
axios.get(config.api.url + '/api/test', options)
.then( (groups) => {
setGroup(groups.data.subtask)
console.log(groups.data.subtask);
})
.catch( (error) => {
console.log(error);
})
}, [])
return (
<div className={classes.root}>
<Grid container spacing={3}>
<Grid item xs={12} className={classes.content}>
<TableContainer component={Paper}>
<Table id="split_table" size="small">
<TableHead>
</TableHead>
<TableBody>
{Object.keys(groups).map( (item, index) => (
<TableRow key={index} selected="false">
<TableCell>{item.user_id}</TableCell>
<TableCell>{item.task_name}</TableCell>
</TableRow>))}
</TableBody>
</Table>
</TableContainer>
</Grid>
</Grid>
</div>
)
This is what I see in the browser:
This is an example of data (groups.data.subtask):
I think the problem is that you use this.setState instead of setGroup
useEffect(() => {
axios.get(config.api.url + '/api/test', options)
.then( (groups) => {
setGroup(groups)
})
.catch( (error) => {
console.log(error);
})
}, [])
Change your map function
{Object.keys(groups).map( (row, index) => (
<TableRow key={index} selected="false">
<TableCell>{row._id}</TableCell>
<TableCell>{row.user_id}</TableCell>
</TableRow>))}
import '../../App.css';
import React, { useEffect } from 'react'
import { makeStyles } from '#material-ui/core/styles';
import Grid from '#material-ui/core/Grid';
import Table from '#material-ui/core/Table';
import TableBody from '#material-ui/core/TableBody';
import TableCell from '#material-ui/core/TableCell';
import TableContainer from '#material-ui/core/TableContainer';
import TableHead from '#material-ui/core/TableHead';
import TableRow from '#material-ui/core/TableRow';
import Paper from '#material-ui/core/Paper';
import axios from 'axios'
import config from '../../config/config.json';
const useStyles = makeStyles((theme) => ({
root: {
width: '100%',
},
heading: {
fontSize: theme.typography.pxToRem(18),
fontWeight: theme.typography.fontWeightBold,
},
content: {
fontSize: theme.typography.pxToRem(14),
fontWeight: theme.typography.fontWeightRegular,
textAlign: "left",
marginTop: theme.spacing.unit*3,
marginLeft: theme.spacing.unit*3,
marginRight: theme.spacing.unit*3
},
table: {
minWidth: 650,
},
tableheader: {
fontWeight: theme.typography.fontWeightBold,
color: "#ffffff",
background: "#3f51b5"
},
tableCell: {
background: "#f50057"
},
button: {
fontSize: "12px",
minWidth: 100
},
}));
export function Main() {
const [groups, setGroup] = React.useState([]);
const classes = useStyles();
const options = {
'headers': {
'Authorization': `Bearer ${localStorage.getItem('accessToken')}`
}
}
useEffect(() => {
axios.get(config.api.url + '/api/test', options)
.then( (groups) => {
setGroup(groups.data.subtask)
console.log(groups.data.subtask);
})
.catch( (error) => {
console.log(error);
})
}, [])
return (
<div className={classes.root}>
<Grid container spacing={3}>
<Grid item xs={12} className={classes.content}>
<TableContainer component={Paper}>
<Table id="split_table" size="small">
<TableHead>
</TableHead>
<TableBody>
{Object.keys(groups).map( (item, index) => (
<TableRow key={index} selected="false">
<TableCell>{item.user_id}</TableCell>
<TableCell>{item.task_name}</TableCell>
</TableRow>))}
</TableBody>
</Table>
</TableContainer>
</Grid>
</Grid>
</div>
)
I think it's the Object.keys(groups) .
It's not React state, so it will not re-render?
Can you try to make a groupKey state and then useEffect to update the state when groups is updated.
const [groupKey,setGroupKey] = useState([]);
useEffect(() => {
setGroupKey(Object.keys(groups));
},[groups]);
In the component , use
{groupKey.map((item, index) => (
<TableRow key={index} selected="false">
<TableCell>{item.user_id}</TableCell>
<TableCell>{item.task_name}</TableCell>
</TableRow>))
}
You get the idea.
Something like below should help:
Related
I am making the infinite scroll in react js , but i am having problem setting the posts value whenever i refresh the page the posts length is 0 whereas it should have been 10.
But after the first reload if i don't reload and just change something in code ( lets say add console.log ) and save it then the whole infinite scroll starts working but if i reload then it stops working
Please help.
Feed.js
import { Box, CircularProgress, Stack, Typography } from "#mui/material";
import Post from "./Post";
import About from "./About";
import { useEffect, useRef, useState } from "react";
import { getInfiniteScroll } from "../apis/posts";
import { useNavigate } from "react-router-dom";
const Feed = () => {
// const posts = useSelector((state) => state.allPosts.posts);
const [posts, setPosts] = useState([]);
const [skip, setSkip] = useState(0);
const [isEnd, setIsEnd] = useState(false);
const ref = useRef();
useEffect(() => {
fetchPosts();
ref.current?.addEventListener("scroll", handleScroll);
// return () => ref.current?.removeEventListener("scroll", handleScroll);
}, [skip]);
const fetchPosts = async () => {
try {
const { data, error } = await getInfiniteScroll(skip);
if (error) {
console.log(error);
return;
}
if (data?.length === 0) {
setIsEnd(true);
return;
}
// setPosts(data);
setPosts([...posts, ...data]);
console.log(posts.length);
} catch (error) {
console.log(error.message);
}
};
const handleScroll = (e) => {
const { offsetHeight, scrollTop, scrollHeight } = e.target;
if (offsetHeight + scrollTop >= scrollHeight) {
console.log(posts?.length);
setSkip(posts?.length);
}
console.log("skip : ", skip);
};
return (
<Box flex={4} sx={{ padding: { xs: "0", sm: "0px 20px " } }}>
<Box
ref={ref}
// onScroll={handleScroll}
sx={{
width: { xs: "100%", sm: "105% " },
marginBottom: "50px",
height: "600px",
overflow: "scroll",
overflowX: "hidden",
}}>
{posts.length > 0 ? (
posts.map((post) => <Post key={post._id} {...post} />)
) : (
<Box
sx={{
display: "flex",
justifyContent: "center",
alignSelf: "center",
marginTop: "200px",
}}>
<CircularProgress
sx={{
alignSelf: "center",
}}
/>
</Box>
)}
</Box>
<Box
sx={{
display: { sm: "none", xs: "block" },
justifyContent: "center",
alignItems: "center",
paddingBottom: "50px",
}}>
<About />
</Box>
</Box>
);
};
export default Feed;
Posts.js
import {
Avatar,
Card,
CardActions,
CardContent,
CardHeader,
CardMedia,
Checkbox,
IconButton,
} from "#mui/material";
import ShareIcon from "#mui/icons-material/Share";
import MoreVertIcon from "#mui/icons-material/MoreVert";
import Favorite from "#mui/icons-material/Favorite";
import { FavoriteBorder } from "#mui/icons-material";
import { useNavigate } from "react-router-dom";
import SmartText from "../Helpers/SmartText";
import { capitalize } from "../Helpers/Capitalize";
const Post = ({ _id, desc, title, photo, caption, updatedAt }) => {
const navigate = useNavigate();
return (
<Card sx={{ marginBottom: "20px" }}>
<CardHeader
avatar={
<Avatar sx={{ bgcolor: "red" }} aria-label="recipe">
{Array.from(title)[0]}
</Avatar>
}
action={
<IconButton aria-label="settings">
<MoreVertIcon />
</IconButton>
}
title={capitalize(title)}
subheader={updatedAt}
onClick={() => {
navigate("/posts/singlePost/" + _id);
}}
sx={{ cursor: "pointer" }}
/>
<CardMedia component="img" height="20%" image={photo} alt="Paella dish" />
<CardContent>
<SmartText text={desc} />
{/* <Typography variant="body2" color="text.secondary">
{post.desc}
</Typography> */}
</CardContent>
<CardActions disableSpacing>
<IconButton aria-label="add to favorites">
<Checkbox
icon={<FavoriteBorder />}
checkedIcon={<Favorite sx={{ color: "red" }} />}
/>
</IconButton>
<IconButton aria-label="share">
<ShareIcon />
</IconButton>
{/* <ExpandMore
expand={expanded}
aria-expanded={expanded}
aria-label="show more"
>
<ExpandMoreIcon />
</ExpandMore> */}
</CardActions>
</Card>
);
};
export default Post;
api/posts.js
export const getInfiniteScroll = async (skip) => {
try {
const res = await fetch(`/api/posts/infiniteScroll?skip=${skip}`, {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
});
return await res.json();
} catch (error) {
throw new Error(error);
}
};
backend/postController.js
exports.getInfinitePost = async (req, res) => {
const skip = req.query.skip ? Number(req.query.skip) : 0;
const DEFAULT_LIMIT = 10;
try {
const posts = await Post.find({}).skip(skip).limit(DEFAULT_LIMIT);
res.status(201).json({ data: posts, success: true });
} catch (error) {
res.status(400).json({ message: error });
}
};
Now I have created this custom hook to perform lazy loading,which takes redux slice action as input and
import { useState, useEffect, useCallback, useRef } from "react";
import { useDispatch } from "react-redux";
function useLazyFetch(fetchAction) {
const dispatch = useDispatch();
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const loadMoreRef = useRef(null);
const handleObserver = useCallback(async(entries) => {
const [target] = entries;
console.log(target.isIntersecting);
if (target.isIntersecting) {
console.log("INTERSECTING.....");
await new Promise((r) => setTimeout(r, 2000));
setPage((prev) => prev + 1);
}
}, []);
useEffect(() => {
const option = {
root: null,
rootMargin: "0px",
threshold: 1.0,
};
const observer = new IntersectionObserver(handleObserver, option);
if (loadMoreRef.current) observer.observe(loadMoreRef.current);
}, [handleObserver]);
const fetchApi = useCallback(async () => {
try {
setLoading(true);
await new Promise((r) => setTimeout(r, 2000));
dispatch(fetchAction(page))
setLoading(false);
} catch (err) {
console.error(err);
}
}, [page,fetchAction,dispatch]);
useEffect(() => {
fetchApi();
}, [fetchApi]);
return { loading, loadMoreRef };
}
export default useLazyFetch;
I am using this in my component like this, here you can see I am tracking div in the bottom using loadMoreRef from useLazyFetch, Now when I am commenting out the fetchApi(); from custom hook its working as expected, on scroll its logging INTERSECTING... in the console but the moment I try to execute the action through fetchApi() my whole app goes into loop,the div tracker with ref comes to top and it fetches the posts but after immediately that action repeats the tracker comes to top and page becomes empty & it fetches next set of posts,I can see that my list is getting appended new set of posts to state in redux dev tool instead of completely setting new state, but in UI it's rendering all posts again and again whic is causing the loop,how can I avoid this ?
import { CircularProgress, Grid, IconButton, Typography } from "#mui/material";
import { Box } from "#mui/system";
import React, { useEffect,useRef,useState } from "react";
import AssistantIcon from "#mui/icons-material/Assistant";
import Post from "../components/Post";
import { useDispatch, useSelector } from "react-redux";
import { getPosts } from "../redux/postSlice";
import AddPost from "../components/AddPost";
import useLazyFetch from "../hooks/useLazyFetch";
export default function Home() {
const dispatch = useDispatch();
// const api = `https://picsum.photos/v2/list`
const { status, posts } = useSelector((state) => state.post);
const {loading,loadMoreRef} = useLazyFetch(getPosts)
useEffect(() => {
dispatch(getPosts());
}, []);
return (
<Box>
<Box borderBottom="1px solid #ccc" padding="8px 20px">
<Grid container justifyContent="space-between" alignItems="center">
<Grid item>
<Typography variant="h6">Home</Typography>
</Grid>
<Grid item>
<IconButton>
<AssistantIcon />
</IconButton>
</Grid>
</Grid>
</Box>
<Box height="92vh" sx={{ overflowY: "scroll" }}>
<AddPost />
<Box textAlign="center" marginTop="1rem">
{status === "loading" && (
<CircularProgress size={20} color="primary" />
)}
</Box>
{status === "success" &&
posts?.map((post) => <Post key={post._id} post={post} />)}
<div style={{height:"50px",width:"100px",backgroundColor:"red"}} ref={loadMoreRef}>{loading && <p>loading...</p>}</div>
</Box>
</Box>
);
}
And here is my redux action & state update part
const initialState = {
status: "idle",
posts: []
};
export const getPosts = createAsyncThunk("post/getPosts", async (page) => {
console.log(page);
console.log("calling api ...");
const { data } = await axios.get(`/api/posts?page=${page}`);
return data;
});
export const postSlice = createSlice({
name: "post",
initialState,
reducers: {},
extraReducers: {
[getPosts.pending]: (state, action) => {
state.status = "loading";
},
[getPosts.fulfilled]: (state, action) => {
state.status = "success";
state.posts = [...state.posts,...action.payload.response.posts] ;
},
[getPosts.rejected]: (state, action) => {
state.status = "failed";
},
}
this is the solution that is working
import { CircularProgress, Grid, IconButton, Typography } from "#mui/material";
import { Box } from "#mui/system";
import React, { useEffect,useMemo } from "react";
import AssistantIcon from "#mui/icons-material/Assistant";
import Post from "../components/Post";
import { useDispatch, useSelector } from "react-redux";
import { getPosts } from "../redux/postSlice";
import AddPost from "../components/AddPost";
import useLazyFetch from "../hooks/useLazyFetch";
export default function Home() {
const { status, posts } = useSelector((state) => state.post);
const {loading,loadMoreRef} = useLazyFetch(getPosts)
const renderedPostList = useMemo(() => (
posts.map((post) => {
return( <Post key={post._id.toString()} post={post} />)
})
), [posts])
return (
<Box>
<Box borderBottom="1px solid #ccc" padding="8px 20px">
<Grid container justifyContent="space-between" alignItems="center">
<Grid item>
<Typography variant="h6">Home</Typography>
</Grid>
<Grid item>
<IconButton>
<AssistantIcon />
</IconButton>
</Grid>
</Grid>
</Box>
<Box height="92vh" sx={{ overflowY: "scroll" }}>
<AddPost />
<Box textAlign="center" marginTop="1rem">
{status === "loading" && (
<CircularProgress size={20} color="primary" />
)}
</Box>
{renderedPostList}
<div style={{height:"50px",width:"100px",backgroundColor:"red"}} ref={loadMoreRef}>{loading && <p>loading...</p>}</div>
</Box>
</Box>
);
}
}
I used useMemo hook to memoize and it works as expected
I have an upload file component, delete file component and files table component(presents the existing files in the system using axios) in the same page:
filesPage.js
import React from 'react';
import UploadFile from '../components/UploadFile'
import DeleteFile from '../components/DeleteFile'
import FilesTable from '../components/FilesTable'
function UploadFiles() {
return (
<div className="filesPage">
<UploadFile/>
<DeleteFile/>
<FilesTable/>
</div>
)
}
export default UploadFiles;
Now I want every time I upload new file or delete one, the files table will be updated which means after the axios post/delete, I need to rerender the files table component and do axios get again to get the active files.
Someone can help?
FilesTable.js
import React, {useState, useEffect} from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Paper from '#material-ui/core/Paper';
import Table from '#material-ui/core/Table';
import TableBody from '#material-ui/core/TableBody';
import TableCell from '#material-ui/core/TableCell';
import TableContainer from '#material-ui/core/TableContainer';
import TableHead from '#material-ui/core/TableHead';
import TablePagination from '#material-ui/core/TablePagination';
import TableRow from '#material-ui/core/TableRow';
import axios from 'axios';
function FilesTable() {
const [tfaArray, setTfaArray] = useState([]);
useEffect(() => {
axios.get("api/tfa").then((res) => setTfaArray(res.data)).catch((err) => console.log(err));
}, [])
const columns = [
{id: 'fileId', label: '#', minWidth: 100},
{id: 'name', label: 'name', minWidth: 100},
{id: 'date', label: 'upload date', minWidth: 100}
];
const rows = tfaArray.map((tfa, index) => ({fileId: (index + 1), name: tfa.fileName, date: tfa.dateTime.slice(0,24)}) )
const useStyles = makeStyles({
root: {
width: '50%',
position: 'absolute',
right: '10%',
top: '15%',
},
container: {
maxHeight: 322
},
headerCell: {
background: '#F5F5F5',
fontSize: '16px',
zIndex: '0'
}
});
const classes = useStyles();
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(5);
const handleChangePage = (event, newPage) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (event) => {
setRowsPerPage(+event.target.value);
setPage(0);
};
return (
<>
<Paper className={classes.root}>
<TableContainer className={classes.container}>
<Table stickyHeader aria-label="sticky table">
<TableHead>
<TableRow>
{columns.map((column) => (
<TableCell className={classes.headerCell}
key={column.id}
style={{ minWidth: column.minWidth }}>
{column.label}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((row, index) => {
return (
<TableRow hover role="checkbox" tabIndex={-1} key={index}>
{columns.map((column) => {
const value = row[column.id];
return (
<TableCell key={column.id}>
{value}
</TableCell>
);
})}
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={[5, 10, 15]}
component="div"
count={rows.length}
rowsPerPage={rowsPerPage}
page={page}
onChangePage={handleChangePage}
onChangeRowsPerPage={handleChangeRowsPerPage}
/>
</Paper>
</>
)
}
export default FilesTable;
Typically with React you do this by lifting state up as described in this React documentation.
In this case, you'd lift the state to the parent of these components, and have the FilesTable receive the list of files as a prop. (Props are basically component state managed by the parent rather than by the component itself.) Similarly the DeleteFile component would receive the function to call to delete a file, the UploadFile component would receive the function to use to add a file, etc.
Here's a simplified example:
const {useState} = React;
const Parent = () => {
const [files, setFiles] = useState([]);
const addFile = (file) => {
setFiles(files => [...files, file]);
};
const removeFile = (file) => {
setFiles(files => files.filter(f => f !== file));
};
return (
<div>
<FilesTable files={files} removeFile={removeFile} />
<UploadFile addFile={addFile} />
</div>
);
};
const FilesTable = ({files, removeFile}) => {
return (
<React.Fragment>
<div>{files.length === 1 ? "One file:" : `${files.length} files:`}</div>
<ul className="files-table">
{files.map(file => (
<li>
<span>{file}</span>
<span className="remove-file" onClick={() => removeFile(file)}>[X]</span>
</li>
))}
</ul>
</React.Fragment>
);
};
const UploadFile = ({addFile}) => {
const [file, setFile] = useState("");
const onClick = () => {
addFile(file);
setFile("");
};
return (
<div>
<input type="text" value={file} onChange={(e) => setFile(e.target.value)} />
<input type="button" value="Add" disabled={!file} onClick={onClick} />
</div>
);
};
ReactDOM.render(<Parent />, document.getElementById("root"));
ul.files-table {
list-style-type: none;
}
.remove-file {
margin-left: 0.5rem;
cursor: pointer;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
How can I send the value of the checkbox to the checkout.js page? This is the PaymentForm page. I tried my best but it's not working correctly. Basically, I want to use the PaymentForm fields in checkout.js page because my submit button is there.
PaymentForm.js
import React from 'react';
import Typography from '#material-ui/core/Typography';
import Grid from '#material-ui/core/Grid';
import TextField from '#material-ui/core/TextField';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import Checkbox from '#material-ui/core/Checkbox';
import { createStyles } from '#material-ui/core/styles';
import StripeCheckout from 'react-stripe-checkout'
import 'react-toastify/dist/ReactToastify.css';
const styles = createStyles({
formControlLabel: {
fontSize: '1.5rem',
'& label': { fontSize: '5rem' }
}
});
const handleToken = (token) => {
console.log(token);
}
const PaymentForm = ({ PaymentForm, changePaymentForm }) => {
const [state, setState] = React.useState({
checkedA: false,
});
const handleChange = (event) => {
setState({ ...state, [event.target.name]: event.target.checked });
};
return (
<React.Fragment>
<Typography variant="h4" gutterBottom>
Payment method
</Typography><br />
<Grid container spacing={3}>
<Grid item xs={12}>
<FormControlLabel
control={<Checkbox checked={state.checkedA} onChange={handleChange} name="checkedA"/>}
label={<Typography style={styles.formControlLabel}>Cash on delivery</Typography>}
/>
</Grid>
<Grid item xs={12}>
<StripeCheckout
stripeKey="pk_test_51I9XPQAesAg2GfzQyVB7VgP0IbmWwgcfeFJSuCpB2kbNu60AFTbFhC7dxwje8YF4w2ILMJ6o2InB9ENczpd4dCSa00e09XoDbw"
token={handleToken}
amount={2 * 100}
name="All Products"
/>
</Grid>
</Grid>
</React.Fragment>
);
};
export default PaymentForm;
Checkout.js
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import CssBaseline from '#material-ui/core/CssBaseline';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import Paper from '#material-ui/core/Paper';
import Stepper from '#material-ui/core/Stepper';
import Step from '#material-ui/core/Step';
import StepLabel from '#material-ui/core/StepLabel';
import Button from '#material-ui/core/Button';
import Link from '#material-ui/core/Link';
import Typography from '#material-ui/core/Typography';
import axios from '../../axios-orders';
import AddressForm from './CheckoutForm';
import PaymentForm from './PaymentForm';
import Review from './Review';
const useStyles = makeStyles((theme) => ({
appBar: {
position: 'relative',
},
layout: {
width: 'auto',
marginLeft: theme.spacing(2),
marginRight: theme.spacing(2),
[theme.breakpoints.up(1000 + theme.spacing(2) * 2)]: {
width: 1100,
marginLeft: 'auto',
marginRight: 'auto',
},
},
paper: {
marginTop: theme.spacing(3),
marginBottom: theme.spacing(3),
padding: theme.spacing(2),
[theme.breakpoints.up(700 + theme.spacing(3) * 2)]: {
marginTop: theme.spacing(6),
marginBottom: theme.spacing(6),
padding: theme.spacing(3),
backgroundColor: 'rgb(248, 246, 244)',
},
},
stepper: {
padding: theme.spacing(5, 0, 5),
fontWeight: 'bold',
backgroundColor: 'rgb(248, 246, 244)',
},
buttons: {
display: 'flex',
justifyContent: 'flex-end',
},
button: {
marginTop: theme.spacing(3),
marginLeft: theme.spacing(1),
border: "none"
},
}));
const steps = ['Shipping address', 'Payment details', 'Review your order'];
function Copyright() {
return (
<Typography variant="body2" color="textSecondary" align="center">
{'Copyright © '}
<Link color="inherit" href="https://material-ui.com/">
Your Website
</Link>{' '}
{new Date().getFullYear()}
{'.'}
</Typography>
);
}
function getStepContent(step, formValues = null, changeFormValue = null, paymentValues = null, changePaymentValue = null) {
switch (step) {
case 0:
return <AddressForm addressValues={formValues} changeAddressValue={changeFormValue} />;
case 1:
return <PaymentForm PaymentForm={paymentValues} changePaymentForm={changePaymentValue}/>;
case 2:
return <Review />;
default:
throw new Error('Unknown step');
}
}
export default function Checkout(props) {
const classes = useStyles();
const [addressFormValues, setAddressFormValues] = React.useState({});
const [paymentFormValues, setPaymentFormValues] = React.useState({});
const [paymentFormNewValues, setPaymentFormNewValues] = React.useState({});
const [activeStep, setActiveStep] = React.useState(0);
if(paymentFormValues === true){
setPaymentFormNewValues('Cash')
}
if(paymentFormValues === false){
setPaymentFormNewValues('Online')
}
console.log('[paymentFormNewValues: ]', paymentFormNewValues)
console.log('[paymentFormValues: ]', paymentFormValues)
const handleNext = () => {
setActiveStep(activeStep + 1);
axios.post('/UserPortal/CartItems/checkout_details_check.php', {
customer_id: localStorage.getItem('Id'),
})
.then((response) => {
if(response.data === null)
{
axios.post('/UserPortal/CartItems/checkout_details.php', {
customer_id: localStorage.getItem('Id'),
firstname: addressFormValues.firstname,
lastname: addressFormValues.lastname,
address: addressFormValues.address,
city: addressFormValues.city,
state: addressFormValues.state
})
.then((response) => {
console.log(response.data);
})
}
else{
axios.post('/UserPortal/CartItems/checkout_details_update.php', {
customer_id: localStorage.getItem('Id'),
firstname: addressFormValues.firstname,
lastname: addressFormValues.lastname,
address: addressFormValues.address,
city: addressFormValues.city,
state: addressFormValues.state,
payment_method: paymentFormNewValues
})
.then((response) => {
console.log(response.data);
})
}
})
};
const handleBack = () => {
setActiveStep(activeStep - 1);
};
const changeAddressFormValue = (key, value) => {
let values = { ...addressFormValues };
values[key] = value;
setAddressFormValues(values);
};
const changePaymentFormValue = (key, value) => {
let values = { ...addressFormValues };
values[key] = value;
setPaymentFormValues(values);
};
return (
<React.Fragment>
<CssBaseline />
<AppBar position="absolute" color="default" className={classes.appBar}></AppBar>
<main className={classes.layout}>
<Paper className={classes.paper}>
<Typography component="h1" variant="h3" align="center">
Checkout
</Typography>
<Stepper activeStep={activeStep} className={classes.stepper}>
{steps.map((label) => (
<Step key={label}>
<StepLabel><Typography component="h1" variant="h5" align="center">
{label} </Typography></StepLabel>
</Step>
))}
</Stepper>
<React.Fragment>
{activeStep === steps.length ? (
<React.Fragment>
<Typography variant="h5" gutterBottom>
Thank you for your order.
</Typography>
<Typography variant="subtitle1">
Your order number is #2001539. We have emailed your order
confirmation, and will
send you an update when your order has shipped.
</Typography>
</React.Fragment>
) : (
<React.Fragment>
{activeStep === 0 ? getStepContent(activeStep, addressFormValues, changeAddressFormValue , paymentFormValues, changePaymentFormValue) : getStepContent(activeStep)}
{ <div className={classes.buttons}>
{ activeStep !== 0 && (
<Button variant="contained" style={{outline: 'none'}}
className={classes.button}
onClick={handleBack}
>
Back
</Button>
)}
<Button style={{outline: 'none'}}
variant="contained"
color="secondary"
onClick={handleNext}
className={classes.button}
>
{activeStep === steps.length - 1 ? 'Place order' : 'Next'}
</Button>
</div> }
</React.Fragment>
)}
</React.Fragment>
</Paper>
<Copyright />
</main>
</React.Fragment>
);
}
It seems you have checked the activeStep wrong.
Maybe the right code must be like the following:
<React.Fragment>
{activeStep !== 0 ?getStepContent(activeStep, addressFormValues, changeAddressFormValue , paymentFormValues, changePaymentFormValue) : getStepContent(activeStep)}
Do you ever consider using Context API?
React Context API
But also you can use create a checkboxValue with useState in Checkout.js and pass setCheckBoxValue to PaymentForm in getStepContent as prop. When checkbox checked trigger setCheckBoxValue and it will trigger parent component's state (Checkbox).
I have the father which is Productos and a child called EditarProductos. I want to pass producto.id to EditarProductos.
Here is Productos:
import {Button, TableHead, TableRow, TableCell, TableBody, Table} from '#material-ui/core'
import { withStyles, makeStyles } from '#material-ui/core/styles';
import InsertarProductos from './InsertarProductos';
import Grid from '#material-ui/core/Grid';
import EditIcon from '#material-ui/icons/Edit';
import DeleteIcon from '#material-ui/icons/Delete';
import EditarProductos from './EditarProductos';
const StyledTableCell = withStyles((theme) => ({
head: {
backgroundColor: theme.palette.common.black,
color: theme.palette.common.white,
},
body: {
fontSize: 14,
},
}))(TableCell);
const StyledTableRow = withStyles((theme) => ({
root: {
'&:nth-of-type(odd)': {
backgroundColor: theme.palette.background.default,
},
},
}))(TableRow);
function Productos(props) {
const [productos, setProductos] = useState([]);
var id='';
useEffect(() => {
const getProductos = async () => {
const res = await fetch("/productos", {
method: 'GET',
headers: {'Content-Type': 'application/json'},
})
//console.log(res);
const response = await res.json();
setProductos(response);
}
getProductos();
})
function editar(producto){
console.log("entro 1");
//console.log(producto.id);
id = producto.id;
console.log(id);
return <EditarProductos productoEdit = {id}/>;
}
function eliminar(producto){
console.log(producto.id);
id=producto.id;
deleteProductos();
window.location.reload();
}
const deleteProductos = async () => {
console.log("entro");
console.log(id);
const res = await fetch("/productos", {
method: 'DELETE',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
id: id
})
})
const response = await res.json();
}
const useStyles = makeStyles({
table: {
minWidth: 700,
},
});
//console.log(plot);
const mystlye = {
minWidth: "50%",
minHeight: 50
};
//
const classes = useStyles();
return (
<div>
<br />
<Grid container spacing={3}>
<Grid item xs={3}></Grid>
<Grid item xs={3}></Grid>
<Grid item xs={3}></Grid>
<Grid item xs={3}>
<InsertarProductos productoInsert="fer"/>
</Grid>
</Grid>
<br />
<Table className={classes.table}>
<TableHead>
<TableRow>
<StyledTableCell>ID</StyledTableCell>
<StyledTableCell>Nombre</StyledTableCell>
<StyledTableCell>Precio de Compra</StyledTableCell>
<StyledTableCell>Precio de Venta</StyledTableCell>
<StyledTableCell>Cantidad</StyledTableCell>
<StyledTableCell>Categoria</StyledTableCell>
<StyledTableCell>Extras</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
{productos.map((producto) =>
<TableRow className="data-row">
<StyledTableCell>{producto.id}</StyledTableCell>
<StyledTableCell>{producto.nombre}</StyledTableCell>
<StyledTableCell>{producto.precio_compra}</StyledTableCell>
<StyledTableCell>{producto.precio_venta}</StyledTableCell>
<StyledTableCell>{producto.cantidad}</StyledTableCell>
<StyledTableCell>{producto.categorias_id}</StyledTableCell>
<StyledTableCell>
<Button variant="outlined" onClick={() => editar(producto)}>
<EditIcon />
</Button>
<Button variant="outlined" onClick={() => eliminar(producto)} ><DeleteIcon /> </Button>
</StyledTableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
);
}
export default Productos;
If I try to pass the id inside the button, it starts to print the id multiple times like in a loop.
EditarProductos is never reached and the id isn't passed. Could someone help me fix my problem, please?
PD: In EditarProductos I'm trying to print id like this:
console.log(props.productoEdit);
You can't return (and render) UI elements like this. You can rather set the id of a product to display though. Using the is you can conditionally render EditarProductos into the UI. You'll likely also want/need a way to reset this when the dialog is dismissed/closed.
The following is one way to do this:
function Productos(props) {
const [productos, setProductos] = useState([]);
const [id, setId] = useState(null); // create state variable
...
function editar(producto){
console.log("entro 1");
console.log(producto.id);
setId(producto.id);
}
function onEditarClose() {
setId(null);
}
...
return (
<div>
{id &&
// conditionally render in UI the dialog
<EditarProductos onClose={onEditarClose} productoEdit={id} />
}
<br />
<Grid container spacing={3}>
<Grid item xs={3}></Grid>
<Grid item xs={3}></Grid>
<Grid item xs={3}></Grid>
<Grid item xs={3}>
<InsertarProductos productoInsert="fer"/>
</Grid>
</Grid>
<br />
<Table className={classes.table}>
<TableHead>
<TableRow>
<StyledTableCell>ID</StyledTableCell>
<StyledTableCell>Nombre</StyledTableCell>
<StyledTableCell>Precio de Compra</StyledTableCell>
<StyledTableCell>Precio de Venta</StyledTableCell>
<StyledTableCell>Cantidad</StyledTableCell>
<StyledTableCell>Categoria</StyledTableCell>
<StyledTableCell>Extras</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
{productos.map((producto) =>
<TableRow className="data-row">
<StyledTableCell>{producto.id}</StyledTableCell>
<StyledTableCell>{producto.nombre}</StyledTableCell>
<StyledTableCell>{producto.precio_compra}</StyledTableCell>
<StyledTableCell>{producto.precio_venta}</StyledTableCell>
<StyledTableCell>{producto.cantidad}</StyledTableCell>
<StyledTableCell>{producto.categorias_id}</StyledTableCell>
<StyledTableCell>
<Button variant="outlined" onClick={() => editar(producto)}>
<EditIcon />
</Button>
<Button variant="outlined" onClick={() => eliminar(producto)} ><DeleteIcon /> </Button>
</StyledTableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
);
}