Changing background Image based on Route in React - reactjs

Hey I have a background image for Home component. I've placed the background image in Header Component. In my Header component there is a navbar and the background image. I've place the Header component outside switch inside React Router so that some pages can access the navbar. Now what I want to do is when I go to a food description page I want to keep the navbar but I want to change/remove the background Image . How can I achieve that ?
App.js
function App() {
return (
<div>
<DataContextProvider>
<LoginContextProvider>
<Router>
<Header></Header>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/login" component={Login} />
<Route path="/food/:id" component={FoodDetail} />
</Switch>
</Router>
</LoginContextProvider>
</DataContextProvider>
</div>
);
}
Header.js
<div className={classes.grow}>
<AppBar position="fixed" style={{ backgroundColor: "white" }}>
<Container>
<Toolbar>
<Link to="/">
<Typography className={classes.title} variant="h6" noWrap>
Material-UI
</Typography>
</Link>
<div className={classes.grow} />
<div className={classes.sectionDesktop}>
{loggedInUser.email ? (
<MenuItem onClick={handleProfileMenuOpen}>
<IconButton
aria-label="account of current user"
aria-controls="primary-search-account-menu"
aria-haspopup="true"
color="inherit"
>
<AccountCircleIcon style={{ fill: "black" }} />
</IconButton>
<p className={classes.textColor}>{loggedInUser.name}</p>
</MenuItem>
) : (
<MenuItem onClick={handleLogin}>
<IconButton
aria-label="account of current user"
aria-controls="primary-search-account-menu"
aria-haspopup="true"
color="inherit"
>
<AccountCircleIcon style={{ fill: "black" }} />
</IconButton>
<p className={classes.textColor}>Login</p>
</MenuItem>
)}
<IconButton
aria-label="show 11 new notifications"
color="inherit"
>
<Badge badgeContent={0} color="secondary">
<ShoppingCart style={{ fill: "black" }}></ShoppingCart>
</Badge>
</IconButton>
</div>
<div className={classes.sectionMobile}>
<IconButton
aria-label="show more"
aria-controls={mobileMenuId}
aria-haspopup="true"
onClick={handleMobileMenuOpen}
color="inherit"
>
<MenuIcon style={{ fill: "black" }} />
</IconButton>
</div>
</Toolbar>
</Container>
</AppBar>
{renderMobileMenu}
{renderMenu}
<div style={{ backgroundImage: `url(${bg})` }} className="header">
<h2>Whatever you need, we will deliver it to you!</h2>
<Paper component="form" className="search">
<InputBase
className={classes.input}
placeholder="Search Keywords"
inputProps={{ "aria-label": "search google maps" }}
onChange={(e) => setSearch(e.target.value)}
/>
<IconButton
type="submit"
className={classes.iconButton}
aria-label="search"
>
<SearchIcon />
</IconButton>
</Paper>
</div>
</div>
);
};
FoodDetail.js
const FoodDetail = () => {
const { id } = useParams();
const [foodDetails, setFoodDetails] = useState({});
useEffect(() => {
fetch(`http://localhost:5000/api/foods/${id}`)
.then((res) => res.json())
.then((data) => {
setFoodDetails(data);
console.log(data);
});
}, [id]);
return (
<div>
<h1>Details</h1>
<h1>Name: {foodDetails.name} </h1>
</div>
);
};

You need to use 'useLocation' to get the page path and then add it in a className.
Header.js
import React from "react";
import { useLocation } from "react-router-dom";
import "./Header.css";
import Navigation from "../Navigation/Navigation";
export default function Header() {
const path = useLocation().pathname;
const location = path.split("/")[1];
return (
<div className={"header " + location}>
<Navigation />
</div>
);
}
Header.css
.header {
height: 300px;
}
.header.home {
background: grey
}
.header.about {
background: red
}
.header.contact {
background: yellow
}
Check my complete demo : Stackblitz

Related

How to pass state between these 2 components

I am trying to conditionally render a functional component. I have another component for the layout of the page. It contains a list item which the user will click to show this other component. Ideally, I'm hoping to have a few of these.
I have tried just about every example I can find online. Callback functions, using the React docs etc. I'm only trying to toggle a Boolean value to show a component or not. So using Redux seems a bit extreme for what I'm trying to do. I'm not sure if it has to do with this being a next, or typescript project.
Here is the parent component - index.tsx
const Index: React.FC = ({flagHelper}: any) => {
const handleOpenLink = (href: string) => {
window.open(href);
return false;
};
const classes = useStyles();
return (
<Layout>
<div className={classes.root}>
<div className={classes.background}>
</div>
<Head>
<title>Countryball Creator</title>
</Head>
<div className={classes.strip}>
{flagHelper && (<div>hello</div>)}
</div>
</div>
</Layout>
);
};
export default Index;
And here is the layout/child component.
export const Layout: React.FC<LayoutProps> = (props) => {
const { container } = props;
const classes = useStyles();
const theme = useTheme();
const [mobileOpen, setMobileOpen] = React.useState(false);
function handleDrawerToggle() {
setMobileOpen(!mobileOpen);
}
// Set state for component
const [flagHelper, setFlagHelper] = useState(false); // Flag tool
const drawer = (
<div>
<div className={classes.toolbar} />
<Divider />
<List>
{['Pick Flag'].map((text) => (
<ListItem button key={text} onClick={() => setFlagHelper(!flagHelper)} className={classes.menuItem}>
<ListItemIcon className={classes.menuItem}><DashboardIcon /></ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</div>
);
return (
<div className={classes.root}>
<CssBaseline />
<AppBar position="fixed" className={classes.appBar}>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
edge="start"
onClick={handleDrawerToggle}
className={classes.menuButton}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap>
Countryball Creator 🌎
</Typography>
<User/>
</Toolbar>
</AppBar>
<nav className={classes.drawer} aria-label="mailbox folders">
{/* The implementation can be swapped with js to avoid SEO duplication of links. */}
<Hidden smUp implementation="css">
<Drawer // this one is for mobile
container={container}
variant="temporary"
anchor={theme.direction === 'rtl' ? 'right' : 'left'}
open={mobileOpen}
onClose={handleDrawerToggle}
classes={{
paper: classes.drawerPaper,
}}
ModalProps={{
keepMounted: true, // Better open performance on mobile.
}}
>
{drawer}
</Drawer>
</Hidden>
<Hidden xsDown implementation="css">
<Drawer // This one is for desktop
classes={{
paper: classes.drawerPaper,
}}
variant="permanent"
open
>
{drawer}
</Drawer>
</Hidden>
</nav>
<main className={classes.content}>
<div className={classes.toolbar} />
{props.children}
</main>
</div>
);
}

How would I render my MUI drawer on all components except login page?

I am trying to render the Material UI drawer on all pages except the login page but it's not working accordingly.
In short
My App component has 2 routes login and dashboard (Both Working)
Then my dashboard page has multiple routes for home and about pages (None of them working.)
Below is my code separately. Also, I'm attaching codeSandBox link for the same
Expected Output To understand my desired output
On login URL = <Login/> should render
On dashboard URL = <AppDrawer/> should render
inside AppDrawer I am having more routes in
<main>
<Route path="/home" component={home}/>
Route path="/about" component={about}/>
</main>
My actual code is here:
App.js
import { Switch, Route } from "react-router-dom";
import AppDrawerBar from "./compponents/AppDrawerBar";
import Login from "./pages/Login";
import "./styles.css";
export default function App() {
return (
<div className="App">
<Switch>
<Route path="/login" exact component={Login} />
<Route path="/dashboard" component={AppDrawerBar} />
</Switch>
</div>
);
}
Login.js
import React from "react";
const Login = () => {
return <h1>Login Page</h1>;
};
export default Login;
AppDrawerBar.js
import React from "react";
import clsx from "clsx";
import { makeStyles, useTheme } from "#material-ui/core";
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 Home from "../pages/Home";
import About from "../pages/About";
import { Route, Switch } from "react-router-dom";
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>
{["Inbox", "Starred", "Send email", "Drafts"].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<Divider />
<List>
{["All mail", "Trash", "Spam"].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</Drawer>
<main
className={clsx(classes.content, {
[classes.contentShift]: open
})}
>
<div className={classes.drawerHeader} />
// More Routes for main pages
<Switch>
<Route path="/home" exact component={Home} />
<Route path="/about" component={About} />
</Switch>
</main>
</div>
);
}
Home.js
import React from "react";
const Home = () => {
return <h1>Home Page</h1>;
};
export default Home;
About.js
import React from "react";
const About = () => {
return <h1>About Page</h1>;
};
export default About;
You are mainly missing two steps to get your router working and updating your main content:
Propagate route change upon items click: The side Drawer component should be wrapped with a Router component and individual ListItems should be declared with Link as their component so whenever an item is clicked on the side menu, the change is propagated.
Updating your main content based on the navigated route: The main element should make use only of Route components to have the content dynamically loaded upon URL location change.
Here down the updated AppDrawerBar component (note that I omitted the redundant ListItems to bring focus to the important changes:
return (
<BrowserRouter>
<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>
<ListItem button key="home" to="/home" component={Link}>
<ListItemIcon>
<MailIcon />
</ListItemIcon>
<ListItemText primary="Home" />
</ListItem>
</List>
<Divider />
<List>
<ListItem button key="about" to="/about" component={Link}>
<ListItemIcon>
<InboxIcon />
</ListItemIcon>
<ListItemText primary="About" />
</ListItem>
</List>
</Drawer>
<main
className={clsx(classes.content, {
[classes.contentShift]: open
})}
>
<div className={classes.drawerHeader} />
<Route path="/home" exact component={Home} />
<Route path="/about" component={About} />
</main>
</div>
</BrowserRouter>
);
Edit:
The Link component mentioned above is the one from react-router-dom since it will redirect to the new /home and /about locations allowing the main directive content to update accordingly.
Here you can find a forked (working) copy of your AppDrawerBar component.
I saw you use react-router so if you want your drawer is displayed all the time except on the login page you can simply add conditional render for your drawer using the useHistory hook from "react-router-dom".
const history = useHistory
console.log(history.location.pathname) // give the current path name
So here is a solution :
export default function PersistentDrawerLeft() {
const history = useHistory(); // from react-router-dom
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 />
{history.location.pathname !== "/login" &&
<>
<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>
{["Inbox", "Starred", "Send email", "Drafts"].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<Divider />
<List>
{["All mail", "Trash", "Spam"].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</Drawer>
</>
}
<main
className={clsx(classes.content, {
[classes.contentShift]: open
})}
>
<div className={classes.drawerHeader} />
// More Routes for main pages
<Switch>
<Route path="/home" exact component={Home} />
<Route path="/about" component={About} />
</Switch>
</main>
</div>
);
}

How to access child ref from parent without fowardRef

I have a ref which works on the parent component, but i need this ref on the child component.
const divRef = React.useRef<any>();
However props.ref is showing undefined on the commentListContainer. What should i pass on the commentListContainer ?
I saw that forwardRef could be used in a situation like this, but im unsure how this fowardRef would work using hooks in a typescript manner.
PostItemContainer.tsx
import React, { Fragment, useState, useRef } from "react";
import Avatar from "#material-ui/core/Avatar";
import Button from "#material-ui/core/Button";
import Grid from "#material-ui/core/Grid";
import Paper from "#material-ui/core/Paper";
import Typography from "#material-ui/core/Typography";
import DeleteOutlineOutlinedIcon from "#material-ui/icons/DeleteOutlineOutlined";
import FavoriteIcon from "#material-ui/icons/Favorite";
import FavoriteBorderIcon from "#material-ui/icons/FavoriteBorder";
import moment from "moment";
import { toast, ToastContainer } from "react-toastify";
import OurLink from "../../../common/OurLink";
import CommentForm from "../comment/CommentForm";
import CommentList from "../commentList/CommentList";
import OurModal from "../../../common/OurModal";
import "react-toastify/dist/ReactToastify.css";
function PostItemContainer(props: any) {
const [openModal, setOpenModal] = useState(false);
const [openForm, setOpenForm] = useState(false);
const [comment_body, setCommentBody] = useState("");
const [gifUrl, setGifUrl] = useState("");
const divRef = React.useRef<any>();
const writeComment = () => {
// this is the same as this.setState({ openForm: !this.state.open })
setOpenForm(!openForm);
};
const commentChange = (comment) => {
setGifUrl("");
setCommentBody(comment);
};
const selectGif = (e) => {
setGifUrl(e.images.downsized_large.url);
setCommentBody("");
// you wont be able to add text comment with a gif, it will look weird :(
};
const handleClickOpen = () => {
setOpenModal(true);
};
const handleCloseModal = () => {
setOpenModal(false);
};
const commentSubmit = (e: any, id: number) => {
e.preventDefault();
const formData = {
comment_body,
id,
gifUrl,
};
props.postComment(formData);
setCommentBody("");
setOpenForm(false);
console.log(divRef);
window.scrollTo(0, divRef.current.offsetTop);
};
const { post, currentUser, getNotifications } = props;
return (
<Fragment>
{getNotifications && <ToastContainer autoClose={1000} position={toast.POSITION.BOTTOM_RIGHT} />}
<Grid item={true} sm={12} md={12} style={{ margin: "20px 0px" }}>
<Paper style={{ padding: "20px" }}>
<Typography variant="h5" align="left">
<OurLink
style={{ fontSize: "16px" }}
to={{
pathname: `/post/${post.id}`,
state: { post },
}}
title={post.title}
/>
</Typography>
<Grid item={true} sm={12} md={12} style={{ padding: "30px 0px" }}>
<Typography align="left">{post.postContent.slice(0, 50)}</Typography>
</Grid>
<Avatar
style={{
display: "inline-block",
margin: "-10px -20px",
padding: "0px 30px 0px 20px",
}}
sizes="small"
src={post.author.gravatar}
/>
<Typography display="inline" variant="subtitle1" align="left">
<OurLink
to={{
pathname: `/profile/${post.author.username}`,
state: { post },
}}
title={post.author.username}
/>
</Typography>
<Typography align="right">Likes: {post.likeCounts}</Typography>
<Grid container={true} spacing={1} style={{ padding: "20px 0px" }}>
<Grid item={true} sm={10} lg={10} md={10} style={{ padding: "0px 0px" }}>
<Typography align="left">
{currentUser && currentUser.user && post.userId === currentUser.user.id ? (
<span style={{ cursor: "pointer" }} onClick={() => props.deletePost(post.id, post.userId)}>
<DeleteOutlineOutlinedIcon style={{ margin: "-5px 0px" }} color="primary" /> <span>Delete</span>
</span>
) : null}
</Typography>
</Grid>
<Grid item={true} sm={2} lg={2} style={{ padding: "0px 15px" }}>
<Typography align="right">
{Object.entries(currentUser).length === 0 ? (
<Fragment>
<span onClick={handleClickOpen}>
<FavoriteBorderIcon style={{ color: "red", cursor: "pointer" }} />
</span>
{openModal ? <OurModal open={openModal} handleClose={handleCloseModal} /> : null}
</Fragment>
) : (
<Fragment>
{post.likedByMe === true ? (
<span style={{ cursor: "pointer" }} onClick={() => props.dislikePost(post.id)}>
<FavoriteIcon style={{ color: "red" }} />
</span>
) : (
<span onClick={() => props.likePost(post.id)}>
<FavoriteBorderIcon style={{ color: "red", cursor: "pointer" }} />
</span>
)}
</Fragment>
)}
</Typography>
</Grid>
</Grid>
<Typography variant="h6" align="left">
{moment(post.createdAt).calendar()}
</Typography>
<Grid item={true} sm={12} lg={12} style={{ paddingTop: "40px" }}>
{Object.entries(currentUser).length === 0 ? (
<Fragment>
<Button onClick={handleClickOpen} variant="outlined" component="span" color="primary">
{"Write A Comment"}
</Button>
{openModal ? <OurModal open={openModal} handleClose={handleCloseModal} /> : null}
</Fragment>
) : (
<Fragment>
<Button onClick={writeComment} variant="outlined" component="span" color="primary">
{openForm ? "Close" : "Write A Comment"}
</Button>
</Fragment>
)}
{openForm ? (
<CommentForm
commentChange={(e: any) => commentChange(e.target.value)}
comment_body={comment_body}
onSubmit={(e) => commentSubmit(e, post.id)}
gifUrl={selectGif}
isGif={gifUrl}
/>
) : null}
{post.Comments.length > 0 ? (
<Fragment>
<Typography style={{ padding: "10px 0px", margin: "20px 0px" }}>Commments</Typography>
<CommentList ref={divRef} user={currentUser} deleteComment={props.deleteComment} userId={post.userId} postId={post.id} comments={post.Comments} {...props} />
{/* if show more hide show more button and show show less comments button */}
</Fragment>
) : (
<Grid item={true} sm={12} lg={12} style={{ padding: "30px 0px" }}>
<Typography>No Commments Yet</Typography>
</Grid>
)}
// <div ref={divRef}></div> works here
</Grid>
</Paper>
</Grid>
</Fragment>
);
}
export default PostItemContainer;
commentListContainer.tsx
import React from "react";
import List from "#material-ui/core/List";
import Typography from "#material-ui/core/Typography";
import CommentItem from "./../commentItem/CommentItem";
import moment from "moment";
import CommentAuthorData from "../commentAuthorData/commentAuthorData";
const ourStyle = {
margin: "15px",
};
const CommentListContainer = (props) => {
const { comment, openModal, handleClickOpen, handleCloseModal, isBold } = props;
return (
<List style={{ paddingBottom: "20px" }}>
<CommentAuthorData {...props} comment={comment} openModal={openModal} handleClickOpen={handleClickOpen} handleCloseModal={handleCloseModal} isBold={isBold} />
{/* want to call ref here but it returns undefined */}
<div ref={props.ref} style={ourStyle}>
<CommentItem comment={comment} user={props.user} postId={props.postId} {...props} />
<Typography style={{ fontSize: "12px" }} variant="body1" align="left">
{moment(comment.createdAt).calendar()}
</Typography>
</div>
</List>
);
};
export default CommentListContainer;
According to the docs: https://reactjs.org/docs/forwarding-refs.html you should use forwardRef for this:
// I used any for props, feel free to replace with your Props interface
const CommentListContainer: React.ForwardRefRenderFunction <HTMLDivElement, any> = (props, ref) => {
const { comment, openModal, handleClickOpen, handleCloseModal, isBold } = props;
return (
<List style={{ paddingBottom: "20px" }}>
<CommentAuthorData {...props} comment={comment} openModal={openModal} handleClickOpen={handleClickOpen} handleCloseModal={handleCloseModal} isBold={isBold} />
{/* here you pass your ref */}
<div ref={ref} style={ourStyle}>
<CommentItem comment={comment} user={props.user} postId={props.postId} {...props} />
<Typography style={{ fontSize: "12px" }} variant="body1" align="left">
{moment(comment.createdAt).calendar()}
</Typography>
</div>
</List>
);
};
// you use forwardRef here
export default React.forwardRef(CommentListContainer);
Here is the relevant TS definition for this: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts#L564
Your parent should remain unchanged.

react calls function for each item in array

I'm quite stuck on how to execute a function for a specific post.id, and not execute it for all items within the array.
The scenario
upon clicking show more comments, it shows more comments for each item in the array.
For example
and this
How can i make it so that upon show more comments, it shows more comments for that post only.
here is the current code, in which the logic is happening.
{post.Comments.length > 0 ? (
<Fragment>
<Typography style={{padding: "10px 0px", margin: "20px 0px"}}>Commments</Typography>
<CommentList showMore={showMore} comments={post.Comments} />
{/* if show more hide show more button and show show less comments button */}
{/* {isPost === post.id ? ( */}
<Fragment>
{post.Comments.length > 3 && showLessFlag === false && (
<Button onClick={ e => showComments(e, post.id)} variant="outlined" component="span" color="primary">
Show More Comments
</Button>
)}
{post.Comments.length > 3 && showLessFlag === true && (
<Button onClick={ e => showLessComments(e)} variant="outlined" component="span" color="primary">
Show Less Comments
</Button>
)}
</Fragment>
{/* ):(
null
)} */}
</Fragment>
):(
<Grid item={true} sm={12} lg={12} style={{ padding: "30px 0px"}}>
<Typography>No Commments Yet</Typography>
</Grid>
)}
fullCode(postList)
import Avatar from "#material-ui/core/Avatar";
import Button from "#material-ui/core/Button";
import Divider from "#material-ui/core/Divider";
import Grid from "#material-ui/core/Grid";
import Paper from "#material-ui/core/Paper";
import Typography from "#material-ui/core/Typography";
import DeleteOutlineOutlinedIcon from "#material-ui/icons/DeleteOutlineOutlined";
import FavoriteIcon from "#material-ui/icons/Favorite";
import FavoriteBorderIcon from "#material-ui/icons/FavoriteBorder";
import moment from "moment";
import React, { Fragment, useState } from "react";
import OurLink from "../../common/OurLink";
import CommentList from "../../forms/commentList/CommentList";
import CommentForm from "../../forms/comment/CommentForm";
export default function PostList(props: any) {
const [isComment, setIsComment] = useState(false);
const [showMore, setShowMore] = useState(3)
const [showLessFlag, setShowLessFlag] = useState(false);
const [comment_body, setCommentBody] = useState('');
const [isPost, setIsPost] = useState(null);
const writeComment = (id) => {
setIsComment(isComment ? "" : id);
};
const showComments = (e, id) => {
e.preventDefault();
setShowMore(12);
setShowLessFlag(true);
// setIsPost(isPost ? "" : id)
}
const showLessComments = (e) => {
e.preventDefault();
setShowMore(3);
setShowLessFlag(false);
}
const commentSubmit = (e: any, id:number) => {
e.preventDefault();
const formData = {
comment_body,
postId: id
};
if(comment_body.length > 6 ){
if(props.postComment(formData)){
setIsComment(false)
setCommentBody('')
}
}else{
alert("Comment must be at least 6 characters")
}
};
const { posts, currentUser} = props;
console.log(isPost)
return posts.length > 0 ? (
posts.map((post, i) => (
<Fragment key={i}>
<Grid item={true} sm={12} md={12} style={{ margin: "20px 0px" }}>
<Paper style={{ padding: "20px",}}>
<Typography variant="h5" align="left">
<OurLink to={{
pathname: `/post/${post.id}`,
state: { post },
}}
title={post.title}
/>
</Typography>
<Grid item={true} sm={12} md={12} style={{ padding: "30px 0px"}} >
<Typography align="left">{post.postContent.slice(0, 30)}</Typography>
</Grid>
<Avatar
style={{
display: "inline-block",
margin: "-10px -20px",
padding: "0px 30px 0px 20px",
}}
sizes="small"
src={post.author.gravatar}
/>
<Typography display="inline" variant="subtitle1" align="left">
{post.author.username}
</Typography>
<Typography align="right">Likes: {post.likeCounts}</Typography>
{/* <span
style={{ cursor: "pointer" }}
onClick={() => props.likePost(post.id)}
>
{" "}
Like this post
</span>
<div style={{ margin: "20px 0px", cursor: "pointer" }}>
<span onClick={() => props.dislikePost(post.id)}>
Dislike this post
</span>
</div> */}
<Grid container={true} spacing={1} style={{ padding: "20px 0px"}}>
<Grid item={true} sm={10} lg={10} md={10} style={{ padding: "0px 0px"}}>
<Typography align="left">
{currentUser && currentUser.user && post.userId === currentUser.user.id ? (
<span style={{cursor: "pointer"}} onClick={() => props.deletePost(post.id)}>
<DeleteOutlineOutlinedIcon style={{ margin: "-5px 0px"}} color="primary" /> <span>Delete</span>
</span>
) : (
null
)}
</Typography>
</Grid>
<Grid item={true} sm={2} lg={2} style={{ padding: "0px 15px"}}>
<Typography align="right">
{post.likedByMe === true ? (
<span style={{ cursor: "pointer"}} onClick={() => props.dislikePost(post.id)}>
<FavoriteIcon style={{ color: "red" }}/>
</span>
) : (
<span onClick={() => props.likePost(post.id)}>
<FavoriteBorderIcon
style={{ color: "red", cursor: "pointer" }}
/>
</span>
)}
</Typography>
</Grid>
</Grid>
<Typography variant="h6" align="left">
{moment(post.createdAt).calendar()}
</Typography>
<Grid item={true} sm={12} lg={12} style={{ paddingTop: "40px"}}>
<Button onClick={() => writeComment(post.id)} variant="outlined" component="span" color="primary">
{isComment === post.id ? "Close" : "Write A Comment"}
</Button>
{isComment === post.id
? (
<CommentForm
commentChange={e => setCommentBody(e.target.value)}
comment_body={comment_body}
onSubmit={e => commentSubmit(e, post.id)}
/>
)
: null}
{post.Comments.length > 0 ? (
<Fragment>
<Typography style={{padding: "10px 0px", margin: "20px 0px"}}>Commments</Typography>
<CommentList showMore={showMore} comments={post.Comments} />
{/* if show more hide show more button and show show less comments button */}
{/* {isPost === post.id ? ( */}
<Fragment>
{post.Comments.length > 3 && showLessFlag === false && (
<Button onClick={ e => showComments(e, post.id)} variant="outlined" component="span" color="primary">
Show More Comments
</Button>
)}
{post.Comments.length > 3 && showLessFlag === true && (
<Button onClick={ e => showLessComments(e)} variant="outlined" component="span" color="primary">
Show Less Comments
</Button>
)}
</Fragment>
{/* ):(
null
)} */}
</Fragment>
):(
<Grid item={true} sm={12} lg={12} style={{ padding: "30px 0px"}}>
<Typography>No Commments Yet</Typography>
</Grid>
)}
</Grid>
</Paper>
</Grid>
</Fragment>
))
) : (
<div>
<Grid item={true} md={8}>
<Typography>No Posts yet</Typography>
</Grid>
</div>
);
}
CommentList
import Divider from "#material-ui/core/Divider";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import Typography from "#material-ui/core/Typography";
import Button from "#material-ui/core/Button";
import Grid from "#material-ui/core/Grid";
import moment from "moment";
import React, { Component } from "react";
const CommentList = (props: any) => {
return(
<div style={{ overflow:"scroll"}}>
{props.comments.slice(0, props.showMore).map((comment, i) => (
<div key={i}>
<List style={{ paddingBottom: "20px"}}>
<ListItem alignItems="center" style={{ padding: "0px"}}>
<Typography color="primary" align="left">
{comment.comment_body}
</Typography>
</ListItem>
<Typography style={{ padding: "0px 0px"}} variant="caption" align="left">{comment.author.username}</Typography>
<Typography style={{fontSize: "12px"}} variant="body1" align="left">{moment(comment.createdAt).calendar()}</Typography>
<Divider variant="fullWidth" component="li" />
</List>
</div>
))}
</div>
)
};
export default CommentList;
I pretty much just moved the show/show less logic to the commentlist component, and made this component into to a react hook component, instead of a state less component.
so now we have
Similar issue
how to prevent duplicate onChange values within map loop
CommentList
import Divider from "#material-ui/core/Divider";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import Typography from "#material-ui/core/Typography";
import Button from "#material-ui/core/Button";
import Grid from "#material-ui/core/Grid";
import moment from "moment";
import React, { Component, Fragment, useState } from "react";
export default function CommentList(props: any) {
const [showMore, setShowMore] = useState(3)
const [showLessFlag, setShowLessFlag] = useState(false);
const showComments = (e) => {
e.preventDefault();
setShowMore(12);
setShowLessFlag(true);
}
const showLessComments = (e) => {
e.preventDefault();
setShowMore(3);
setShowLessFlag(false);
}
return (
<Grid>
{props.comments.slice(0, showMore).map((comment, i) => (
<div key={i}>
<List style={{ paddingBottom: "20px" }}>
<ListItem alignItems="center" style={{ padding: "0px" }}>
<Typography color="primary" align="left">
{comment.comment_body}
</Typography>
</ListItem>
<Typography style={{ padding: "0px 0px" }} variant="caption" align="left">{comment.author.username}</Typography>
<Typography style={{ fontSize: "12px" }} variant="body1" align="left">{moment(comment.createdAt).calendar()}</Typography>
<Divider variant="fullWidth" component="li" />
</List>
</div>
))}
<Fragment>
{props.comments.length > 3 && showLessFlag === false ? (
<Button onClick={e => showComments(e)} variant="outlined" component="span" color="primary">
Show More Comments
</Button>
) : (
<Fragment>
{props.comments.length > 3 && (
<Button onClick={e => showLessComments(e)} variant="outlined" component="span" color="primary">
Show Less Comments
</Button>
)}
</Fragment>
)}
</Fragment>
</Grid>
)
};
postList(after clean up)
{post.Comments.length > 0 ? (
<Fragment>
<Typography style={{ padding: "10px 0px", margin: "20px 0px" }}>Commments</Typography>
<CommentList comments={post.Comments} />
{/* if show more hide show more button and show show less comments button */}
</Fragment>
) : (
<Grid item={true} sm={12} lg={12} style={{ padding: "30px 0px" }}>
<Typography>No Commments Yet</Typography>
</Grid>
)}

Hide Menu Icon Button of Appbar of Material UI for only Desktop

<AppBar title="My AppBar" showMenuIconButton={false} />
This Hides the Menu Icon in all devices.
I need to hide only in Desktop.
How Can I achieve this?
You can try this-
showMenuIconButton={window.screen.width.<600? true:false}
where 600 is 600 pixels.
You can subscribe to resize event listener:
import React, { useState, useEffect } from 'react'
const Events = () => {
const [windowWidth, setWindowWidth] = useState(window.innerWidth)
useEffect(() => {
function handleResize() {
setWindowWidth(window.innerWidth)
}
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
return (
<div>
<AppBar title="My AppBar" showMenuIconButton={windowWidth < 600} />
</div>
)
you can use sx prop attribute for responsive style.
sx={{ mr: 2, display: {sm: 'none'} }}
<IconButton
edge="start"
color="inherit"
aria-label="menu"
sx={{ mr: 2, display: {sm: 'none'} }}
>
<MenuIcon />
</IconButton>
AppBar should look like,
<Box sx={{display: 'flex'}}>
<CssBaseline />
<AppBar
position="fixed"
elevation={1}
>
<Toolbar variant="dense">
<IconButton
edge="start"
color="inherit"
aria-label="menu"
sx={{ mr: 2, display: {sm: 'none'} }}
onClick={this.handleDrawerToggle}>
<MenuIcon />
</IconButton>
<Typography variant="h6" color="inherit" component="div">
Logo
</Typography>
</Toolbar>
</AppBar>
<LeftSideBar
handleDrawerToggle={this.handleDrawerToggle}
mobileOpen={this.state.mobileOpen}
/>
</Box>

Resources