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;
Related
I want to move the content from Form.jsx according to the direction in which the drawer will move. When I click on the menu icon, the Drawer will be expanded but the content inside the body cannot be moved according to the drawer. How to make it enable here?
Before the drawer menu gets clicked:
After the drawer menu gets clicked:
Code is below:
Form.jsx
import { styled } from '#mui/material/styles';
import Box from '#mui/material/Box';
const DrawerHeader = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
padding: theme.spacing(0, 1),
...theme.mixins.toolbar,
}));
export default function Form() {
return (
<Box component="main" sx={{ flexGrow: 1, p: 9 }}>
<DrawerHeader />
<TextField id="outlined-basic" label="Outlined" variant="outlined" />
<TextField id="filled-basic" label="Filled" variant="filled" />
<TextField id="standard-basic" label="Standard" variant="standard" />
</Box>
);
};
Drawer.jsx
import * as React from 'react';
import { styled, createTheme, ThemeProvider } from '#mui/material/styles';
import Box from '#mui/material/Box';
import MuiDrawer from '#mui/material/Drawer';
import MuiAppBar from '#mui/material/AppBar';
import Toolbar from '#mui/material/Toolbar';
import List from '#mui/material/List';
import CssBaseline from '#mui/material/CssBaseline';
import Typography from '#mui/material/Typography';
import Divider from '#mui/material/Divider';
import IconButton from '#mui/material/IconButton';
import MenuIcon from '#mui/icons-material/Menu';
import ChevronLeftIcon from '#mui/icons-material/ChevronLeft';
import ChevronRightIcon from '#mui/icons-material/ChevronRight';
import ListItem from '#mui/material/ListItem';
import ListItemButton from '#mui/material/ListItemButton';
import ListItemIcon from '#mui/material/ListItemIcon';
import ListItemText from '#mui/material/ListItemText';
import InboxIcon from '#mui/icons-material/MoveToInbox';
import MailIcon from '#mui/icons-material/Mail';
import { Link } from 'react-router-dom';
const drawerWidth = 240;
const openedMixin = (theme) => ({
width: drawerWidth,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
overflowX: 'hidden',
});
const closedMixin = (theme) => ({
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
overflowX: 'hidden',
width: `calc(${theme.spacing(7)} + 1px)`,
[theme.breakpoints.up('sm')]: {
width: `calc(${theme.spacing(8)} + 1px)`,
},
});
const DrawerHeader = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
padding: theme.spacing(0, 1),
// necessary for content to be below app bar
...theme.mixins.toolbar,
}));
const AppBar = styled(MuiAppBar, {
shouldForwardProp: (prop) => prop !== 'open',
})(({ theme, open }) => ({
zIndex: theme.zIndex.drawer + 1,
background: "fb8500",
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
...(open && {
marginLeft: drawerWidth,
width: `calc(100% - ${drawerWidth}px)`,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
}),
}));
const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })(
({ theme, open }) => ({
width: drawerWidth,
flexShrink: 0,
whiteSpace: 'nowrap',
boxSizing: 'border-box',
...(open && {
...openedMixin(theme),
'& .MuiDrawer-paper': openedMixin(theme),
}),
...(!open && {
...closedMixin(theme),
'& .MuiDrawer-paper': closedMixin(theme),
}),
}),
);
export default function MiniDrawer() {
const PageList = [
{
id: 1,
text: "Add new Donee",
to: "/form",
},
{
id: 2,
text: "Donees",
to: "/view",
},
];
// const theme = useTheme();
const theme = createTheme({
palette: {
primary: {
// Purple and green play nicely together.
main: '#ffca3a',
},
secondary: {
// This is green.A700 as hex.
main: '#11cb5f',
},
},
});
const [open, setOpen] = React.useState(false);
const handleDrawerOpen = () => {
setOpen(true);
};
const handleDrawerClose = () => {
setOpen(false);
};
return (
<ThemeProvider theme={theme}>
<Box sx={{ display: 'flex' }}>
<CssBaseline />
<AppBar position="fixed" open={open}>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
onClick={handleDrawerOpen}
edge="start"
sx={{
marginRight: 5,
...(open && { display: 'none' }),
}}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap component="div">
Compassion Lanka Data Entry
</Typography>
</Toolbar>
</AppBar>
<Drawer variant="permanent" open={open}>
<DrawerHeader>
<IconButton onClick={handleDrawerClose}>
{theme.direction === 'rtl' ? <ChevronRightIcon /> : <ChevronLeftIcon />}
</IconButton>
</DrawerHeader>
<Divider />
<List>
{PageList.map((text, index) => (
<ListItem key={text.id} disablePadding sx={{ display: 'block' }}>
<ListItemButton
sx={{
minHeight: 48,
justifyContent: open ? 'initial' : 'center',
px: 2.5,
}}
component={Link} to={text.to}
>
<ListItemIcon
sx={{
minWidth: 0,
mr: open ? 3 : 'auto',
justifyContent: 'center',
}}
>
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
</ListItemIcon>
<ListItemText primary={text.text} sx={{ opacity: open ? 1 : 0 }} />
</ListItemButton>
</ListItem>
))}
</List>
<Divider />
</Drawer>
</Box>
</ThemeProvider>
);
}
Set margin or padding of <Box component="main"> to the width of the drawer.
I have created a Material-UI persistent drawer in which there is a list item component that aims to change the icon color whenever a user clicks on the list item. But my styling is only working with Material-UI icon, not with external SVG.
Here is codesandbox link for the same project to understand it better.
Here is my AppBarDrawer.js parent component that renders my listItem component. Working fine and can be ignored
import React from "react";
import clsx from "clsx";
import { makeStyles, useTheme } from "#material-ui/core/styles";
import Drawer from "#material-ui/core/Drawer";
import CssBaseline from "#material-ui/core/CssBaseline";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import List from "#material-ui/core/List";
import Typography from "#material-ui/core/Typography";
import Divider from "#material-ui/core/Divider";
import IconButton from "#material-ui/core/IconButton";
import MenuIcon from "#material-ui/icons/Menu";
import ChevronLeftIcon from "#material-ui/icons/ChevronLeft";
import ChevronRightIcon from "#material-ui/icons/ChevronRight";
import ListItem from "#material-ui/core/ListItem";
import ListItemIcon from "#material-ui/core/ListItemIcon";
import ListItemText from "#material-ui/core/ListItemText";
import InboxIcon from "#material-ui/icons/MoveToInbox";
import MailIcon from "#material-ui/icons/Mail";
import DrawerList from "./components/DrawerList";
const drawerWidth = 240;
const useStyles = makeStyles((theme) => ({
root: {
display: "flex"
},
appBar: {
transition: theme.transitions.create(["margin", "width"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
})
},
appBarShift: {
width: `calc(100% - ${drawerWidth}px)`,
marginLeft: drawerWidth,
transition: theme.transitions.create(["margin", "width"], {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen
})
},
menuButton: {
marginRight: theme.spacing(2)
},
hide: {
display: "none"
},
drawer: {
width: drawerWidth,
flexShrink: 0
},
drawerPaper: {
width: drawerWidth
},
drawerHeader: {
display: "flex",
alignItems: "center",
padding: theme.spacing(0, 1),
// necessary for content to be below app bar
...theme.mixins.toolbar,
justifyContent: "flex-end"
},
content: {
flexGrow: 1,
padding: theme.spacing(3),
transition: theme.transitions.create("margin", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
}),
marginLeft: -drawerWidth
},
contentShift: {
transition: theme.transitions.create("margin", {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen
}),
marginLeft: 0
}
}));
export default function PersistentDrawerLeft() {
const classes = useStyles();
const theme = useTheme();
const [open, setOpen] = React.useState(true);
const handleDrawerOpen = () => {
setOpen(true);
};
const handleDrawerClose = () => {
setOpen(false);
};
return (
<div className={classes.root}>
<CssBaseline />
<AppBar
position="fixed"
className={clsx(classes.appBar, {
[classes.appBarShift]: open
})}
>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
onClick={handleDrawerOpen}
edge="start"
className={clsx(classes.menuButton, open && classes.hide)}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap>
Persistent drawer
</Typography>
</Toolbar>
</AppBar>
<Drawer
className={classes.drawer}
variant="persistent"
anchor="left"
open={open}
classes={{
paper: classes.drawerPaper
}}
>
<div className={classes.drawerHeader}>
<IconButton onClick={handleDrawerClose}>
{theme.direction === "ltr" ? (
<ChevronLeftIcon />
) : (
<ChevronRightIcon />
)}
</IconButton>
</div>
<Divider />
<List>
<DrawerList />
</List>
</Drawer>
<main
className={clsx(classes.content, {
[classes.contentShift]: open
})}
>
<div className={classes.drawerHeader} />
<Typography paragraph>
Lorem Nulla posuere sollicitudin aliquam ultrices sagittis orci a
</Typography>
</main>
</div>
);
}
The Main file DrawerList.js which is not giving desired out
Here the real issue is my external icons color is not changing to white whenever I click on it however the last icon named ExitToAppOutlined is a Material-UI icon and is working fine on click.
import React, { useState } from "react";
import ListItem from "#material-ui/core/ListItem";
import Link from "#material-ui/core/Link";
import ListItemIcon from "#material-ui/core/ListItemIcon";
import { ExitToAppOutlined } from "#material-ui/icons";
import ListItemText from "#material-ui/core/ListItemText";
import { useStyles } from "./DrawerListStyle";
import Typography from "#material-ui/core/Typography";
import Box from "#material-ui/core/Box";
import { SvgIcon } from "#material-ui/core";
import { ReactComponent as Appointment } from "../../assets/Appointment.svg";
import { ReactComponent as Customers } from "../../assets/manage customers 2.svg";
const itemList = [
{
text: "Book Appointment",
icon: (
<SvgIcon>
{/* external icons as svg */}
<Appointment />
</SvgIcon>
)
},
{
text: "Manage",
icon: (
<SvgIcon>
{/* external icons as svg */}
<Customers />
</SvgIcon>
)
},
{
text: "Logout",
// Material Icons
icon: <ExitToAppOutlined />
}
];
const DrawerList = () => {
const [selectedIndex, setSelectedIndex] = useState(0);
const classes = useStyles();
const ListData = () =>
itemList.map((item, index) => {
const { text, icon } = item;
return (
<ListItem
button
key={text}
component={Link}
selected={index === selectedIndex}
onClick={(e) => handleListItemClick(e, index)}
style={selectedIndex === index ? { backgroundColor: "#6A2CD8" } : {}}
>
<ListItemIcon
className={classes.iconStyle}
style={selectedIndex === index ? { color: "#fff" } : {}}
>
{icon}
<ListItemText>
<Typography
component="div"
className={classes.iconTitle}
style={selectedIndex === index ? { color: "#fff" } : {}}
>
<Box fontWeight={500} fontSize={13.5}>
{text}
</Box>
</Typography>
</ListItemText>
</ListItemIcon>
</ListItem>
);
});
const handleListItemClick = (e, index) => {
setSelectedIndex(index);
};
return (
<div className={classes.root}>
<ListData />
</div>
);
};
export default DrawerList;
DrawerListStyle.js just an stylejs file and can be ignored
import { makeStyles } from "#material-ui/core";
const useStyles = makeStyles((theme) => ({
root: {
marginTop: theme.spacing(2)
},
iconStyle: {
margin: theme.spacing(0, 0, 1, 0),
color: "#6A2CD8"
},
iconTitle: {
margin: theme.spacing(0, 0, 0, 1),
color: "#555458"
}
}));
export { useStyles };
Material-UI sets the color of your ListItemIcon when the ListItem is selected, but because your custom svg icons already have the fill attribute set to another color, it overrides the color from MUI. The fix is simple, override the fill attribute again in your custom svg using makeStyles:
const useStyles = makeStyles((theme) => ({
{...}
listItem: {
"&.Mui-selected": {
"& path": {
fill: "white"
}
}
}
}));
<ListItem className={classes.listItem}
Live Demo
you can also use SVGR to build your own icons.
you can follow these steps:
1- install svgr using
npm install --save-dev #svgr/cli
# or use yarn
yarn add --dev #svgr/cli
2- add your icons in public/icons
3- create a file named svgr.config.js and place it in the root of project
module.exports = {
icon: true,
dimensions: false,
expandProps: false,
replaceAttrValues: {
'#000': 'currentColor',
'#292D32': 'currentColor',
'#292d32': 'currentColor',
'#55BB9D': 'currentColor',
'#FFBC50': 'currentColor',
'#A7A7A7': 'currentColor'
}
};
4- add script in package.json
"svg": "svgr --ext=jsx -d src/#share/icons public/icons"
5 - run yarn svg.
once you run above command svgr grab all icons in the public folder and gerenate React component inside src/#share/icons folder based on the config you provided.
and replace every color text of svg into current color.
in this case replace every key in replaceAttrValues to the currentColor.
then in your react component you can simply do this:
import { MyIcon } from '#share/icons';
import { SvgIcon } from '#mui/material';
function MyComponent() {
return <p><SvgIcon component={MyIcon} /> check this icon </p>
};
export default MyComponent;
in this way you can change the color the way you change the text color of p element.
I am trying to pass function as prop. I did this before but now with the same logic it is giving me error (this.props.functionName is not a function).
I have a child (Navbar) and a parent component(MainComponent). I want to send a input value from Navbar to MainComponet and set it to the state value in parent Component.
Parent Component
import React ,{Component}from 'react'
import Navbar from '../Presentational/Navbar'
class Main extends Component{
constructor(props){
super(props)
this.state = {
searchItem: ''
}
}
GetSearchItem(search){
this.setState({searchItem:search})
}
render(){
return(
<div className = 'container'>
<div className = 'row'>
<div className = 'col-12 mt-1'>
<Navbar onChange = {(search)=>this.GetSearchItem(search)}></Navbar>
</div>
</div>
<div className = 'row'>
<div className = 'col-3'>
<h3>{this.state.searchItem}</h3>
</div>
</div>
</div>
)
}
}
export default Main
Child Component (Navbar)
import React,{Component} from 'react'
import {AppBar,Toolbar,IconButton,Typography,InputBase} from '#material-ui/core'
import MenuIcon from '#material-ui/icons/Menu';
import SearchIcon from '#material-ui/icons/Search';
import {fade , makeStyles} from '#material-ui/core/styles'
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',
},
},
},
}));
class Navbar extends Component{
render(){
const classes = this.props.classes;;
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>
Pizaa Valley
</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' }}
onChange={(event)=>this.props.onChange(event.target.value)}
/>
</div>
</Toolbar>
</AppBar>
</div>
)
}
}
export default () => {
const classes = useStyles();
return (
<Navbar classes={classes} />
)
}
The problem is that you have two Navbar types. You first have the class component created using class Navbar. And second you have the following functional component defined here:
export default () => {
const classes = useStyles();
return (
<Navbar classes={classes} />
)
}
When you do
import Navbar from '../Presentational/Navbar'
<Navbar onChange = {(search)=>this.GetSearchItem(search)}></Navbar>
The onChange prop is correctly given to the functional component, but is never passed along to the class-based component. You can fix this by replacing your functional component with the below code:
export default props => {
const classes = useStyles();
return (
// using the "spread operator", we pass along all the props given
// to the functional component, so the class-based component can
// also access these
<Navbar {...props} classes={classes} />
)
}
you've done everything correctly except change this:
GetSearchItem(search){
this.setState({searchItem:search})
}
to
GetSearchItem = (search) => {
this.setState({searchItem:search})
}
as an arrow function it has access to the scope above
Try with the following:-
In your parent component modified the below line:-
<Navbar onChangeCallBack = {(search)=>this.GetSearchItem(search)}></Navbar>
In your child Navbar component only modified the below line:-
onChange={(event)=>this.props.onChangeCallBack(event.target.value)}
I'm still new to React so I have this project which has a drawer component with several routes (Dashboard, Chart, and User Settings). Under the Dashboard component (which also consists of the Navbar and Drawer component), I have a TablePage, ChartPage and SettingsPage components which will be rendered based from the route.
When I log in, it will render the table component in the /dashboard which gets the data from an API. If I click Chart from the drawer, it will reroute to /chart.
Currently it will call an API which will get the information when the dashboard component is loaded (/dashboard) and it will pass all necessary data (props) to the child component in ChartPage and SettingsPage. The problem is, if the database is updated, I will need to refresh the page in order to get the updated data. What I want to do, is whenever the user clicks on the button from the drawer, it will call the API to get the updated value.
In order to that, I'm using the useContext hooks which will pass the readData function to the TablePage component. How do I render it once if there is no information is updated and will render the component if there is an updated information from the database? Apparently it says that the readData is not a function even though I retrieve it from the useContext method in TablePage.js.
Dashboard.js
import clsx from "clsx";
import Drawer from "#material-ui/core/Drawer";
import MenuIcon from "#material-ui/icons/Menu";
import ChevronLeftIcon from "#material-ui/icons/ChevronLeft";
import DrawerHeader from "./DrawerHeader";
import DrawerContent from "./DrawerContent";
import React, { useState, useContext, useEffect, Fragment } 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 Typography from "#material-ui/core/Typography";
import IconButton from "#material-ui/core/IconButton";
// import RefreshIcon from "#material-ui/icons/Refresh";
// import Badge from "#material-ui/core/Badge";
// import NotificationsIcon from "#material-ui/icons/Notifications";
import ToggleMenu from "./ToggleMenu";
import Loader from "./Loader";
import axios from "axios";
import AuthApi from "../utils/createContext";
import moment from "moment";
import PageRoute from "./PageRoute";
import DataApi from "../utils/createContext";
const m = moment();
const today = m.format("LL");
// function refresh() {
// setTimeout(function () {
// window.location.reload();
// }, 100);
// }
const drawerWidth = 240;
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
},
listContainer: {
display: "flex",
},
toolbar: {
paddingRight: 24, // keep right padding when drawer closed
},
toolbarIcon: {
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
padding: "0 8px",
...theme.mixins.toolbar,
},
appBar: {
zIndex: theme.zIndex.drawer + 1,
transition: theme.transitions.create(["width", "margin"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
},
appBarShift: {
marginLeft: drawerWidth,
width: `calc(100% - ${drawerWidth}px)`,
transition: theme.transitions.create(["width", "margin"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
menuButton: {
marginRight: 36,
},
menuButtonHidden: {
display: "none",
},
title: {
flexGrow: 1,
},
drawerPaper: {
position: "relative",
whiteSpace: "nowrap",
width: drawerWidth,
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
drawerPaperClose: {
overflowX: "hidden",
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
width: theme.spacing(7),
[theme.breakpoints.up("sm")]: {
width: theme.spacing(9),
},
},
appBarSpacer: theme.mixins.toolbar,
// content: {
// flexGrow: 1,
// height: "100vh",
// overflow: "auto",
// },
paper: {
padding: theme.spacing(2),
display: "flex",
overflow: "auto",
flexDirection: "column",
},
fixedHeight: {
height: 240,
},
content: {
flexGrow: 1,
padding: theme.spacing(3),
transition: theme.transitions.create("margin", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
// marginLeft: "-70px",
},
contentShift: {
transition: theme.transitions.create("margin", {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen,
}),
marginLeft: 0,
},
}));
export default function Dashboard2() {
const classes = useStyles();
const [open, setOpen] = useState(false);
const handleDrawerOpen = () => {
setOpen(true);
};
const handleDrawerClose = () => {
setOpen(false);
};
const { value } = useContext(AuthApi);
//get user and customer api
const [userData, setUserData] = useState({});
const [customerData, setCustomerData] = useState([]);
const [customerCount, setCustomerCount] = useState();
const [isFetching, setIsFetching] = useState(false);
const readData = async () => {
const fetchUserInfo = await axios.post(`/auth/getemail/${value}`);
console.log(fetchUserInfo);
// console.log(result.data.user[0]._id);
const { data } = fetchUserInfo;
const { user } = data;
let newUserData = {};
user.forEach((cData) => {
newUserData = {
joindate: cData.joindate,
email: cData.email,
adminId: cData.adminId,
_id: cData._id,
};
});
// console.log(newUserData);
setUserData(newUserData);
const fetchCustomerInfo = await axios.post(
`/customerinfo/getcustomer/${fetchUserInfo.data.user[0].adminId}`
);
// console.log(fetchCustomerInfo);
const cust = fetchCustomerInfo.data;
// console.log(cust.customer_info);
// console.log(cust.count);
setCustomerData(cust.customer_info);
setCustomerCount(cust.count);
setIsFetching(true);
};
useEffect(() => {
readData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// const username = value.slice(0, value.search("#"));
return (
<Fragment>
{isFetching ? (
<div className={classes.root}>
<CssBaseline />
<AppBar
position="absolute"
className={clsx(classes.appBar, open && classes.appBarShift)}
>
<Toolbar className={classes.toolbar}>
<IconButton
edge="start"
color="inherit"
aria-label="open drawer"
onClick={handleDrawerOpen}
className={clsx(
classes.menuButton,
open && classes.menuButtonHidden
)}
>
<MenuIcon />
</IconButton>
<Typography
component="h1"
variant="h6"
color="inherit"
noWrap
className={classes.title}
>
Dashboard
</Typography>
{/* <IconButton color="inherit" onClick={refresh}>
<RefreshIcon />
</IconButton> */}
{/* <IconButton color="inherit">
<Badge badgeContent={4} color="secondary">
<NotificationsIcon />
</Badge>
</IconButton> */}
<ToggleMenu />
</Toolbar>
</AppBar>
<Drawer
variant="persistent" //persistent,permanent,temporary
anchor="left"
classes={{
paper: clsx(
classes.drawerPaper,
!open && classes.drawerPaperClose
),
}}
open={open}
>
<div className={classes.toolbarIcon}>
<IconButton onClick={handleDrawerClose}>
<ChevronLeftIcon />
</IconButton>
</div>
<DrawerHeader
email={userData.email}
joindate={moment(userData.joindate).format("LL")}
id={userData._id}
/>
<DrawerContent />
</Drawer>
<DataApi.Provider
value={{
customerData,
customerCount,
userData,
isFetching,
readData,
}}
>
<main
className={clsx(classes.content, {
[classes.contentShift]: open,
})}
>
<div className={classes.appBarSpacer} />
<PageRoute
customerdata={customerData}
userdata={userData}
customercount={customerCount}
momentdate={today}
/>
</main>
</DataApi.Provider>
</div>
) : (
<Loader type={"Bars"} color={"#9370DB"} />
)}
</Fragment>
);
}
TablePage.js
import React, { useContext, useEffect, Fragment } from "react";
import MaterialTable from "material-table";
import { Container, Grid } from "#material-ui/core";
import { makeStyles } from "#material-ui/core/styles";
import DataApi from "../utils/createContext";
import Loader from "./Loader";
const useStyles = makeStyles((theme) => ({
container: {
paddingTop: theme.spacing(4),
paddingBottom: theme.spacing(4),
},
}));
export default function TablePage({ customerdata }) {
const classes = useStyles();
const { readData, isFetching } = useContext(DataApi);
useEffect(() => {
readData();
}, []);
return (
<Fragment>
{isFetching ? (
<div>
<Container maxWidth="lg" className={classes.container}>
<Grid>
<MaterialTable
columns={[
{ title: "Customer Name", field: "fullname" },
{ title: "Phone No.", field: "phone", type: "string" },
{ title: "Address", field: "address" },
{ title: "Date", field: "updated", type: "date" }, //Data type: 'boolean', 'numeric', 'date', 'datetime', 'time', 'currency'
{ title: "Time", field: "updated", type: "time" },
]}
data={customerdata}
title="Check-In Information"
options={{
exportButton: true,
filtering: true,
}}
/>
</Grid>
</Container>
</div>
) : (
<Loader type={"Bars"} color={"#9370DB"} />
)}
</Fragment>
);
}
UPDATED: DataApi.Provider location is changed to wrap only the Routes component and the readData dependencies on the useEffect for Table.js has been removed
I have an application that lets a user to add tasks in a list. The tasks are fetched from the API and are displayed with the "List" component. When a user adds a new task from the "AddButton" component the task is stored in the database.
I want the to re-render the "List" component when the handleSubmit function happens on the "AddButton" component and adds the task to the database. The "addTask" and "getTasks" are fetching data from the API.
Thanks for your help in advance.
List component
import React, { useState, useEffect } from 'react';
import { makeStyles } from '#material-ui/styles';
import List from '#material-ui/core/List';
import ListItem from '#material-ui/core/ListItem';
import ListItemText from '#material-ui/core/ListItemText';
import Moment from 'react-moment';
import { getTasks } from './services/getTasks';
import AddButton from './AddButton';
import './App.css';
const useStyles = makeStyles(theme => ({
root: {
display: 'flex',
flexDirection: 'column',
width: '100%',
justifyContent: 'space-between',
height: '100%',
fontSize: '16px',
},
listItemLinkRoot: {
paddingLeft: theme.spacing(3),
width: '100%',
'&:hover': {
backgroundColor: '#212121',
color: 'white',
},
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
},
buttonContainer: {
display: 'flex',
width: '100%',
flexDirection: 'column',
justifyContent: 'flex-end',
},
list: {
flexGrow: 1,
overflow: 'auto',
},
listItemText: {
marginBottom: 8,
// fontSize: 20,
},
}));
function ListItemLink(props) {
return <ListItem button component="a" {...props} />;
}
export default function TaskList() {
const classes = useStyles();
const [tasks, setTasks] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await getTasks();
setTasks(result);
};
fetchData();
}, []);
return (
<div className={classes.root}>
<List classes={{ root: classes.list }}>
{tasks.map(task => (
<ListItemLink
divider
key={task.id}
classes={{ root: classes.listItemLinkRoot }}
href="simple-list"
>
<ListItemText
classes={{ root: classes.listItemText }}
primary={task.description}
/>
<Moment
classes={{ root: classes.listItemDate }}
format="DD/MM/YYYY"
>
{task.createdAt}
</Moment>
</ListItemLink>
))}
</List>
<div className={classes.buttonContainer}>
<AddButton classes={{ root: classes.add }} />
</div>
</div>
);
}
AddButton component
import React, { useState } from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Fab from '#material-ui/core/Fab';
import AddIcon from '#material-ui/icons/Add';
import TextField from '#material-ui/core/TextField';
import { addTask } from './services/postTask';
const useStyles = makeStyles(theme => ({
cont: {
display: 'flex',
flexDirection: 'row',
paddingBottom: '24px',
justifyContent: 'space-between',
backgroundColor: '#e0e0e0',
width: '100%',
alignItems: 'center',
felxGrow: 1,
},
fab: {
marginTop: theme.spacing(2),
marginRight: theme.spacing(2),
width: '100%',
},
textField: {
marginLeft: theme.spacing(3),
marginTop: 0,
marginBottom: 0,
flexGrow: 1,
},
}));
export default function AddButton() {
const classes = useStyles();
const [task, setTask] = useState({
description: '',
completed: false,
});
const handleChange = ev => {
setTask({ ...task, [ev.target.id]: ev.target.value });
};
const handleSubmit = () => {
addTask(task);
};
return (
<div className={classes.cont}>
<TextField
onChange={handleChange}
id="description"
label="Add a task"
rowsMax="4"
className={classes.textField}
margin="normal"
/>
<Fab
onClick={handleSubmit}
variant="extended"
size="small"
color="primary"
aria-label="add"
className={classes.fab}
>
<AddIcon />
Add
</Fab>
</div>
);
}
In your list component you can have your handleSubmit function and pass it down to your child AddButton component:
<AddButton classes={{ root: classes.add }} handleSubmit={handleSubmit} />
One solution I can think of is to move the fetchData function outside of your useEffect hook and pass it to the Button as a prop:
const fetchData = async () => {
const result = await getTasks();
setTasks(result);
};
useEffect(() => {
fetchData();
}, []);
...
<AddButton classes={{ root: classes.add }} refetch={fetchData}/>
Then in AddButton (assuming addTask() is async).
const handleSubmit = () => {
addTask(task)
.then(res => props.refetch())
};
Though it may make more sense to handle all of the state functionality in the parent component.