Custom Pagination in Material-Table - javascript

I want to override the default pagination in Material-Table to look different, but keep the same default elements there. My override allows me to move between pages in the table, but I cannot change how many rows appear in the table. I cannot skip to the last or first page either. I want to keep the exact same options as what is there by default, but change how they look.
All of my Icon, Grid, Typography, etc, imports are #material-ui/core/ or #material-ui/icons/
function IntakeList() {
const CustomPaginationComponent = (props) => {
const { page, rowsPerPage, count, onChangePage } = props;
let from = rowsPerPage * page + 1;
let to = rowsPerPage * (page + 1);
if (to > count) {
to = count;
}
return (
<td>
<Grid container alignItems="center" style={{ paddingTop: 8 }}>
<Grid item>
<IconButton disabled={page === 0} onClick={(e) => onChangePage(e, page - 1)}>
<SkipPreviousIcon fontSize="small" color={page === 0 ? "disabled" : "primary"} />
<Typography>Prev</Typography>
</IconButton>
</Grid>
<Grid item>
<Typography variant="caption" style={{ color: "black" }}>
{from}-{to} of {count}
</Typography>
</Grid>
<Grid item>
<IconButton disabled={to >= count} onClick={(e) => onChangePage(e, page + 1)}>
<Typography>Next</Typography>
<SkipNextIcon fontSize="small" color={to < count ? "primary" : "disabled"} />
</IconButton>
</Grid>
</Grid>
</td>
);
};
return (
<MaterialTable
title="Title"
data={data}
columns={columns}
options={{
pageSize: 10,
pageSizeOptions: [10, 15, 25, 50, 100],
}}
components={{
Pagination: (props) => {
return <CustomPaginationComponent {...props} />;
},
}}
/>
);
}

What you have done so far is right. You just simply have to code for the "skip to last page", "skip to first page" and "select row options" just like you did for "next" and "previous" page.
https://codesandbox.io/s/solution-q1cmer?file=/src/App.js
When you console log the props of pagination you will get this
Using the rowsPerPageOptions , onChangeRowsPerPage and some of the code you have coded I was able to code the number of rows per page.
To be honest I don't think you have to code to next page and previous page functionality. It's already in props. You simply has to declare them in the right place.
Happy Coding

Related

Close parent component

I have a code with which the user can select a file from their device. The Card component will display its name and operations that can be done with the file.
But my problem is that I don't know how to close this component if the user wants to cancel the action.
export default function DisplaySelectedFile() {
const [fileName, setFileName] = useState("");
console.log(setFileName)
return (
<div>
<SelectFileButton setFileName={setFileName} />
{fileName && <Card sx={styles.CommonStyle}>
<Stack spacing={10} direction="row" style={{paddingTop: "20px", paddingLeft: "10px"}}>
<div>{fileName}</div>
<Stack spacing={3} direction="row">
<div>Convert to</div>
<ConvertToFormatFile></ConvertToFormatFile>
</Stack>
<Button>CONVERT</Button>
<CloseIcon/>
</Stack>
</Card>}
</div>
);
}
I have added a button which should close the entire Card component. If I add the following code
<CloseIcon onClick={() => setFileName(false)}/>
If I add the following code, then the component closes. But the next time you select a file, this component does not open (only after reloading the page).
Tell me how to close the Card component correctly so that you can open it without problems
I would suggest to handle separately the card visibility and the file name value.
Something like this should work:
import React, { useState, useCallback } from "react";
const DisplaySelectedFile = () => {
const [fileName, setFileName] = useState(null);
const [showCard, setShowCard] = useState(false);
const handleSelectFile = useCallback(
(file) => {
setFileName(file);
file && setShowCard(true);
},
[setFileName, setShowCard]
);
const handleCloseCard = useCallback(() => {
setShowCard(false);
setFileName(null); // add this line only if it fits your use case
}, [setFileName, setShowCard]);
return (
<div>
<SelectFileButton setFileName={handleSelectFile} />
{showCard && (
<Card sx={styles.CommonStyle}>
<Stack
spacing={10}
direction="row"
style={{ paddingTop: "20px", paddingLeft: "10px" }}
>
<div>{fileName}</div>
<Stack spacing={3} direction="row">
<div>Convert to</div>
<ConvertToFormatFile></ConvertToFormatFile>
</Stack>
<Button>CONVERT</Button>
<CloseIcon onClick={handleCloseCard} />
</Stack>
</Card>
) || null}
</div>
);
}
export default DisplaySelectedFile;

Having Issues with Components Not Rendering or Re-Rendering Too Many Times on State Change

We're working on an inventory management page of sorts. The page makes a call to an external API and if that endpoint returns data, it displays that data on the page for the user. The API call uses the current logged in users ID, so we use userLoaded to wait for that ID before making the API call.
If an inventory doesn't exist (API returns nothing), we show some "Not Available" text.
However, when a page initially loads, it's showing the "Not Available" text, re-rendering a couple times, and then finally showing the correct data. Is there anyway to cut back on those re-renders or make it more graceful?
You can see the issue here: https://recordit.co/EYDLupg3xs or on the demo link here: https://showzone.io/inventory
Oddly enough, the table component is not re-rendering with the data - so it stays blank. But if you force the table to re-render (by using one of the Filters for example), the data does indeed show. How can we re-render the table component when the data is ready?
Here is the code (cut down a bit to make it easier to understand):
function Inventory() {
const { currentUser, userLoaded } = useAuth()
const [data, setData] = useState([])
const [inventoryExists, setInventoryExists] = useState(false)
const fetchData = useCallback(async () => {
if (userLoaded) {
const userInventoryData = await axios.get(
`https://showzone-api.herokuapp.com/api/user-inventory/${currentUser.uid}` // For the demo link, I hardcoded a value for testing purposes
)
setData(userInventoryData.data)
setInventoryExists(true)
}
}, [currentUser?.uid, userLoaded])
useEffect(fetchData, [fetchData])
return (
<>
.....
<TabPanel value="1" sx={{ padding: 0 }}>
<Card mb={6}>
<CardContent>
<Grid container spacing={6}>
<Grid item xs={12}>
<Typography>
<strong>Total Cards: </strong>{" "}
{inventoryExists
? data?.owned_count?.toLocaleString() +
" / " +
data?.total_card_count?.toLocaleString()
: "Not Available"}
</Typography>
<Typography>
<strong>Estimated Value: </strong>{" "}
{inventoryExists
? data?.owned_value?.toLocaleString()
: "Not Available"}
</Typography>
</Grid>
<Grid item xs={12} md={6} sx={{ display: "flex" }}>
{inventoryExists ? (
<div>
<Doughnut
data={{
datasets: [
{
data: [
Math.round(data?.owned_count),
Math.round(data?.total_card_count),
],
},
],
}}
/>
</div>
) : (
<BlankDoughnut text="Cards" />
)}
</Grid>
</Grid>
</CardContent>
</Card>
</TabPanel>
<Divider />
{inventoryExists ? (
<PlayerSearch id={currentUser?.uid} />
) : (
<PlayerSearch />
)}
</>
)
}

Chart Data shows it's never updated through my setState variable

I have a popup dialog where I get a bunch of values from the user and then get a response after making an API request. I put an inline conditional rendering on the dialog box as it should only render once chart data is updated from the response. However, the dialog never appears even if console.log shows the data is updated. I tried to use useEffect() with many functions but it did not work. Any idea how to refresh the data again?
Edit: Added only relevant code
const [barGraphData, setBarGraphData] = useState([]);
const funcSetBarGraphData = (newBarGraphData) => {
setBarGraphData(newBarGraphData);
};
const sendChartData = async () => {
let bar_response = await axios.post(
"http://localhost:8080/h2h-backend/bardata",
bar_data,
{headers: {'Content-Type': 'application/json'}}
).then(res=>{
const resData = res.data;
const resSubstring = "[" + resData.substring(
resData.indexOf("[") + 1,
resData.indexOf("]")
) + "]";
const resJson = JSON.parse(resSubstring);
console.log(typeof resJson, resJson);
funcSetBarGraphData(barGraphData);
}).catch(err=>{
console.log(err);
});
chartClickOpen();
};
Returning popup dialog with charts when button is clicked:
<StyledBottomButton onClick={sendChartData}>Submit</StyledBottomButton>
{barGraphData.length > 0 && <Dialog
fullScreen
open={openChart}
onClose={chartClickClose}
TransitionComponent={Transition}
>
<AppBar sx={{ position: 'relative' }}>
<Toolbar>
<Typography sx={{ ml: 2, flex: 1 }} variant="h6" component="div">
Analytics View
</Typography>
<IconButton
edge="start"
color="inherit"
onClick={chartClickClose}
aria-label="close"
>
<CloseIcon />
</IconButton>
</Toolbar>
</AppBar>
<Grid container spacing={2}>
<Grid item xs={8} sx={{ pt: 2 }}>
<BarChart width={730} height={250} data={barGraphData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="business_name" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="num_of_customers" fill="#8884d8" />
<Bar dataKey="sum_total_amount" fill="#82ca9d" />
</BarChart>
{/* <Bar options={set_bar.bar_options} data={set_bar.bar_data} redraw={true}/> */}
</Grid>
<Grid item xs={4} sx={{ pt: 2 }}>
{/* <Pie data={data2} /> */}
</Grid>
</Grid>
</Dialog>}
<StyledBottomButton onClick={handleClose}>Cancel</StyledBottomButton>

Map is returning the items of an array multiple times

I have an array of object which contains images:
import React, { Fragment } from "react"
import { useStaticQuery, graphql } from "gatsby"
import Button from "#material-ui/core/Button"
import Card from "#material-ui/core/Card"
import CardActions from "#material-ui/core/CardActions"
import CardContent from "#material-ui/core/CardContent"
import CardMedia from "#material-ui/core/CardMedia"
import { CardActionArea, GridList } from "#material-ui/core"
import CssBaseline from "#material-ui/core/CssBaseline"
import Grid from "#material-ui/core/Grid"
import Toolbar from "#material-ui/core/Toolbar"
import Typography from "#material-ui/core/Typography"
import Container from "#material-ui/core/Container"
import Link from "#material-ui/core/Link"
import homePageStyles from "../ui/styles/homePageStyles"
import mobile from "../../images/mobileApps.jpeg"
import coporation from "../../images/coporations.png"
import statute from "../../images/gavel.png"
import policy from "../../images/policies.png"
export default function HeroBlock() {
const classes = homePageStyles()
const data = useStaticQuery(graphql`
query getCardCategoriesAndCounts {
allStrapiCategory {
edges {
node {
category
strapiId
}
}
}
allStrapiMobileApplication {
totalCount
}
allStrapiCorporation {
totalCount
}
allStrapiStatutoryLaw {
totalCount
}
allStrapiPolicyIssue {
totalCount
}
}
`)
const categories = data.allStrapiCategory.edges
const categoryImages = [
{ cImg: mobile },
{ cImg: coporation },
{ cImg: statute },
{ cImg: policy },
]
console.log(categoryImages)
const catImage = categoryImages.map(categoryImage => {
return (
<Fragment>
<CardMedia
image={categoryImage.cImg}
className={classes.images}
></CardMedia>
</Fragment>
)
})
const cards = (
<Fragment>
{categories.map(c => (
<Card style={{ width: "15.625rem", wordWrap: "break-word" }}>
<div>{catImage}</div>
<CardActionArea>
<CardContent>
<Typography variant={"h6"}>
{c.node.category.toUpperCase().replace(/_/g, " ")}
</Typography>
<Fragment>
{c.node.category === "Mobile_Apps" ? (
<Typography variant="paragraph">
{`There are currently ${data.allStrapiMobileApplication.totalCount} mobile applications.`}
</Typography>
) : c.node.category === "Corporations" ? (
<Typography variant="paragraph">
{`There are currently ${data.allStrapiCorporation.totalCount} coporations.`}
</Typography>
) : c.node.category === "Statutes" ? (
<Typography variant="paragraph">
{`There are currently ${data.allStrapiStatutoryLaw.totalCount} statutory laws.`}
</Typography>
) : c.node.category === "Policy_Issues" ? (
<Typography variant="paragraph">
{`There are currently ${data.allStrapiPolicyIssue.totalCount} policy issues.`}
</Typography>
) : (
<div></div>
)}
</Fragment>
</CardContent>
</CardActionArea>
</Card>
))}
</Fragment>
)
return (
<React.Fragment>
<CssBaseline />
<main>
<div className={classes.heroContent}>
<Grid container>
<Grid item classes={{ root: classes.heroColumn }}>
<Grid container>
<Grid
item
container
direction="column"
classes={{ root: classes.hero }}
>
<Typography component="h1" variant="h2" align="center">
MIKE <br /> The Mobile Information Knowledge Ecosystem
</Typography>
<Typography variant="h5" align="center" paragraph>
<strong>
<em>The</em>{" "}
</strong>
central repository for how to extract data from a mobile
device, extract data from a mobile application, what data
the mobile app provider retains, and the specific laws
pertaining to accessing that data.
</Typography>
</Grid>
</Grid>
</Grid>
</Grid>
</div>
<Grid Container>
<Grid item container justifyContent="space-evenly">
{cards}
</Grid>
</Grid>
</main>
</React.Fragment>
)
}
Updated with the entire code base. There is multiple map statements. If that is the issue how do you map multiple arrays inside React? Does that require using some sort of data store? It is returning each one of the images 4 times, rather than each images per card.
You generate an array of card media components, and then render this within another loop, so this is why you see the 4 category images repeated for each category mapped later.
const catImage = categoryImages.map(categoryImage => { // array
return (
<Fragment>
<CardMedia
image={categoryImage.cImg}
className={classes.images}
></CardMedia>
</Fragment>
)
})
const cards = (
<Fragment>
{categories.map(c => (
<Card style={{ width: "15.625rem", wordWrap: "break-word" }}>
<div>{catImage}</div> // <-- render array of images here!!
<CardActionArea>
...
Assuming the categoryImages and categories arrays have the same length and sorted order, then you can render the card media component directly in the mapping and use the current index to access the correct image from the categoryImages array.
const cards = (
<Fragment>
{categories.map((c, index) => ( // <-- use current index
<Card style={{ width: "15.625rem", wordWrap: "break-word" }}>
<CardMedia
image={categoryImages[index].cImg} // <-- access by index
className={classes.images}
/>
<CardActionArea>
...
Array.map returns array. So catImage is an array that contains components.You can use each CardMedia component by indicating index number.
catImage[index]
catImage[0] is the first card.
So you can update your code like below:
const cards = ( // <==== line 74
<Fragment>
{categories.map((c, index) => (
<Card style={{ width: "15.625rem", wordWrap: "break-word" }}>
<div>{catImage[index]}</div>
I hope my answer will be solve your problem.
Thanks.

React Hook useState value being reset to initial value

The state of a value set using React useState hook gets set to the proper value and then reset to null. Critical code below. The click event that sets the startDate to the current date and time is 3 components down from where startDate is initialized. When setStartDate did not work I created an arrow function, updateStartDate. Both had the same problem where the startDate was changed after the click event (witnessed per the console.log in the top component), but was null just before the next click event (per the console.log in the click event). This is not an async problem as I see the change made before subsequent click.
If this is something that just does not work please explain. I could probably fix with useReducer but prefer to keep the useState if there is something I can do to correct this... If not correctable then I would like to at least understand why it does not work so that I can avoid this problem in the future.
export const DisplayTicTacToeContainer = (props) => {
const [startDate, setStartDate]= useState();
const updateStartDate = (newDate) => {
setStartDate(newDate);
}
useEffect (() => {
setStartDate(null);
}, []);
useEffect(() => {
console.log( "displayTicTacToeContainer useEffect for change of startDate = ", startDate)
}, [startDate]);
return (
<DisplayTicTacToeMatch arrayOfMatchingItems ={arrayOfMatchingItems}
startDate={startDate}
setStartDate={setStartDate}
updateStartDate={updateStartDate}
/>);
}
//-----------------------------------------------
export const DisplayTicTacToeMatch = (props) => {
const { startDate,
setStartDate,
updateStartDate,
} = props;
useEffect(() => {
// Performs some prep and working fine.
}, []);
return (
<TicTacToe
startDate={startDate}
setStartDate={setStartDate}
updateStartDate={updateStartDate}
/>
);
}
//-----------------------------------------------
const TicTacToeContainer = (props) => {
const { startDate,
setStartDate,
updateStartDate,
} = props;
const [board, setBoard] = useState(<Board
updateStartDate={updateStartDate}
startDate={startDate}
setStartDate={setStartDate}/>);
return (
<Board/>
)
}
export default TicTacToeContainer;
I renamed the component to BoardComponent and the state variable to boardLayout. I included the full return portion of the BoardComponent below.
As I am still experiencing the problem I would agree with you that, "DisplayTicTacToeContainer is being mounted twice". Any thoughts on how I can avoid this from happening?
Other than this inability to setStartDate, everything is working fine.
//-----------------------------------------------
const Board = (props) => {
const { updateStartDate,
startDate,
setStartDate,
} = props;
return (
<>
<Grid container maxwidth="lg" alignItems="center" spacing={1}>
<Grid item xs={9}>
<Grid container alignItems="center">
<Grid item xs={9}>
<Typography variant = "body1">
First select a square. Once the "Inquiry" word or phrase appears below, find
the correct response in the column on the right and select that buttton. A correct
response will fill the square previously selected with an "O" or "X".
</Typography>
<div style={{ width: '100%' }}>
<Box
display="flex"
flexWrap="wrap"
p={1}
m={1}
bgcolor="background.paper"
css={{ maxWidth: 900 }}
>
<Box p={1} bgcolor="grey.300">
Inquiry : {inquiry}
</Box>
</Box>
<Box
display="flex"
flexWrap="wrap"
p={1}
m={1}
bgcolor="background.paper"
css={{ maxWidth: 900 }}
>
<Box p={1} bgcolor="grey.300">
Next move by : {currentPlayer}
</Box>
<Box p={1} bgcolor="grey.300">
{showStatus}
</Box>
</Box>
</div>
</Grid>
</Grid>
<MyAux>
{boardLayout.map((row, rowId) => {
const columns = row.map((column, columnId) => (
<Grid key={columnId} item>
<ButtonBase >
<Paper
onClick={(e) => {
clickSquareHandler(e);
}}
elevation={4}
data-coord={rowId + ':' + columnId}
id={"Square" + rowId.toString() + columnId.toString()}
className={classes.Paper}>
<Icon
className={classes.Icon}
style={{fontSize: 78}}>
</Icon>
</Paper>
</ButtonBase>
</Grid>
));
return (
<Grid
key={rowId}
className={classes.Grid}
container
spacing={2}>
{columns}
</Grid>)
})}
</MyAux>
</Grid>
<Grid item xs={3} >
<Paper className={classes.paper}>
<Typography variant = "body1">
Response Options
</Typography>
<ButtonGroup
orientation="vertical"
color="secondary"
aria-label="vertical outlined secondary button group"
>
{responseChoices.map((choice) => (
<Controls.Button
key ={choice.value}
text={choice.value}
variant="contained"
color = "secondary"
onClick={() => {
chooseChecker(choice);
}}
className={
response && response.value === choice.value ? "selected" : ""
}
disabled={!!selected[choice.value]}
fullWidth = "true"
size = "small"
/>
))}
</ButtonGroup>
</Paper>
</Grid>
</Grid>
</>
)
}
BoardContainer.propTypes = {
won: PropTypes.func,
size: PropTypes.number
};
export default BoardContainer;
At least, code below doesn't make much sense.
Please don't set state value as a component.
Also, try to name state variable different from components, since it will confuse you at some ppint.
const [board, setBoard] = useState(<Board
updateStartDate={updateStartDate}
startDate={startDate}
setStartDate={setStartDate}/>);
return (
<Board/>
)
Another possibility is that the DisplayTicTacToeContainer is being mounted twice, but I can't confirm it with the code provided.

Categories