Multiple buttons triggering the same modal component - javascript

I have an videos array, which in turn has objects of type Video (typing below).
I need that when clicking on the button corresponding to a specific video, I can open only one modal with the information of the clicked video.
interface VideosInfo {
id: number;
title: string;
url: string;
quiz: boolean;
}
interface PagePros {
videos: VideosInfo[]
}
Below is the component that renders the array of videos through a map, notice that inside the map, I have an onClick function that calls the modal.
import { VideoModal } from '../index';
import { useVideos } from '../../../../hooks/Videos';
export const Videos: React.FC<VideoProps> = ({ module_id }) => {
const [modalOpen, setModalOpen] = useState<boolean>(false);
const { getVideos, videos, loadingVideos } = useVideos();
const handleCloseModal = () => {
setModalOpen(false);
};
const VideosData = () => {
if (videos.length) {
return (
<List dense>
{videos?.map(video => (
<div key={video.id}>
<ListItem onClick={() => setModalOpen(true)} button>
<ListItemText primary={video.title} />
</ListItem>
<Divider />
<VideoModal
open={modalOpen}
handleClose={() => handleCloseModal()}
video={video}
video_id={video.id}
/>
</div>
))}
</List>
);
}
if (!videos.length && !loadingVideos) {
return (
<Typography variant="body1">
Não existem vídeos cadastrados neste módulo.
</Typography>
);
}
return <LoadingScreen text="Carregando vídeos..." />;
};
useEffect(() => {
getVideos(module_id);
}, [module_id, getVideos]);
return (
<Grid container spacing={2}>
<Grid item xs={12} md={12}>
<VideosData />
</Grid>
<Grid item xs={12} md={12}>
<Button variant="text" color="primary">
Novo Vídeo
</Button>
</Grid>
</Grid>
);
};
And below the VideoModal component:
export const VideoModal: React.FC<ModalProps> = ({
video,
open,
handleClose,
video_id,
}) => {
console.log('videos modal', video);
return (
<Dialog
open={open}
aria-labelledby="form-dialog-title"
onClose={handleClose}
>
<DialogTitle id="form-dialog-title">Subscribe</DialogTitle>
<DialogContent>
<h2>test</h2>
</DialogContent>
</Dialog>
);
};
I understand that the modal uses the "open" property to define whether it is open or not, but when I click the button and perform the setModalOpen, it renders a modal for each object in the array. I don't understand how I could assemble this correctly.

I solved it as follows, created a state called videoToModal of type VideosInfo and a function called handleModalOpen, passed the video parameter to the function, and in the function stored this video in the videoToModal state.
I instantiated the VideoModal component outside the map (obviously should have done this before) and passed the state to the VideoModal component's video parameter.
Below is the complete code for the component.
import React, { useEffect, useState } from 'react';
import {
Button,
Divider,
Grid,
IconButton,
List,
ListItem,
ListItemSecondaryAction,
ListItemText,
Tooltip,
Typography,
} from '#material-ui/core';
import { Delete, QuestionAnswer } from '#material-ui/icons';
import { useVideos } from '../../../../hooks/Videos';
import { useStyles } from './styles';
import { LoadingScreen } from '../../../../components/CustomizedComponents';
import { VideoModal } from '../index';
import { VideosInfo } from '../../../../hooks/Videos/types';
import { VideoProps } from './types';
export const Videos: React.FC<VideoProps> = ({ module_id }) => {
const [openModal, setOpenModal] = useState<boolean>(false);
const [videoToModal, setVideoToModal] = useState<VideosInfo>();
const classes = useStyles();
const { getVideos, videos, loadingVideos } = useVideos();
const handleCloseModal = () => {
setOpenModal(false);
};
const handleOpenModal = (video: VideosInfo) => {
setVideoToModal(video);
setOpenModal(true);
};
const VideosData = () => {
if (videos.length) {
return (
<List dense>
{videos?.map(video => (
<div key={video.id}>
<ListItem
className={classes.listItem}
onClick={() => handleOpenModal(video)}
button
>
<ListItemText
primary={video.title}
className={classes.listItemText}
/>
<ListItemSecondaryAction>
<Tooltip
placement="top"
title={
video.Quizzes?.length
? 'Clique para ver as perguntas'
: 'Clique para iniciar o cadastro de perguntas'
}
>
<IconButton edge="end" aria-label="delete">
<QuestionAnswer
color={video.Quizzes?.length ? 'primary' : 'action'}
/>
</IconButton>
</Tooltip>
<Tooltip placement="top" title="Deletar Vídeo">
<IconButton edge="end" aria-label="delete">
<Delete color="secondary" />
</IconButton>
</Tooltip>
</ListItemSecondaryAction>
</ListItem>
<Divider />
</div>
))}
<VideoModal
open={openModal}
handleClose={() => handleCloseModal()}
video={videoToModal}
/>
</List>
);
}
if (!videos.length && !loadingVideos) {
return (
<Typography variant="body1">
Não existem vídeos cadastrados neste módulo.
</Typography>
);
}
return <LoadingScreen text="Carregando vídeos..." />;
};
useEffect(() => {
getVideos(module_id);
}, [module_id, getVideos]);
return (
<Grid container spacing={2} className={classes.container}>
<Grid item xs={12} md={12}>
<VideosData />
</Grid>
<Grid item xs={12} md={12}>
<Button variant="text" color="primary">
Novo Vídeo
</Button>
</Grid>
</Grid>
);
};

Instead of using
<div key={video.id}>
can you use,
<List dense>
{videos?.map((video,i) => (
<div key={i}>
<ListItem onClick={() => setModalOpen(true)} button>
<ListItemText primary={video.title} />
</ListItem>
<Divider />
<VideoModal
open={modalOpen}
handleClose={() => handleCloseModal()}
video={video}
video_id={video.id}
/>
</div>
))}
</List>

Related

Can a component change the data of another component?

I have a component, GameGrid which makes an axios call to get the data that I pass as props to another component, GameCard. I currently have a Header component that is in the same file as GameGrid. The header has buttons which when clicked, changes the endpoint that the GameGrid is calling. I am trying to figure out a way to separate the two components, Header and GameGrid, but still have the buttons on the header determine which endpoint the GameGrid should call. Also, is the GameGrid the right place to have the data being called?
GameGrid.jsx
import React, { useEffect, useState } from "react";
import AppBar from '#mui/material/AppBar';
import Toolbar from '#mui/material/Toolbar';
import Box from '#mui/material/Box';
import IconButton from '#mui/material/IconButton';
import Grid from '#mui/material/Grid';
import axios from "axios";
import GameCard from "./GameCard";
export default function GameGrid() {
const [data, setData] = useState(); // Data Hook
const [route, setRoute] = useState('/')
useEffect(() => {
const getData = async () => {
try {
const { data } = await axios.get(`${process.env.REACT_APP_URL}${route}`);
console.log(data);
setData(data);
} catch (err) {
console.log(err);
}
};
getData();
}, [route]);
const createCards = (data) => {
return <GameCard
key = {data.id}
id = {data.id}
url = {data.game_url}
thumbnail = {data.thumbnail}
title = {data.title}
platform = {data.platform}
description = {data.short_description}
genre = {data.genre}
publisher = {data.publisher}
developer = {data.developer}
/>;
}
const Header = () => {
return (
<Box sx ={{ flexGrow: 1 }}>
<AppBar position="fixed">
<Toolbar>
<Grid sx={{ flexGrow: 1,
border: '1px solid white'}} container spacing={2}>
<Grid sx={{
}}>
<IconButton onClick={() => setRoute('/')}>
<img src='computer-mouse.png' alt='Logo' id='icon'/>
</IconButton>
<IconButton onClick={() => setRoute('/shooter')}>
<img src="shooter.png" alt="Shooter Games" />
</IconButton>
</Grid>
<Grid>
</Grid>
</Grid>
</Toolbar>
</AppBar>
</Box>
)
}
return (
<>
<Header/>
<div className="card-container">
{data?.map(createCards)}
</div>
</>
)
}
GameCard.jsx
import React from "react";
import Card from "#mui/material/Card";
import CardContent from "#mui/material/CardContent";
import CardMedia from "#mui/material/CardMedia";
import Typography from "#mui/material/Typography";
import { CardActionArea, Chip } from "#mui/material";
import Stack from '#mui/material/Stack';
export default function GameCard(props) {
return (
<Card sx={{ width: 550, margin: 2}} key={props.id} id={props.id} >
<CardActionArea href={props.url}>
<CardMedia
component="img"
height="fit-content"
image={props?.thumbnail}
/>
<CardContent>
<Stack direction="row" justifyContent="space-between">
<Typography gutterBottom variant="h6" component="div">
{props.title}
</Typography>
<Chip label={props.platform} size="small"/>
</Stack>
<Typography gutterBottom variant="body1" id="description-text"
color="text.secondary"
sx={{
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap'
}}>
{props.description}
</Typography>
<Stack direction="row" justifyContent="space-between">
<Chip label={props.genre} size="small" />
<Chip label={props.publisher} size="small" />
<Chip label={props.developer} size="small" />
</Stack>
</CardContent>
</CardActionArea>
</Card>
);
}
To separate Header, simply define it with all the props it requires. In this instance, that appears to be the setRoute function though you can call it whatever you like.
For example
// Header.jsx
const Header = ({ onRouteClick }) => (
<Box sx={{ flexGrow: 1 }}>
{/* ... */}
<IconButton onClick={() => onRouteClick("/")}>
<img src="computer-mouse.png" alt="Logo" id="icon" />
</IconButton>
<IconButton onClick={() => onRouteClick("/shooter")}>
<img src="shooter.png" alt="Shooter Games" />
</IconButton>
{/* ... */}
</Box>
);
export default Header;
and in your GameGrid component, import Header from "./Header" and use...
<Header onRouteClick={setRoute} />
If you were using Typescript (highly recommended), the API for Header would look something like this
interface HeaderProps {
onRouteClick: (route: string) => void;
}
const Header = ({ onRouteClick }: HeaderProps) => (
// ...
);

# Object.convertDocToObj TypeError: Cannot read property '_id' of null

I'm having a problem, and I don't really know how to fix this. Been at this for a while now.
I get this typeError with mongoDB:
utils\db.js (38:16) # Object.convertDocToObj
TypeError: Cannot read property '_id' of null
36 |
37 | function convertDocToObj(doc) {
> 38 | doc._id = doc._id.toString();
| ^
39 | doc.createdAt = doc.createdAt.toString();
40 | doc.updatedAt = doc.updatedAt.toString();
41 | return doc;
This my db.js
import mongoose from 'mongoose';
const connection = {};
async function connect() {
if (connection.isConnected) {
console.log('already connected');
return;
}
if (mongoose.connections.length > 0) {
connection.isConnected = mongoose.connections[0].readyState;
if (connection.isConnected === 1) {
console.log('use previous connection');
return;
}
await mongoose.disconnect();
}
const db = await mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('new connection');
connection.isConnected = db.connections[0].readyState;
}
async function disconnect() {
if (connection.isConnected) {
if (process.env.NODE_ENV === 'production') {
await mongoose.disconnect();
connection.isConnected = false;
} else {
console.log('not disconnected');
}
}
}
function convertDocToObj(doc) {
doc._id = doc._id.toString();
doc.createdAt = doc.createdAt.toString();
doc.updatedAt = doc.updatedAt.toString();
return doc;
}
const db = { connect, disconnect, convertDocToObj };
export default db;
This is my [slug].js
import React from 'react'
import useStyles from '../../utils/styles'
import NextLink from 'next/link'
import { Link, Grid, List, ListItem, Typography, Card, Button } from '#material-ui/core'
import Image from 'next/image'
import Layout from '../../components/Layout'
import Product from '../../models/Product'
import db from '../../utils/db';
export default function ProductScreen(props) {
const { product } = props
const classes = useStyles()
if(!product) {
return <div>Product Not Found</div>
}
return (
<Layout title={product.name} description={product.description}>
<div className={classes.section}>
<NextLink href="/" passHref>
<Link>Back to Products</Link>
</NextLink>
</div>
<Grid container spacing={1}>
<Grid item md={6} xs={12}>
<Image src={product.image} alt={product.name} width={640} height={640} layout="responsive"/>
</Grid>
<Grid item md={3} xs={12}>
<List>
<ListItem>
<Typography component="h1" variant='h1'>
{product.name}
</Typography>
</ListItem>
<ListItem>
<Typography>
Category: {product.category}
</Typography>
</ListItem>
<ListItem>
<Typography>
Brand: {product.brand}
</Typography>
</ListItem>
<ListItem>
<Typography>
Rating: {product.rating} stars ({product.numReviews} reviews)
</Typography>
</ListItem>
<ListItem>
<Typography>Description: {product.description}</Typography>
</ListItem>
</List>
</Grid>
<Grid item md={3} xs={12}>
<Card>
<List>
<ListItem>
<Grid container>
<Grid item xs={6}><Typography>Price</Typography></Grid>
<Grid item xs={6}><Typography>${product.price}</Typography></Grid>
</Grid>
</ListItem>
<ListItem>
<Grid container>
<Grid item xs={6}><Typography>Status</Typography></Grid>
<Grid item xs={6}><Typography>{product.countInStock>0?'In stock':'Unavailable'}</Typography></Grid>
</Grid>
</ListItem>
<ListItem>
<Button fullWidth variant="contained" color='primary'>
Add to Cart
</Button>
</ListItem>
</List>
</Card>
</Grid>
</Grid>
</Layout>
)
}
export async function getServerSideProps(context) {
const { params } = context;
const { slug } = params;
await db.connect();
const product = await Product.findOne({ slug }).lean();
await db.disconnect();
return {
props: {
product: db.convertDocToObj(product),
},
};
}
This is my index.js
import Layout from '../components/Layout'
import NextLink from 'next/link'
import { Grid, Card, CardActionArea, CardMedia, CardContent, CardActions, Button, Typography } from '#material-ui/core';
import Product from '../models/Product';
import db from '../utils/db';
export default function Home(props) {
const {products} = props;
return (
<Layout>
<div>
<h1>Products</h1>
<Grid container spacing={3}>
{data.products.map((product) => (
<Grid item md={4} key={product.name}>
<Card>
<NextLink href={`/product/${product.slug}`} passHref>
<CardActionArea>
<CardMedia
component="img"
image={product.image}
title={product.name}
></CardMedia>
<CardContent>
<Typography>
{product.name}
</Typography>
</CardContent>
</CardActionArea>
</NextLink>
<CardActions>
<Typography>${product.price}</Typography>
<Button size='small' color='primary'>
Add to Cart...
</Button>
</CardActions>
</Card>
</Grid>
))}
</Grid>
</div>
</Layout>
)
}
export async function getServerSideProps() {
await db.connect();
const products = await Product.find({}).lean();
await db.disconnect()
return {
props: {
products: products.map(db.convertDocToObj),
},
}
}
And here is my index.js api
import nc from 'next-connect';
import Product from '../../../models/Product';
import db from '../../../utils/db';
const handler = nc();
handler.get(async (req, res) => {
await db.connect();
const products = await Product.find({});
await db.disconnect();
res.send(products);
});
export default handler;
If you are using the lean() method, you won't have the id. The lean() method disables the hydration process which is converting the original result from POJO to Mongoose document. This means that the results you have are in POJO which doesn't include the id.
Try removing the lean() and see if that helps.
Also, you can use mongoose-lean-id module to include the id in the results while using lean(), I personally never used it.

React - Close MUI drawer from nested menu

I'm using this excellent example (Nested sidebar menu with material ui and Reactjs) to build a dynamic nested menu for my application. On top of that I'm trying to go one step further and put it into a Material UI appbar/temporary drawer. What I'd like to achieve is closing the drawer when the user clicks on one of the lowest level item (SingleLevel) however I'm having a tough time passing the toggleDrawer function down to the menu. When I handle the click at SingleLevel I consistently get a 'toggle is not a function' error.
I'm relatively new to this so I'm sure it's something easy and obvious. Many thanks for any answers/comments.
EDIT: Here's a sandbox link
https://codesandbox.io/s/temporarydrawer-material-demo-forked-v11ur
Code is as follows:
Appbar.js
export default function AppBar(props) {
const [drawerstate, setDrawerstate] = React.useState(false);
const toggleDrawer = (state, isopen) => (event) => {
if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
return;
}
setDrawerstate({ ...state, left: isopen });
};
return (
<Box sx={{ flexGrow: 1 }}>
<AppBar position="static" color="secondary">
<Toolbar>
<IconButton
size="large"
edge="start"
color="primary"
aria-label="menu"
onClick={toggleDrawer('left', true)}
>
<MenuIcon />
</IconButton>
<img src={logo} alt="logo" />
</Toolbar>
<Drawer
anchor='left'
open={drawerstate['left']}
onClose={toggleDrawer('left', false)}
>
<Box>
<AppMenu toggleDrawer={toggleDrawer} />
</Box>
</Drawer>
</AppBar>
</Box >
)
}
Menu.js
export default function AppMenu(props) {
return MenuItemsJSON.map((item, key) => <MenuItem key={key} item={item} toggleDrawer={props.toggleDrawer} />);
}
const MenuItem = ({ item, toggleDrawer }) => {
const MenuComponent = hasChildren(item) ? MultiLevel : SingleLevel;
return <MenuComponent item={item} toggleDrawer={toggleDrawer} />;
};
const SingleLevel = ({ item, toggleDrawer }) => {
const [toggle, setToggle] = React.useState(toggleDrawer);
return (
<ListItem button onClick={() => { toggle('left', false) }}>
<ListItemIcon>{item.icon}</ListItemIcon>
<ListItemText primary={item.title} />
</ListItem>
);
};
const MultiLevel = ({ item }) => {
const { items: children } = item;
const [open, setOpen] = useState(false);
const handleClick = () => {
setOpen((prev) => !prev);
};
return (
<React.Fragment>
<ListItem button onClick={handleClick}>
<ListItemIcon>{item.icon}</ListItemIcon>
<ListItemText primary={item.title} secondary={item.description} />
{open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{children.map((child, key) => (
<MenuItem key={key} item={child} />
))}
</List>
</Collapse>
</React.Fragment>
);
};
You shouldn't call a react hook inside of any function that is not a react component. Please see React Rules of Hooks
What you could do instead is pass setToggle directly into the Drawer component as a prop and do something like this for it's onClick attribute:
onClick={() => setToggle(<value>)}

React js useState throws error when state is passed as prop

I am writing an ecommerce webshop using React js and Commerce.js
I am very confused as I am not able to identify the precise problem. But here's how it's happening:
My App.js:
const App = () => {
const [products, setProducts] = useState([]);
const [cart, setCart] = useState([]);
const fetchProducts = async () => {
const { data } = await commerce.products.list();
setProducts(data);
};
const fetchCart = async () => {
setCart(await commerce.cart.retrieve());
};
const handleAddToCart = async (productId, quantity) => {
const item = await commerce.cart.add(productId, quantity);
setCart(item.cart);
};
useEffect(() => {
fetchProducts();
fetchCart();
}, []);
console.log(cart);
return (
<>
<Navbar totalItems={cart.total_items} />
{/* <Products products={products} onAddToCart={handleAddToCart} /> */}
{/* <Cart cartItems={cart} /> */}
</>
);
Now when I uncomment the <Cart cartItems={cart} />, React js Throws an error
This is the error in from the console
Uncaught Error: Objects are not valid as a React child (found: object with keys {raw, formatted, formatted_with_symbol, formatted_with_code}). If you meant to render a collection of children, use an array instead.
Interestingly enough, the single item is being passed on through the Cart.js but not without the error.
Here's Cart.js for Reference
const Cart = ({ cartItems }) => {
const classes = useStyles();
const EmptyCart = () => {
return (
<Typography variant="subtitle1">
You have no items in your cart. Start adding some :)
</Typography>
);
};
const FilledCart = () => {
return (
<>
<Grid container spacing={3}>
{cartItems.line_items.map((item) => (
<Grid item xs={12} sm={4} key={item.id}>
<CartItem items={item} />
</Grid>
))}
</Grid>
<div className={classes.cardDetails}>
<Typography variant="h4">
Subtotal: {cartItems.subtotal.formatted_with_symbol}
</Typography>
<div>
<Button
className={classes.emptyButton}
size="large"
type="button"
variant="contained"
color="secondary"
>
Empty Cart
</Button>
<Button
className={classes.checkoutButton}
size="large"
type="button"
variant="contained"
color="primary"
>
Checkout
</Button>
</div>
</div>
</>
);
};
if (!cartItems.line_items)
return <Typography variant="h4">Loading...</Typography>;
return (
<Container>
<div className={classes.toolbar} />
<Typography className={classes.title} variant="h3">
Your Shopping Cart
</Typography>
{!cartItems.line_items.length ? <EmptyCart /> : <FilledCart />}
</Container>
);
};
Update:
Here's what Cart object looks like

React - Can't find the leak, infinite state declaration suspected

I'm a complete beginner in React and I was pretty happy with my first app since I, maybe, have a memory leak ? My app is very laggy after entering value in the input, after click on the add button, at bottom right, and I suspect that I don't use 'useState' like I should ? I dunno, I've been searching for hours :( ...
Here's the app : https://n3g.gitlab.io/react-conso-energie/
Here's the code :
In the App.js (parent) :
import React, { useState } from 'react'
import firebase from './firebase'
import AddForm from './AddForm'
import ListingTable from './ListingExpansionPanels'
import moment from 'moment'
// MATERIAL UI
import { CssBaseline } from '#material-ui/core'
import Grid from '#material-ui/core/Grid'
import Snackbar from '#material-ui/core/Snackbar'
import MuiAlert from '#material-ui/lab/Alert'
import './styles.css'
export default function App () {
// BDD
const dbRef = firebase.database().ref('items')
// LISTING DATA
const [listingData, setListingData] = useState([{}])
dbRef.once('value', (snapshot) => {
const releves = snapshot.val()
const listingData = []
for (const [key, releve] of Object.entries(releves)) {
listingData.push({
key: key,
month: releve.month,
gaz: releve.gaz,
electricite: releve.electricite,
total: releve.total,
submissionDate: releve.submissionDate
})
}
const listingDataSorted = listingData.sort((a, b) => (a.submissionDate > b.submissionDate) ? 1 : -1)
setListingData(listingDataSorted)
})
const lastItemIndex = listingData.length - 1
// MONTHS
const [selectedDate, handleDateChange] = useState(new Date())
const dateFormat = moment(selectedDate).format('MMMM')
// ELECTRICITÉ
const constElec = { prix: 0.15356, abo: 117.56 }
const [kw, setKw] = useState('')
const diffElec = kw - listingData[lastItemIndex].electricite
const resultatElec = Math.round((constElec.prix * diffElec + (constElec.abo / 12)) * 1e2) / 1e2
// GAZ
const constGaz = { prix: 0.0681, abo: 215.73, indice: 11.34 }
const [m3, setm3] = useState('')
const diffGaz = m3 - listingData[lastItemIndex].gaz
const resultatGaz = Math.round((((constGaz.indice * diffGaz) * constGaz.prix) + (constGaz.abo / 12)) * 1e2) / 1e2
// TOTAL
const total = Math.round((resultatElec + resultatGaz) * 1e2) / 1e2
// SUBMIT
const handleSubmit = () => {
const releve = {
submissionDate: moment(selectedDate).unix(),
month: dateFormat,
gaz: m3,
electricite: kw,
total: total
}
dbRef.push(releve)
setOpenSnack(true)
setm3('')
setKw('')
}
// DELETE
const handleDelete = key => {
dbRef.child(key).remove()
}
// SNACKBAR
function Alert (props) {
return <MuiAlert elevation={6} variant='filled' {...props} />
}
const [openSnack, setOpenSnack] = React.useState(false)
const handleClose = (event, reason) => {
if (reason === 'clickaway') {
return
}
setOpenSnack(false)
}
return (
<>
<CssBaseline />
<div className='App'>
<Grid container justify='center' spacing={2}>
<Grid item xs={12}>
<h1>Conso Energie</h1>
<AddForm m3={m3} setm3={setm3} kw={kw} setKw={setKw} selectedDate={selectedDate} handleDateChange={handleDateChange} handleSubmit={handleSubmit} />
</Grid>
<Grid item xs={12}>
<ListingTable listingData={listingData} handleDelete={handleDelete} />
</Grid>
</Grid>
<Snackbar open={openSnack} autoHideDuration={3500} onClose={handleClose}>
<Alert onClose={handleClose} severity='success'>
Sauvegardé
</Alert>
</Snackbar>
</div>
</>
)
}
In the AddForm.js (child - I'm using Material UI) :
import React from 'react'
import TextField from '#material-ui/core/TextField'
import InputAdornment from '#material-ui/core/InputAdornment'
import Button from '#material-ui/core/Button'
import CloudUploadIcon from '#material-ui/icons/CloudUpload'
import { MuiPickersUtilsProvider, DatePicker } from '#material-ui/pickers'
import MomentUtils from '#date-io/moment'
import moment from 'moment'
import 'moment/locale/fr'
import Dialog from '#material-ui/core/Dialog'
import DialogActions from '#material-ui/core/DialogActions'
import DialogContent from '#material-ui/core/DialogContent'
import DialogTitle from '#material-ui/core/DialogTitle'
import Fab from '#material-ui/core/Fab'
import AddIcon from '#material-ui/icons/Add'
export default function AddForm ({ m3, setm3, kw, setKw, handleSubmit, selectedDate, handleDateChange }) {
const handleUpdateGaz = function (event) {
setm3(Number(event.target.value))
}
const handleUpdateKw = function (event) {
setKw(Number(event.target.value))
}
moment.locale('fr')
const [open, setOpen] = React.useState(false)
const handleClickOpenAddDialog = () => {
setOpen(true)
}
const handleCloseAddDialog = () => {
setOpen(false)
}
return (
<>
<div>
<Fab className='fab-btn-add' color='primary' aria-label='add' onClick={handleClickOpenAddDialog}>
<AddIcon />
</Fab>
<Dialog
open={open}
onClose={handleCloseAddDialog}
aria-labelledby='alert-dialog-title'
aria-describedby='alert-dialog-description'
>
<DialogTitle id='alert-dialog-title'>Entrer les valeurs</DialogTitle>
<DialogContent>
<form className='addform' noValidate autoComplete='off'>
<MuiPickersUtilsProvider utils={MomentUtils}>
<DatePicker
inputVariant='outlined'
value={selectedDate}
onChange={handleDateChange}
label='Mois'
format='MMMM Y'
views={['month']}
minDate={new Date('2020-01-01')}
maxDate={new Date('2020-12-31')}
/>
</MuiPickersUtilsProvider>
<TextField
label='Electricité'
variant='outlined'
type='number'
InputProps={{
endAdornment: <InputAdornment position='end'>kW</InputAdornment>
}}
value={kw}
onChange={handleUpdateKw}
/>
<TextField
label='Gaz'
variant='outlined'
type='number'
InputProps={{
endAdornment: <InputAdornment position='end'>m3</InputAdornment>
}}
value={m3}
onChange={handleUpdateGaz}
/>
<Button
className='btn-submit'
size='large'
variant='contained'
color='primary'
startIcon={<CloudUploadIcon />}
onClick={handleSubmit}
>
Confirmer
</Button>
</form>
</DialogContent>
<DialogActions>
<Button onClick={handleCloseAddDialog} color='primary' autoFocus>
Retour
</Button>
</DialogActions>
</Dialog>
</div>
</>
)
}
Pretty sure it's unrelated but here's the ListingExpansionPanel.js
import React from 'react'
import ExpansionPanel from '#material-ui/core/ExpansionPanel'
import ExpansionPanelSummary from '#material-ui/core/ExpansionPanelSummary'
import ExpansionPanelDetails from '#material-ui/core/ExpansionPanelDetails'
import Typography from '#material-ui/core/Typography'
import ExpandMoreIcon from '#material-ui/icons/ExpandMore'
import Icon from '#material-ui/core/Icon'
import Grid from '#material-ui/core/Grid'
import Button from '#material-ui/core/Button'
import Chip from '#material-ui/core/Chip'
import SwipeableViews from 'react-swipeable-views'
export default function ListingTable ({ listingData, handleDelete }) {
const dataRow = listingData.map((data, key) => (
<ExpansionPanel key={key} className='relevePanel'>
<ExpansionPanelSummary
expandIcon={<ExpandMoreIcon />}
aria-controls={key}
id={key}
>
<Typography>
<span>
<Icon style={{ marginRight: 15 }}>calendar_today</Icon>
{data.month}<b>{data.total ? ' : ' + data.total + ' €' : ''}</b>
</span>
</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<SwipeableViews>
<Typography className='expansion-detail'>
<Grid container>
<Grid item xs={12}>
<b style={{ color: '#cacaca' }}>2020</b>
</Grid>
<Grid item xs>
<Icon>power</Icon>
<span>{data.electricite} <small>Kwh</small></span>
<div>
<Chip className='plus' size='small' label='+3,4%' />
</div>
</Grid>
<Grid item xs>
<Icon>whatshot</Icon>
<span>{data.gaz} <small>m<sup>3</sup></small></span>
<div>
<Chip className='moins' size='small' label='-5,2%' />
</div>
</Grid>
<Grid item xs={12}>
<Button
className='btnRemove'
style={{ marginTop: 15 }}
size='small'
color='secondary'
onClick={() => handleDelete(data.key)}
>
Supprimer
</Button>
</Grid>
</Grid>
</Typography>
<Typography className='expansion-detail'>
<Grid container>
<Grid item xs={12}>
<b style={{ color: '#cacaca' }}>2019</b>
</Grid>
<Grid item xs>
<Icon>power</Icon>
<span>{data.electricite} <small>Kwh</small></span>
<div>
<Chip className='plus' size='small' label='+3,4%' />
</div>
</Grid>
<Grid item xs>
<Icon>whatshot</Icon>
<span>{data.gaz} <small>m<sup>3</sup></small></span>
<div>
<Chip className='moins' size='small' label='-5,2%' />
</div>
</Grid>
<Grid item xs={12}>
<Button
className='btnRemove'
style={{ marginTop: 15 }}
size='small'
color='secondary'
onClick={() => handleDelete(data.key)}
>
Supprimer
</Button>
</Grid>
</Grid>
</Typography>
</SwipeableViews>
</ExpansionPanelDetails>
</ExpansionPanel>
))
return (
<>
{dataRow}
</>
)
}
Thank you A LOT for your help, if someone see something.
I continue to search..
Thanks.
Update : Added the whole code for this 2 files
Update 2 : When I disable the firebase link, it's ok ! So I'll investigate this way..
This won't solve your problem but one thing that will use less memory is not creating an anonymous function, but replacing it with a reference in child components.
In the AddForm.js :
// pass the reference to changeKw instead of creating an anonymous function:
<TextField
value={kw}
onChange={changeKw} // e will be passed automatically
/>
In the App.js (parent) :
const changeKw = e => {
console.log(e.target.value) // e gets passed
setKw(e.target.value)
}

Categories

Resources