React siblings components communication - javascript

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>

Related

material ui table delete function not working

why is my code showing wrong output for
as the rows has been already populated after adding it with the handletabledata function
My code is having a table and there is a add button where we add users but on the delete function when I am trying to see the rows I am getting only that row and rows above it.
please help.
import React from 'react'
import './server.scss'
import Paper from '#mui/material/Paper';
import Table from '#mui/material/Table';
import TableBody from '#mui/material/TableBody';
import TableCell from '#mui/material/TableCell';
import TableContainer from '#mui/material/TableContainer';
import TableHead from '#mui/material/TableHead';
import TablePagination from '#mui/material/TablePagination';
import TableRow from '#mui/material/TableRow';
import { Button } from '#mui/material';
import DeleteOutlineIcon from '#mui/icons-material/DeleteOutline';
import Dialog from '#mui/material/Dialog';
import DialogActions from '#mui/material/DialogActions';
import DialogContent from '#mui/material/DialogContent';
import DialogContentText from '#mui/material/DialogContentText';
import DialogTitle from '#mui/material/DialogTitle';
import Slide from '#mui/material/Slide';
import PersonAddAltIcon from '#mui/icons-material/PersonAddAlt';
import TextField from '#mui/material/TextField';
import InputLabel from '#mui/material/InputLabel';
import MenuItem from '#mui/material/MenuItem';
import FormControl from '#mui/material/FormControl';
import Select from '#mui/material/Select';
const columns = [
{ id: 'key', label: '#', minWidth: 70 },
{ id: 'user', label: 'User', minWidth: 100 },
{
id: 'signed',
label: 'Last Signed in',
minWidth: 170,
align: 'right',
},
{
id: 'role',
label: 'Role',
minWidth: 170,
align: 'right',
},
{
id: 'icon',
label: '',
minWidth: 170,
align: 'right',
},
];
function createData(user, role, key, icon, signed) {
signed = signed.getTime()
return { key, user, signed, role, icon };
}
//modal code
const Transition = React.forwardRef(function Transition(props, ref) {
return <Slide direction="up" ref={ref} {...props} />;
});
const Server = () => {
//final insertion of data
const [rows, setrow] = React.useState([]);
// console.log(rows)
//pagination
const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(10);
const handleChangePage = (event, newPage) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (event) => {
setRowsPerPage(+event.target.value);
setPage(0);
};
//modal code
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
//setting data for row
const [id, setid] = React.useState(0);
const handletabledata = () => {
setid(id + 1)
let row = [...rows]
row.push(createData(email, role, id, <DeleteOutlineIcon onClick={console.log(rows)}/>, new Date()))
setrow(row)
setemail('')
setrole('')
setOpen(false);
}
//input field
const [email, setemail] = React.useState('');
const handleEmail = (event) => {
setemail(event.target.value);
};
//dropdown list
const [role, setrole] = React.useState('');
const handleChange = (event) => {
setrole(event.target.value);
};
return (
<div className='server_main'>
<div className='addUser'>
<Button variant="contained" onClick={handleClickOpen}>Add User</Button>
<Dialog
open={open}
TransitionComponent={Transition}
keepMounted
onClose={handleClose}
aria-describedby="alert-dialog-slide-description"
fullWidth={true}
>
<DialogTitle>{"Add User"}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-slide-description">
<div style={{ display: 'flex', height: '250px', justifyContent: 'space-around' }}>
<div >
<PersonAddAltIcon style={{ fontSize: '7rem', position: 'relative', top: '50px' }} />
</div>
<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'space-around' }}>
<span style={{ padding: '10px', position: 'relative', left: '10px', top: '10px' }}>User Information</span>
<TextField placeholder="Email Address" onChange={handleEmail} value={email} />
<FormControl fullWidth>
<InputLabel id="demo-simple-select-label">Role</InputLabel>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={role}
label="role"
onChange={handleChange}
>
<MenuItem value={'Owner'}>Owner</MenuItem>
<MenuItem value={'Sales'}>Sales</MenuItem>
<MenuItem value={'Admin'}>Admin</MenuItem>
</Select>
</FormControl>
</div>
</div>
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<Button onClick={handletabledata}>Add</Button>
</DialogActions>
</Dialog>
</div>
<Paper sx={{ width: '100%', overflow: 'hidden' }}>
<TableContainer sx={{ maxHeight: 440 }}>
<Table stickyHeader aria-label="sticky table">
<TableHead>
<TableRow>
{columns.map((column) => (
<TableCell
key={column.id}
align={column.align}
style={{ minWidth: column.minWidth }}
>
{column.label}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{rows
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((row) => {
return (
<TableRow hover role="checkbox" tabIndex={-1} key={row.code}>
{columns.map((column) => {
const value = row[column.id];
return (
<TableCell key={column.id} align={column.align}>
{column.format && typeof value === 'number'
? column.format(value)
: value}
</TableCell>
);
})}
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={[10, 25, 100]}
component="div"
count={rows.length}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</Paper>
</div>
);
}
export default Server

How to pass the search query to table filter between different JS files?

I have a datagrid table in which I'm getting my database data from an API call and I have written the table code in one file. I also have a search functionality where you can search for a particular record inside the table, but this search code is in another file. I am having difficulty of passing my state variable containing the search parameter from my search file to the table file. I have separated all my components in different pages since it'd be easier to structure them using a grid in my App.js. How do I get my search query to my table file next?
My search code:
export default function SearchInput() {
const [searchTerm, setSearchTerm] = React.useState('');
return (
<Grid item xs={3}>
<Box mt={1.6}
component="form"
sx={{
'& > :not(style)': { m: 1, width: '20ch', backgroundColor: "white", borderRadius: 1},
}}
noValidate
autoComplete="off"
>
<TextField
placeholder="Search Customer ID"
variant="outlined"
size="small"
sx={{input: {textAlign: "left"}}}
onChange={(event) => {
setSearchTerm(event.target.value);
console.log(searchTerm);
}}
/>
</Box>
</Grid>
);
}
My table code:
export default function DataTable() {
const [pageSize, setPageSize] = React.useState(10);
const [data, setData] = React.useState([]);
useEffect(async () => {
setData(await getData());
}, [])
return (
<div style={{ width: '100%' }}>
<DataGrid
rows={data}
columns={columns}
checkboxSelection={true}
autoHeight={true}
density='compact'
rowHeight='40'
headerHeight={80}
disableColumnMenu={true}
disableSelectionOnClick={true}
sx={datagridSx}
pageSize={pageSize}
onPageSizeChange={(newPageSize) => setPageSize(newPageSize)}
rowsPerPageOptions={[5, 10, 15]}
pagination
/>
</div>
);
}
App.js
function App() {
return (
<div className="App">
<Container maxWidth="false" disableGutters="true">
<Grid container spacing={0}>
<ABClogo />
<HHHlogo />
</Grid>
<Grid container spacing={0}>
<LeftButtonGroup />
<SearchInput />
<RightButtonGroup />
</Grid>
<Grid container spacing={0}>
<DataTable />
<TableFooter />
</Grid>
</Container>
</div>
);
}
Here is a minimal example using createContext(), and useReducer() to lift up state and share it between components, similar to what you are after, but as jsNoob says, there are multiple options. This is one I'm comfortable with.
The concept is explained here: https://reactjs.org/docs/context.html
Essentially you can create 'global' state at any point in your component tree and using Provider / Consumer components, share that state and functionality with child components.
//Main.js
import React, { createContext, useContext, useReducer } from 'react';
const MainContext = createContext();
export const useMainContext => {
return useContext(MainContext);
}
const mainReducer = (state, action) => {
switch(action.type){
case: 'SOMETHING':{
return({...state, something: action.data});
}
default:
return state;
}
}
export const Main = () => {
const [mainState, mainDispatch] = useReducer(mainReducer, {something: false});
const stateOfMain = { mainState, mainDispatch };
return(
<MainContext.Provider value={stateOfMain}>
<MainContext.Consumer>
{() => (
<div>
<Nothing />
<Whatever />
</div>
)}
</MainContext.Consumer>
</MainContext.Provider>
)
}
Then you can have the other components use either or both of the main state and dispatch.
//Nothing.js
import {mainContext} from './Main.js'
const Nothing = () => {
const { mainState, mainDispatch } = useMainContext();
return(
<button onClick={() => {mainDispatch({type: 'SOMETHING', data: !mainState.something})}}></button>
)
}
Clicking the button in the above file, should change the display of the below file
//Whatever.js
import {mainContext} from './Main.js'
const Whatever = () => {
const { mainState } = useMainContext();
return(
<div>{mainState.something}</div>
);
}

React Material UI pagination

Requirement In Material UI, using pagination by click on the digits e.g 1, 2, it should do api call with limit=10 and offset=from 10(after the first call)
**issue ** it working but I need to click two times on the digits, but functionality is working properly as it should.
By clicking on the digits or left/right arrow of pagination, calling handlePageChange method and setting offset state value and putted the offset as useEffect() dependency array so on every change it should make api call with new offset value
/*eslint-disable*/
import { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import PerfectScrollbar from 'react-perfect-scrollbar';
import DeleteIcon from '#material-ui/icons/Delete';
import DescriptionIcon from '#material-ui/icons/Description';
import { useNavigate } from 'react-router-dom';
import Pagination from '#material-ui/lab/Pagination';
import {
Box,
Card,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
createTheme,
Tooltip,
IconButton
} from '#material-ui/core';
import usersServices from 'src/services/usersServices';
const CustomerListResults = (props) => {
const navigate = useNavigate();
const handleExportData = (value) => {
console.log('Value inside handleExportData: ', value);
navigate('/app/exports_device_data', { state: value });
};
const handleDeleteUser = (user) => {
const userId = user._id;
console.log('DELETE USER', userId);
const newUsers = props.users.filter((item) => item._id !== userId);
props.newUsers(newUsers, userId);
};
// --------------------------------Pagination-------------------------------
let limit = 10;
const [users, setUsers] = useState([]);
const [page, setPage] = useState(0);
const [count, setCount] = useState(0);
const [offSet, setOffSet] = useState(0);
const retrieveCustomers = async () => {
if (!count) {
const res = await usersServices.getAllUsersRecordCount();
let dataCnt = res.data.data;
setCount(Math.ceil(dataCnt / 10));
}
console.log('CHEKCING useEffect(): ');
const res = await usersServices.getAllUsers(limit, offSet);
setUsers(res.data.data);
};
const handlePageChange = (event, value) => {
setPage(value);
setOffSet(page * 10);
};
useEffect(retrieveCustomers, [offSet]);
console.log('CHECKING FOR offSet Value: ', offSet);
return (
<Card>
<PerfectScrollbar>
<Box sx={{ minWidth: 64 }}>
<Table>
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell>Email</TableCell>
<TableCell>Phone</TableCell>
<TableCell>Type</TableCell>
<TableCell>Status</TableCell>
{/* <TableCell>Data Count</TableCell> */}
<TableCell>Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{users.map((user) => (
<TableRow hover key={user.email}>
<TableCell>{`${user.first_name} ${user.last_name}`}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>{user.phone_number}</TableCell>
<TableCell>{user.user_type}</TableCell>
<TableCell>{user.status}</TableCell>
{/* <TableCell>{user.device_data_count}</TableCell> */}
<TableCell>
<Tooltip title="Delete">
<IconButton
aria-label="delete"
onClick={() => handleDeleteUser(user)}
>
<DeleteIcon />
</IconButton>
</Tooltip>
<Tooltip title="Export">
<IconButton
aria-label="export data"
onClick={() => handleExportData(user)}
>
<DescriptionIcon />
</IconButton>
</Tooltip>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Box>
</PerfectScrollbar>
<Pagination
component="div"
count={count}
page={page}
siblingCount={1}
boundaryCount={1}
variant="outlined"
// shape="rounded"
onChange={handlePageChange}
/>
</Card>
);
};
CustomerListResults.propTypes = {
users: PropTypes.array.isRequired
};
export default CustomerListResults;
Please help me with this, Thanks in advance
Try this:
const [page, setPage] = useState(1);

using useparams and array.find to display detailed data

I am making a small blog application using react js. I have a context api for the user inputs, so that the data can be used globally across components (InputContext.js). Using react router, the user is able to view a list of all blog entries (AllBlogs.js) and view each one of them in detail (BlogDetail.js). What I am trying to achieve is, allow the user to get a detailed view of an individual blog post component from the AllBlogs.js page. All blogs have an "id" property, which is used to query the url and using the array.find method, it is supposed to show a detailed view of the blog with the matching id. The problem is "findBlogs" in BlogDetails that is being passed as a prop to display the detailed individual blog data always only returns the most recent user input value, therefore all blogs show the exact same information. I am unsure as to why this is happening, any guidance towards the right direction is greatly appreciated.
InputContext.js
import React, { useState, createContext, useMemo } from 'react'
//create context
export const InputContext = createContext();
const InputContextProvider = (props) => {
const [blogPost, setBlogPost] = useState({
id: '',
title: '',
author: '',
text: ''
});
//create an array to push all the blogPosts
const [allBlogPosts, setAllBlogPosts] = useState([]);
console.log(allBlogPosts)
//put value inside useMemo so that the component only rerenders when there is change in the value
const value = useMemo(() => ({ blogPost, setBlogPost, allBlogPosts, setAllBlogPosts }), [blogPost, allBlogPosts])
return (
<InputContext.Provider value={value}>
{props.children}
</InputContext.Provider>
)
}
export default InputContextProvider;
WriteBlogPost.js
import React, { useState, useContext, Fragment } from 'react'
import { useHistory } from 'react-router-dom'
import { InputContext } from '../Contexts/InputContext'
import { TextareaAutosize } from '#material-ui/core'
import { v4 as uuidv4 } from 'uuid';
import { Box, TextField, Button, makeStyles } from '#material-ui/core'
const useStyles = makeStyles({
root: {
justifyContent: 'center',
alignItems: 'center',
textAlign: 'center'
}
})
export const WriteBlogPost = () => {
const classes = useStyles();
const [blog, setBlog] = useState({
id: '',
title: '',
author: '',
text: ''
});
const history = useHistory();
const { setBlogPost } = useContext(InputContext);
const { allBlogPosts, setAllBlogPosts } = useContext(InputContext)
const handleBlogPost = () => {
setBlogPost(blog);
setAllBlogPosts([...allBlogPosts, blog]);
history.push("/blogs")
console.log({ blog })
console.log({ allBlogPosts })
}
const handleChange = (e) => {
const value = e.target.value
setBlog({
...blog,
id: uuidv4(),
[e.target.name]: value
})
}
return (
<Fragment>
<Box className={classes.root}>
<div>
<TextField id="standard-basic" onChange={handleChange} value={blog.title} name="title" label="Title" />
</div>
<div>
<TextField id="standard-basic" onChange={handleChange} value={blog.author} name="author" label="Author" />
</div>
<div>
<TextareaAutosize aria-label="minimum height" minRows={20} style={{ width: '70%' }} placeholder="Your blog post"
onChange={handleChange}
value={blog.text}
name="text" />
</div>
<div>
<Button variant="contained" color="primary" onClick={handleBlogPost}>
Submit</Button>
</div>
</Box>
</Fragment>
)
}
AllBlogs.js
import React, { useContext } from 'react'
import { InputContext } from '../Contexts/InputContext'
import { Card, CardContent, Typography } from '#material-ui/core'
import { makeStyles } from '#material-ui/core'
import { Link } from 'react-router-dom'
const useStyles = makeStyles({
root: {
justifyContent: 'center',
alignItems: 'center',
display: 'flex',
textAlign: 'center',
},
text: {
textAlign: 'center'
}
})
export const AllBlogs = () => {
const classes = useStyles();
const { allBlogPosts, blogPost } = useContext(InputContext)
console.log(allBlogPosts)
return (
<div>
<Typography color="textPrimary" variant="h3" className={classes.text}>All blogs</Typography>
{allBlogPosts.map((post, i) =>
<Card variant="outlined" key={i} className={classes.root}>
<CardContent>
<Typography color="textPrimary" variant="h5">
{post.title}
</Typography>
<Typography color="textPrimary" variant="h6">
{post.author}
</Typography>
<Typography color="textPrimary" variant="body2" component="p">
{post.text}
</Typography>
<Link to={`/blogs/${blogPost.id}`}>
Read blog
</Link>
</CardContent>
</Card>
)}
</div>
)
}
BlogDetail.js
import React, { useContext } from 'react'
import { useParams, Route } from 'react-router'
import { SingleBlog } from './SingleBlog';
import { InputContext } from '../Contexts/InputContext';
export const BlogDetail = () => {
const params = useParams();
console.log(params.blogId)
const { allBlogPosts } = useContext(InputContext)
const findBlog = allBlogPosts.find((post) => post.id === params.blogId)
console.log(findBlog)
if (!findBlog) {
return <p>No blogs found.</p>
}
return (
<div>
<h1>Blog details</h1>
<SingleBlog post={findBlog} />
</div>
)
}
Issue
Ah, I see what is happening... had to dig back through your edits to when you included your context code.
In your provider you for some reason store an array of blogs (this part makes sense), but then you also store the last blog that was edited.
const InputContextProvider = (props) => {
const [blogPost, setBlogPost] = useState({
id: '',
title: '',
author: '',
text: ''
});
//create an array to push all the blogPosts
const [allBlogPosts, setAllBlogPosts] = useState([]);
//put value inside useMemo so that the component only rerenders when there is change in the value
const value = useMemo(() => ({
blogPost, // <-- last blog edited
setBlogPost,
allBlogPosts,
setAllBlogPosts
}), [blogPost, allBlogPosts])
return (
<InputContext.Provider value={value}>
{props.children}
</InputContext.Provider>
)
}
export const WriteBlogPost = () => {
...
const [blog, setBlog] = useState({
id: '',
title: '',
author: '',
text: ''
});
...
const { setBlogPost } = useContext(InputContext);
const { allBlogPosts, setAllBlogPosts } = useContext(InputContext)
const handleBlogPost = () => {
setBlogPost(blog); // <-- saves last blog edited/added
setAllBlogPosts([...allBlogPosts, blog]);
history.push("/blogs");
}
const handleChange = (e) => {
const value = e.target.value
setBlog({
...blog,
id: uuidv4(),
[e.target.name]: value
})
}
return (
...
)
}
When you are mapping the blog posts you form incorrect links.
export const AllBlogs = () => {
const classes = useStyles();
const {
allBlogPosts,
blogPost // <-- last blog updated
} = useContext(InputContext);
return (
<div>
...
{allBlogPosts.map((post, i) =>
<Card variant="outlined" key={i} className={classes.root}>
<CardContent>
...
<Link to={`/blogs/${blogPost.id}`}> // <-- link last blog updated id
Read blog
</Link>
</CardContent>
</Card>
)}
</div>
)
}
Solution
Use the current blog post's id when mapping to form the link correctly.
export const AllBlogs = () => {
const classes = useStyles();
const { allBlogPosts } = useContext(InputContext);
return (
<div>
...
{allBlogPosts.map((post, i) =>
<Card variant="outlined" key={post.id} className={classes.root}>
<CardContent>
...
<Link to={`/blogs/${post.id}`}> // <-- link current post id
Read blog
</Link>
</CardContent>
</Card>
)}
</div>
)
}

How to display dynamic data in Material-UI table?

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:

Categories