On page reload, my Gatsby.js footer component renders twice - reactjs

Thank you for taking the time to look at my problem. I would really appreciate any help.
**Update:
Ok, so I think I know what's causing the problem but I don't know why. Before, on my products/merch page I only had one item in my database. On the products/beans page I had multiple items. On the products/merch page where there was only one result from the query the footer did not display twice. As soon as I added another item into the merch category and products/merch had two products, on page reload it also began to duplicate the footer.
Why would multiple queries have any effect on how a page renders? What could be some solutions to this? I'm going to try and pass the data in as props from the graphql to a allProducts component to see if that fixes the issue.
**
I'm using Gatsby.js and Strapi headless CMS for an ecommerce site I'm building. On a page which displays all the products in a specific category, if the page is reloaded, it renders the footer twice. This is confusing because when the page first renders it's fine but only on reload does it do it. If the website loads from that specific page, it also displays two footers. Once I navigate to another page and reload, the second footer goes away. This page giving me an issue has a GraphQL query. I have another page that also has a GraphQL query but it does not to do this. Something in this page is causing in issue in rendering.
What I've tried:
In gatsby-node.js, in the createPages export I had tried to create each page and pass a template and the context of "type" so that the GraphQL query could then filter for the data that the page needs. I thought that using createPage this way could cause a rendering issue. I've also tried removing the contents of the Footer component and directly placing them in layout.js. I've tried removing Layout component from the page and adding Header.js and Footer.js directly, still the same problem. I'm only experiencing this problem once i host it, not on the localhost. Is it an issue with Server Side Rendering? Here is a link to the site hosted on gatsby cloud, and if you click on any other page and refresh the second footer goes away but if you click back to this page and then refresh the second footer pops up again.
https://build-856af2e5-f049-46de-b6a5-9e11aa46a906.gtsb.io/products/beans/
createPage({
path: "/products/merch",
component: path.resolve(`src/templates/allproducts.js`),
context: {
type: "merch",
},
})
createPage({
path: "/products/beans",
component: path.resolve(`src/templates/allproducts.js`),
context: {
type: "beans",
},
})
So I then moved each page into a folder in the Gatsby pages folder. So Instead of using createPages to programmatically create them I made them traditionally by creating a products folder then adding a new page for each category. This did not resolve my issue by creating the pages manually instead of using createPages.
It doesn't make sense to me why the footer would load twice and not the header or the main component in my layout.js, which looks like this:
import React from "react"
import PropTypes from "prop-types"
import { useStaticQuery, graphql } from "gatsby"
import Footer from "./Footer"
import Header from "./header"
import "./layout.css"
const Layout = ({ children }) => {
const data = useStaticQuery(graphql`
query SiteTitleQuery {
site {
siteMetadata {
title
}
}
}
`)
return (
<>
<Header siteTitle={data.site.siteMetadata.title} />
<main>{children}</main>
<Footer />
</>
)
}
Layout.propTypes = {
children: PropTypes.node.isRequired,
}
export default Layout
Here is the Footer.js component:
import React from "react"
import { Facebook, Instagram, Twitter } from "#material-ui/icons"
import styled from "styled-components"
const BottomNav = styled.footer`
border-top: 1px solid black;
position: sticky;
height: 15vh;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
font-family: Raleway;
text-transform: uppercase;
`
const BottomNavContainer = styled.div`
display: flex;
justify-content: space-between;
width: 200px;
margin-bottom: 0.3rem;
`
const Footer = () => {
return (
<BottomNav>
<BottomNavContainer>
<Facebook />
<Instagram />
<Twitter />
</BottomNavContainer>
<div>
© {new Date().getFullYear()},{` `} The Blank
</div>
</BottomNav>
)
}
export default Footer
And here is the code for the pages that are duplicating the footer:
function Alert(props) {
return <MuiAlert elevation={6} variant="filled" {...props} />
}
const AllBeansPage = props => {
const products = props.data.allStrapiProduct.nodes
// const classes = useStyles()
const [open, setOpen] = React.useState(false)
const { addToCart } = useContext(CartContext)
const handleClick = () => {
setOpen(true)
}
const handleClose = (event, reason) => {
if (reason === "clickaway") {
return
}
setOpen(false)
}
return (
<Layout>
<SEO
title={
products[0].type.charAt(0).toUpperCase() + products[0].type.slice(1)
}
/>
<Fade in={true} timeout={{ enter: 2000, exit: 500 }}>
<AllProductsContainer>
<Container>
{products.map(product => (
<Slide
direction="left"
in={true}
mountOnEnter
unmountOnExit
timeout={{ enter: 1500, exit: 500 }}
>
<ProductCard key={product.id}>
<Link
key={product.id}
style={{}}
to={`products/${product.type}/${product.slug}`}
>
<ImageContainer>
{console.log(product.thumbnail.childImageSharp.fluid)}
<Img
key={product.id}
fluid={product.thumbnail.childImageSharp.fluid}
/>
</ImageContainer>
</Link>
<ProductContent>
<Typography
gutterBottom
variant="h5"
component="h1"
style={{
fontFamily: "Caveat",
fontSize: "clamp(22px, 3vw, 28px)",
}}
>
{product.name}
</Typography>
<Typography
variant="body2"
color="textSecondary"
style={{ fontSize: "clamp(12px, 1.5vw, 16px)" }}
>
{product.description.slice(0, 35)}...
</Typography>
<Link
key={product.id}
style={{}}
to={`products/${product.type}/${product.slug}`}
>
{" "}
<Typography
variant="subtitle1"
color="primary"
style={{ fontSize: "clamp(14px, 1.5w, 18px)" }}
>
Learn More
</Typography>
</Link>
</ProductContent>
<CardActionArea>
<CardActions
style={{
display: "flex",
justifyContent: "space-between",
}}
>
<Button
size="small"
color="primary"
onClick={() => {
addToCart(product, 1)
handleClick()
}}
style={{ fontSize: "clamp(12px, 1.5vw, 18px)" }}
>
Add To Cart
</Button>
<Typography
variant="body1"
style={{ fontSize: "clamp(12px, 1.5vw, 18px)" }}
>
{formatPrice(product.price_in_cents)}
</Typography>
</CardActions>
</CardActionArea>
</ProductCard>
</Slide>
))}
</Container>
</AllProductsContainer>
</Fade>
<Snackbar open={open} autoHideDuration={6000} onClose={handleClose}>
<Alert onClose={handleClose} severity="success">
Item successfully added to cart.
</Alert>
</Snackbar>
</Layout>
)
}
export default AllBeansPage
export const productQuery = graphql`
query allBeanQuery {
allStrapiProduct(filter: { type: { eq: "beans" } }) {
nodes {
strapiId
id
description
created_at
price_in_cents
name
slug
type
thumbnail {
childImageSharp {
fluid(quality: 100, maxWidth: 150, maxHeight: 150) {
...GatsbyImageSharpFluid
}
}
}
}
}
}
`

Ok! So here is the solution for this. If you're using GatsbyJS and a CMS, sometimes there can be issues with rendering things on the server if you use Material-UI. I'm thinking something in Material-UI is causing a re-render on the page. Basically, in the product page component, I replaced
<Button
onClick={() => {
addToCart(product, 1)
handleClick()
}}
style={{ fontSize: "clamp(12px, 1.5vw, 16px)" }}>ADD TO CART
</Button>
with
<div
onClick={() => {
addToCart(product, 1)
handleClick()
}}
style={{ fontSize: "clamp(12px, 1.5vw, 16px)" }}>ADD TO CART
</div>
and then it worked. So if you're getting two of the same component rendering on a page and everything seems right, just double check if a component library you're using is causing problems.

Related

How to access values from context in a separate functional component

I'm trying to build a simple light mode/dark mode into my app I saw this example on Material UI for light/dark mode but I'm not sure how I can get access to the value for when the user clicks toggleColorMode in my Header component if it's being set in toggleColorMode function?
I guess my question is how can I get access to the value of light/dark mode of the context in my Header component if it's in a different function?
Here is my code.
import React, { useState, useEffect } from "react";
import MoreVertIcon from "#mui/icons-material/MoreVert";
import DarkModeIcon from "#mui/icons-material/DarkMode";
import LightModeIcon from "#mui/icons-material/LightMode";
import Paper from "#mui/material/Paper";
import { useTheme, ThemeProvider, createTheme } from "#mui/material/styles";
import IconButton from "#mui/material/IconButton";
import Navigation from "../Navigation/Navigation";
const ColorModeContext = React.createContext({ toggleColorMode: () => {} });
export const Header = (props) => {
const { mode } = props;
const theme = useTheme();
const colorMode = React.useContext(ColorModeContext);
console.log("mode is...", mode);
return (
<div className="header-container">
<Paper
elevation={3}
style={{ backgroundColor: "#1F1F1F", padding: "15px" }}
>
<div
className="header-contents"
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<div
className="logo"
style={{ display: "flex", alignItems: "center" }}
>
<img
src="/images/header-logo.png"
alt="URL Logo Shortener"
width={"50px"}
/>
<h1 style={{ color: "#ea80fc", paddingLeft: "20px" }}>
URL Shortener
</h1>
</div>
<div className="settings">
<IconButton
sx={{ ml: 1 }}
onClick={colorMode.toggleColorMode}
color="inherit"
aria-label="dark/light mode"
>
{theme.palette.mode === "dark" ? (
<DarkModeIcon
style={{
cursor: "pointer",
marginRight: "10px",
}}
/>
) : (
<LightModeIcon
style={{
cursor: "pointer",
marginRight: "10px",
}}
/>
)}
</IconButton>
<IconButton aria-label="settings">
<MoreVertIcon style={{ color: "#fff", cursor: "pointer" }} />
</IconButton>
</div>
</div>
</Paper>
{/* Navigation */}
<Navigation />
</div>
);
};
export default function ToggleColorMode() {
const [mode, setMode] = React.useState("light");
const colorMode = React.useMemo(
() => ({
toggleColorMode: () => {
setMode((prevMode) => (prevMode === "light" ? "dark" : "light"));
},
}),
[]
);
const theme = React.useMemo(
() =>
createTheme({
palette: {
mode,
},
}),
[mode]
);
return (
<ColorModeContext.Provider value={colorMode}>
<ThemeProvider theme={theme}>
<Header mode={mode} />
</ThemeProvider>
</ColorModeContext.Provider>
);
}
Read the documentation: createContext, useContext. You need to render a ContextProvider in your parent (or top-level) component, then you can get the data in any component in the tree like const { theme } = useContext(ColorModeContext);.
You don't need to pass the mode as props, put it as one of the values in the context and access it.
Here's how you would render it in your example:
<ColorModeContext.Provider value={{colorMode, theme}}>
<Header />
</ColorModeContext.Provider>
You can pass an object inside the value in the context provider, in other word you can pass the toggle function inside your value to be consumed in the childern. thus you gain an access to change your mode state.
Note that the way changes are determined can cause some issues when passing objects as value, this might trigger unnecessary rerendering see Caveats for more info. or refer to the useContext docs
<ColorModeContext.Provider
value={{ colorMode: colorMode, toggleColorMode: toggleColorMode }}
>
<ThemeProvider theme={theme}>
<Header />
</ThemeProvider>
</ColorModeContext.Provider>

MaterialUI React duplicate keys error from mui components

While creating a navbar I used multiple collapse components from MUI in order to create generate the menu items.
Its not a code breaking error but an annoying to have one. I get the following error in my console.
Warning: Encountered two children with the same key, `indexCollapseListItem`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.
div
./node_modules/#emotion/react/dist/emotion-element-699e6908.browser.esm.js/withEmotionCache/<#http://localhost:3000/static/js/bundle.js:5079:66
List#http://localhost:3000/static/js/bundle.js:18632:82
div
./node_modules/#emotion/react/dist/emotion-element-699e6908.browser.esm.js/withEmotionCache/<#http://localhost:3000/static/js/bundle.js:5079:66
div
./node_modules/#emotion/react/dist/emotion-element-699e6908.browser.esm.js/withEmotionCache/<#http://localhost:3000/static/js/bundle.js:5079:66
div
./node_modules/#emotion/react/dist/emotion-element-699e6908.browser.esm.js/withEmotionCache/<#http://localhost:3000/static/js/bundle.js:5079:66
Transition#http://localhost:3000/static/js/bundle.js:102860:30
Collapse#http://localhost:3000/static/js/bundle.js:12704:82
nav
./node_modules/#emotion/react/dist/emotion-element-699e6908.browser.esm.js/withEmotionCache/<#http://localhost:3000/static/js/bundle.js:5079:66
List#http://localhost:3000/static/js/bundle.js:18632:82
div
div
./node_modules/#emotion/react/dist/emotion-element-699e6908.browser.esm.js/withEmotionCache/<#http://localhost:3000/static/js/bundle.js:5079:66
Paper#http://localhost:3000/static/js/bundle.js:22524:82
./node_modules/#emotion/react/dist/emotion-element-699e6908.browser.esm.js/withEmotionCache/<#http://localhost:3000/static/js/bundle.js:5079:66
div
./node_modules/#emotion/react/dist/emotion-element-699e6908.browser.esm.js/withEmotionCache/<#http://localhost:3000/static/js/bundle.js:5079:66
Drawer#http://localhost:3000/static/js/bundle.js:13680:83
nav
./node_modules/#emotion/react/dist/emotion-element-699e6908.browser.esm.js/withEmotionCache/<#http://localhost:3000/static/js/bundle.js:5079:66
Box#http://localhost:3000/static/js/bundle.js:29040:72
div
./node_modules/#emotion/react/dist/emotion-element-699e6908.browser.esm.js/withEmotionCache/<#http://localhost:3000/static/js/bundle.js:5079:66
Box#http://localhost:3000/static/js/bundle.js:29040:72
NavBar#http://localhost:3000/static/js/bundle.js:1766:7
Dashboard
Routes#http://localhost:3000/static/js/bundle.js:101977:7
Router#http://localhost:3000/static/js/bundle.js:101914:7
BrowserRouter#http://localhost:3000/static/js/bundle.js:101391:7
App
Provider#http://localhost:3000/static/js/bundle.js:98572:15 react-dom.development.js:67
React 19
js index.js:8
factory react refresh:6
Webpack 3
Warning: Encountered two children with the same key, `indexCollapseListItem`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.
div
./node_modules/#emotion/react/dist/emotion-element-699e6908.browser.esm.js/withEmotionCache/<#http://localhost:3000/static/js/bundle.js:5079:66
List#http://localhost:3000/static/js/bundle.js:18632:82
div
./node_modules/#emotion/react/dist/emotion-element-699e6908.browser.esm.js/withEmotionCache/<#http://localhost:3000/static/js/bundle.js:5079:66
div
./node_modules/#emotion/react/dist/emotion-element-699e6908.browser.esm.js/withEmotionCache/<#http://localhost:3000/static/js/bundle.js:5079:66
div
./node_modules/#emotion/react/dist/emotion-element-699e6908.browser.esm.js/withEmotionCache/<#http://localhost:3000/static/js/bundle.js:5079:66
Transition#http://localhost:3000/static/js/bundle.js:102860:30
Collapse#http://localhost:3000/static/js/bundle.js:12704:82
nav
./node_modules/#emotion/react/dist/emotion-element-699e6908.browser.esm.js/withEmotionCache/<#http://localhost:3000/static/js/bundle.js:5079:66
List#http://localhost:3000/static/js/bundle.js:18632:82
div
div
./node_modules/#emotion/react/dist/emotion-element-699e6908.browser.esm.js/withEmotionCache/<#http://localhost:3000/static/js/bundle.js:5079:66
Paper#http://localhost:3000/static/js/bundle.js:22524:82
./node_modules/#emotion/react/dist/emotion-element-699e6908.browser.esm.js/withEmotionCache/<#http://localhost:3000/static/js/bundle.js:5079:66
Transition#http://localhost:3000/static/js/bundle.js:102860:30
Slide#http://localhost:3000/static/js/bundle.js:24602:7
Unstable_TrapFocus#http://localhost:3000/static/js/bundle.js:8464:7
div
./node_modules/#emotion/react/dist/emotion-element-699e6908.browser.esm.js/withEmotionCache/<#http://localhost:3000/static/js/bundle.js:5079:66
Portal#http://localhost:3000/static/js/bundle.js:8032:7
ModalUnstyled#http://localhost:3000/static/js/bundle.js:7245:7
Modal#http://localhost:3000/static/js/bundle.js:21375:82
./node_modules/#emotion/react/dist/emotion-element-699e6908.browser.esm.js/withEmotionCache/<#http://localhost:3000/static/js/bundle.js:5079:66
Drawer#http://localhost:3000/static/js/bundle.js:13680:83
nav
./node_modules/#emotion/react/dist/emotion-element-699e6908.browser.esm.js/withEmotionCache/<#http://localhost:3000/static/js/bundle.js:5079:66
Box#http://localhost:3000/static/js/bundle.js:29040:72
div
./node_modules/#emotion/react/dist/emotion-element-699e6908.browser.esm.js/withEmotionCache/<#http://localhost:3000/static/js/bundle.js:5079:66
Box#http://localhost:3000/static/js/bundle.js:29040:72
NavBar#http://localhost:3000/static/js/bundle.js:1766:7
Dashboard
Routes#http://localhost:3000/static/js/bundle.js:101977:7
Router#http://localhost:3000/static/js/bundle.js:101914:7
BrowserRouter#http://localhost:3000/static/js/bundle.js:101391:7
App
Provider#http://localhost:3000/static/js/bundle.js:98572:15 react-dom.development.js:67
​I am not setting this key in my code but why is it being set by default, which component is being assigned the same key again and again and how can I fix this.
My navbar code is the following for your reference.
//ASSETS
import AuraLogo from '../../../assets/images/logo/aura.png'
//REACT
import { useState } from 'react'
import { Link } from 'react-router-dom'
//MenuItems
import menuItems from '../dashboard/menu-items/index'
//MUI
import PropTypes from 'prop-types'
import AppBar from '#mui/material/AppBar'
import Box from '#mui/material/Box'
import CssBaseline from '#mui/material/CssBaseline'
import Divider from '#mui/material/Divider'
import Drawer from '#mui/material/Drawer'
import IconButton from '#mui/material/IconButton'
import List from '#mui/material/List'
import ListItemIcon from '#mui/material/ListItemIcon'
import ListItemText from '#mui/material/ListItemText'
import MenuIcon from '#mui/icons-material/Menu'
import Toolbar from '#mui/material/Toolbar'
import Typography from '#mui/material/Typography'
import Collapse from '#mui/material/Collapse'
import ListSubheader from '#mui/material/ListSubheader'
import ListItemButton from '#mui/material/ListItemButton'
import ExpandLess from '#mui/icons-material/ExpandLess'
import ExpandMore from '#mui/icons-material/ExpandMore'
import ArrowBackIosNew from '#mui/icons-material/ArrowBackIosNew'
const drawerWidth = 240
// TODO - fix duplicate keys error in components from MUI
function NavBar(props) {
const { window } = props
const [mobileOpen, setMobileOpen] = useState(false)
// TODO - change appbar title depending on the category clicked
const [title, setTitle] = useState()
const [open, setOpen] = useState({
dashboard: true,
categories: true,
subcategories: true,
products: true,
auth: true,
other: false,
})
const handleClick = (id) => {
setOpen((prevState) => ({ ...prevState, [id]: !prevState[id] }))
}
// const handleClick = (id) => {
// setOpen({ ...state, [id]: !open[id] });
// // setOpen((prevState => ({...prevState, [id]: !prevState[id]}))
// };
// const handleClick = (item) => {
// setOpen({
// item : !open
// })
// }
const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen)
}
const drawer = (
<div>
<Toolbar key={'LogoToolbar'}>
<img
as={Link}
src={AuraLogo}
style={{
maxWidth: '60%',
maxHeight: '60%',
paddingTop: '10px',
paddingBottom: '10px',
margin: '0 auto',
}}
/>
</Toolbar>
<Divider />
{menuItems.items.map(({id, icon, title, children}) => (
<>
<List
key={id+'ParentList'}
sx={{ width: '100%', maxWidth: 360, bgcolor: 'background.paper' }}
component='nav'
aria-labelledby='nested-list-subheader'
// subheader={
// <ListSubheader component='div' id='nested-list-subheader'>
// {item.subheader}
// </ListSubheader>
// }
>
<ListItemButton key={id+'listitembutton'} onClick={() => handleClick(id)}>
<ListItemIcon>{icon}</ListItemIcon>
<ListItemText primary={title} />
{open[id] ? <ExpandLess /> : <ExpandMore />}
</ListItemButton>
<Collapse key={id+'collapse'} in={open[id]} timeout='auto' unmountOnExit>
<List key={id+'listchildren'} component='div' disablePadding>
{children.map(({id, icon, title, url}) => (
<ListItemButton
component={Link}
to={`${url}`}
onClick={handleDrawerToggle}
key={id+'CollapseListItem'}
sx={{ pl: 3 }}>
<ListItemIcon>{icon}</ListItemIcon>
<ListItemText primary={title} />
</ListItemButton>
))}
</List>
</Collapse>
</List>
<Divider />
</>
))}
<Link style={{textDecoration: 'none', color: '#202020'}} to='/'>
<ListItemButton key={'returnlistitembutton'}>
<ListItemIcon><ArrowBackIosNew/></ListItemIcon>
<ListItemText primary='Πίσω στους καταλόγους' />
</ListItemButton>
</Link>
</div>
)
const container =
window !== undefined ? () => window().document.body : undefined
return (
<Box sx={{ display: 'flex' }}>
<CssBaseline />
<AppBar
position='fixed'
sx={{
width: { sm: `calc(100% - ${drawerWidth}px)` },
ml: { sm: `${drawerWidth}px` },
}}>
<Toolbar>
<IconButton
color='inherit'
aria-label='open drawer'
edge='start'
onClick={handleDrawerToggle}
sx={{ mr: 2, display: { sm: 'none' } }}>
<MenuIcon />
</IconButton>
<Typography variant='h6' noWrap component='div'>
Dashboard
{/* TODO - add more functionallity to appbar */}
</Typography>
</Toolbar>
</AppBar>
<Box
component='nav'
sx={{ width: { sm: drawerWidth }, flexShrink: { sm: 0 } }}
aria-label='mailbox folders'>
{/* The implementation can be swapped with js to avoid SEO duplication of links. */}
<Drawer
key={'drawer'}
container={container}
variant='temporary'
open={mobileOpen}
onClose={handleDrawerToggle}
ModalProps={{
keepMounted: true, // Better open performance on mobile.
}}
sx={{
display: { xs: 'block', sm: 'none' },
'& .MuiDrawer-paper': {
boxSizing: 'border-box',
width: drawerWidth,
},
}}>
{drawer}
</Drawer>
<Drawer
variant='permanent'
sx={{
display: { xs: 'none', sm: 'block' },
'& .MuiDrawer-paper': {
boxSizing: 'border-box',
width: drawerWidth,
},
}}
open>
{drawer}
</Drawer>
</Box>
<Box
component='main'
sx={{
flexGrow: 1,
p: 3,
width: { sm: `calc(100% - ${drawerWidth}px)` },
}}>
<Toolbar />
{props.children}
</Box>
</Box>
)
}
NavBar.propTypes = {
/**
* Injected by the documentation to work in an iframe.
* You won't need it on your project.
*/
window: PropTypes.func,
}
export default NavBar
The random key properties that appear almost everywhere was my quest in finding which component generates this error but with no luck.
As always thanks in advance and if by any chance you have any suggestions or solutions... I'm all ears!
You can try something like this.
<List
sx={{ width: '100%', maxWidth: 360, bgcolor: 'background.paper' }}
component='nav'
aria-labelledby='nested-list-subheader'
>
{menuItems.items.map(({id, icon, title, children}, idx) => (
<ListItemButton
onClick={() => handleClick(id)}
key={idx}
>
<ListItemIcon>
{icon}
</ListItemIcon>
<ListItemText
primary={title}
/>
{open[id] ? <ExpandLess /> : <ExpandMore />}
</ListItemButton>
<Collapse in={open[id]} timeout='auto' unmountOnExit>
<List component='div' disablePadding>
{children.map(({id, icon, title, url}, subidx) => (
<ListItemButton
component={Link}
to={`${url}`}
onClick={handleDrawerToggle}
key={subidx}
sx={{ pl: 3 }}
>
<ListItemIcon>
{icon}
</ListItemIcon>
<ListItemText
primary={title}
/>
</ListItemButton>
))}
</List>
</Collapse>
))}
</List>
Here :
{menuItems.items.map(({id, icon, title, children}) => (
<>
<List
key={id+'ParentList'}
Your key must be provide on the first child, in your case the React Fragment so :
{menuItems.items.map(({id, icon, title, children}) => (
<Fragment key={id+'ParentList'}>
<List ...
You need to put a uniq key, the index from map can provide the same issue if you use 2 times (0, 1, 2, 3 === 0, 1, 2, 3), you can use an add string or you can use a methods to generate an uniq key :
uuid nom package
nanoid from redux-toolkit (perfect if you use Redux, don't install it just for that)
create a method from Math.random like Math.floor(Math.random()*10000000).toString(16) ( => hexa key)
create a method from a prefix you give in method params and adding an element from a new Date() or timestamp
...
With this, you sure to create a new and uniq key.
With time, in order, I prefer Nanoid or Math random.

Why does using prop.children on a Rebass Box component output a console.error in Chrome?

I have an app built with NextJS and I have a component composed from the Rebass Library which works, but it gives this warning in the console:
Here is the component:
// Container.js
import { Box } from "rebass"
export const Container = (props) => (
<Box
sx={{
maxWidth: "1240px",
mx: "auto",
px: 3,
}}
>
{props.children}
</Box>
)
And the index component:
import { Container } from "./Container"
const Index = (props) => (
<Container>
<div>Hello, World</div>
</Container>
)
export default Index
How can I get rid of this error message?
So it had nothing to do with the above components but rather another component in another file.
// Navbar.js
import { Flex, Link, Text } from "rebass"
import { Container } from "./Container"
export const Nav = (props) => (
<Container>
<Flex
px={2}
height={70}
color="white"
sx={{ background: `${(props) => props.theme.colors.background}` }}
// Using the line above causes the error
sx={{ background: "background" }} // use this line instead
alignItems="center"
>
<Text p={2} fontWeight="bold">
Company
</Text>
<Flex mx="auto" />
<Link variant="nav" href="#!">
Link
</Link>
</Flex>
</Container>
)
This is the prescribed way to get a themed value into a Rebass component. It didn't work for me so that is why I tried a function.

Render Method Component Bug - Invalid hook call. Hooks can only be called inside of the body of a function component

I'm experiencing an issue correctly rendering data in a basic component?
questionThree.js
import React, {Component} from 'react';
import { makeStyles } from '#material-ui/core/styles';
import List from '#material-ui/core/List';
import Paper from '#material-ui/core/Paper';
import Android from "#material-ui/icons/Android";
import Pets from "#material-ui/icons/Pets";
import BugReport from "#material-ui/icons/BugReport";
import QuestionListItem from './questionListItem';
import { createRowData } from './mocks';
const createMockData = () =>{
/* Please do not refactor this function */
return [
createRowData({species: 'Robot', name: 'T-100', Icon: Android, description: "Likes long walks, walking over the bones of it's enemies"}),
createRowData({species: 'Bug', name:'Barry', Icon: BugReport, description: "Likes long walks, and creating problems in all your code"}),
createRowData({species: 'Rabbit', name:'Roger', Icon: Pets, description: "Likes long walks and getting to know the inner you"}),
createRowData({species: null, name: null, Icon: null, description: null}),
]
};
const useStyles = makeStyles(() => ({
container:{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100%',
},
root: {
width: '100%',
},
inline: {
display: 'inline',
},
}));
class questionThree extends Component {
render(){
const classes = useStyles();
const mockData = createMockData();
return (
<div className={classes.container}>
<Paper>
<List className={classes.root}>
{mockData.map((item, i) =>{
return <QuestionListItem item={item} key={item.id} divider={i !== mockData.length -1}/>
})}
</List>
</Paper>
</div>
);
}
}
export default questionThree
*question.js*
import React from 'react'
import Typography from "#material-ui/core/Typography";
import Solution from './images/solution.png'
import { CardMedia } from '#material-ui/core';
const question = ()=>{
return (
<div>
<Typography variant="h4" gutterBottom>
Question Three
</Typography>
<Typography variant="h5" gutterBottom>
List on the fritz
</Typography>
<Typography variant="body1" gutterBottom>
This task revolves around a bug in the render method of a basic component.
</Typography>
<Typography variant="body1" gutterBottom>
Your task if you choose to accept it, is to resolve this bug (displayed on the right and in the console) .
</Typography>
<Typography variant="body1" gutterBottom>
As with all the questions in this tech test, you may or may not wish to refactor some of the code.
</Typography>
<Typography variant="h6" gutterBottom>
Below is what the final solution should look like. (GUID'S will vary)
</Typography>
<CardMedia
image={Solution}
style={{
width: '100%',
height: 500,
backgroundSize: 'contain',
}}
title="The Solution"
/>
</div>
)
};
export default question
index.js
import React from 'react';
import QuestionThree from './questionThree';
import Question from './question'
import ErrorBoundary from '../../components/errorBoundary'
const questionThree = () =>{
return (
<ErrorBoundary question={Question}>
<QuestionThree/>
</ErrorBoundary>
)
}
export default questionThree;
I've tried a number of solutions and most of the solutions available on Google and nothing seems to work. I did try the solution provided at https://reactjs.org/warnings/invalid-hook-call-warning.html, however I hit an issue with getting the results back from the second step of the instruction.
What am I missing? Is this a 'I should've gone to Specsavers' moment?
makeStyles returns a hook called useStyles which can only be called inside a functional component. That is why you are getting the error.
In your case change questionThree to be a functional component
const questionThree = (props) => {
const classes = useStyles();
const mockData = createMockData();
return (
<div className={classes.container}>
<Paper>
<List className={classes.root}>
{mockData.map((item, i) =>{
return <QuestionListItem item={item} key={item.id} divider={i !== mockData.length -1}/>
})}
</List>
</Paper>
</div>
);
}
export default questionThree
To use it for class components, make use of withStyles HOC
import { withStyles } from '#material-ui/core/styles';
const styles = {
container:{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100%',
},
root: {
width: '100%',
},
inline: {
display: 'inline',
},
}
class questionThree extends Component {
render(){
const {classes} = props
const mockData = createMockData();
return (
<div className={classes.container}>
<Paper>
<List className={classes.root}>
{mockData.map((item, i) =>{
return <QuestionListItem item={item} key={item.id} divider={i !== mockData.length -1}/>
})}
</List>
</Paper>
</div>
);
}
}
export default withStyles(styles)(questionThree);

React Material-UI not justify-content will not work for all of my Cards

I'm having really bizarre issue with my code. I am using React to map a Redux state onto Material-UI cards. When I was testing different styling on my cards the other day I was able to make different classes and update the card appearance however I liked. However today when I added new information to my database the new cards will not take all of the styling from the Material-UI database. Specifically it seems like the new cards are not effected by justifyContent.
This is a difficult issue for me to explain but basically I have a database of information being mapped onto multiple different cards. Some of the cards are accepting the Material-UI styling other cards are not.
I would like to post a jsfiddle but I would need to link it to my database and I'm not sure that can be done.
Here is the page which is mapping my redux state:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import ProjectItem from '../ProjectItem/ProjectItem'
import {withRouter} from 'react-router-dom'
import Header from '../Header/Header'
import { withStyles } from '#material-ui/core';
const mapReduxStateToProps = (reduxState) => ({reduxState});
const styles = theme => ({
bodyContent: {
marginTop: 100,
}
})
class ProjectPage extends Component {
//This calls the getProjects function on the page load
componentDidMount() {
this.getProjects();
}
//This function dispatches an action to our index page
getProjects = () => {
console.log('running')
this.props.dispatch({ type: 'GET_PROJECT'})
}
render() {
const {classes} = this.props
return (
<div>
<Header />
<div className= {classes.bodyContent}>
{this.props.reduxState.projects.map((project,index) => {
return(
<ProjectItem
key = {index}
id = {project.id}
name ={project.name}
description = {project.description}
thumbnail = {project.thumbnail}
website = {project.website}
github = {project.github}
tag_id = {project.tag_id}
/>
)
})}
</div>
</div>
);
}
}
export default withRouter(connect(mapReduxStateToProps)(withStyles(styles)(ProjectPage)));
And here is the page with my styling.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import './ProjectItem.css'
import Card from '#material-ui/core/Card';
import CardMedia from '#material-ui/core/CardMedia'
import CardContent from '#material-ui/core/CardContent'
import Typography from '#material-ui/core/Typography'
import { withStyles } from '#material-ui/core';
const mapReduxStateToProps = (reduxState) => ({reduxState});
const styles = theme => ({
card: {
display: 'flex',
width: 550,
alignSelf: 'center',
height: 150,
paddingTop: 5,
paddingBottom:5,
},
details: {
display: 'flex',
flexDirection: 'column'
},
thumbnail: {
height: 150,
minWidth: 150
},
contentBody: {
justifyContent: 'space-between',
textAlign: 'left',
alignItems: 'center',
display : 'inline-flex',
},
toolbar: theme.mixins.toolbar
})
class ProjectItem extends Component {
render() {
const {classes} = this.props
return (
<div className='container'>
<Card className={classes.card}>
<CardMedia
className={classes.thumbnail}
image={this.props.thumbnail}
title="Project Image"
/>
<div className={classes.details}>
<CardContent className={classes.contentBody}>
<Typography variant='h5'>
{this.props.name}
</Typography>
<Typography variant='h6'>
<a href={this.props.github}>GitHub</a>
</Typography>
<Typography variant='h6'>
<a href={this.props.website}>Website</a>
</Typography>
<Typography variant='h6'>
{this.props.tag_id}
</Typography>
</CardContent>
<CardContent className={classes.content}>
<Typography>
{this.props.description}
</Typography>
</CardContent>
</div>
</Card>
</div>
);
}
}
export default connect(mapReduxStateToProps)(withStyles(styles)(ProjectItem));

Resources