I am trying to figure out why any event triggers a visible page refresh when re-rendering. The following is the parent component :
export default function FullScreenDialog({purchaseList={"testtest":"900","heyzi":"90"}}) {
const [open, setOpen] = React.useState(false);
const [pieChartData,setPieChartData]=React.useState([])
const [formset,setFormset]=React.useState([
{
id: uuidv4(),
product:"",
price: 0,
quantity: 0,
productSubtotal: 0,
}
])
const singleForm= {
id: uuidv4(),
product:"",
price: 0,
quantity: 0,
productSubtotal: 0,
}
const handleProduct = (e,id) => {
const newFormset=formset.map(item=>
(item.id !== id? item : {
...item , product: e.target.value , price:purchaseList[e.target.value]
})
)
setFormset(newFormset)
}
const handleQuantity = (e,id) => {
const newFormset=formset.map((item)=>
{
if (item.id===id){
const newItem={
...item, quantity: e.target.value
}
return newItem
}
return item
})
setFormset(newFormset)
}
const handleAdd=()=>{
setFormset([...formset,singleForm])
}
const handleDelete=(id)=>{
const newFormset=formset.filter(item=>
item.id !==id
)
setFormset(newFormset)
}
//below is solely from this component, up is the state from children
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
handlePieChartData()
};
const handlePieChartData=()=>{
setPieChartData(
[...pieChartData,{id:uuidv4(), data:formset }])
}
console.log(formset)
console.log(pieChartData)
return (
<div>
<ListOfPieChartsPresenter
open={open}
handleClickOpen={handleClickOpen}
handleClose={handleClose}
handleProduct={handleProduct}
handleQuantity={handleQuantity}
handleDelete={handleDelete}
handleAdd={handleAdd}
purchaseList={purchaseList}
formset={formset}/>
</div>
);
}
Here is the ListOfPieChartsPresenter component:
const ListOfPieChartsPresenter=({handleClickOpen,open,handleClose,handleProduct,handleQuantity,handleDelete,handleAdd,purchaseList,formset})=>{
const classes = useStyles();
const Transition = React.forwardRef(function Transition(props, ref) {
return <Slide direction="up" ref={ref} {...props} />;
});
return (
<div>
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Open full-screen dialog
</Button>
<Dialog fullScreen open={open} onClose={handleClose} TransitionComponent={Transition}>
<AppBar className={classes.appBar}>
<Toolbar>
<IconButton edge="start" color="inherit" onClick={handleClose} aria-label="close">
{/* <CloseIcon /> */}
</IconButton>
<Typography variant="h6" className={classes.title}>
Sound
</Typography>
<Button autoFocus color="inherit" onClick={handleClose}>
save
</Button>
</Toolbar>
</AppBar>
<form >
<PieChartGroupForm
handleProduct={handleProduct}
handleQuantity={handleQuantity}
handleDelete={handleDelete}
handleAdd={handleAdd}
purchaseList={purchaseList}
formset={formset} />
</form>
</Dialog>
</div>
)
}
Lastly, this is the PieChartGroupFormPresenter:
const PieChartGroupFormPresenter=({handleProduct,handleQuantity,handleDelete,product,quantity,item,purchaseList,id})=>{
return (
<div>
<FormControl>
<Select onChange={(e)=>handleProduct(e,id)} value={product}>
{Object.keys(purchaseList).map((item,index) =>
<MenuItem value={item} key={index}>{item}</MenuItem>
)}
</Select>
<TextField onChange={(e)=>handleQuantity(e,id)} value={quantity} />
<TextField value={item.price} disabled>{item.price}</TextField>
<button onClick={()=>handleDelete(id)}>Delete</button>
</FormControl>
<br>
</br>
</div>
)
}
I have been reviewing this for approx an hour now and I still have no idea why this is happening. Could you please share your opinion? Thank you !
The first issue is that, with function components, every time the parent rerenders, the child will also rerender. You can fix this via React.memo:
const PieChartGroupFormPresenter = React.memo(({handleProduct,handleQuantity,handleDelete,product,quantity,item,purchaseList,id})=>{
//...
});
The second issue is that every time you execute code like const handleAdd=()=>{...} you get a different function (closure). So each time FullScreenDialog's state changes, it's going to change all the props passed to ListOfPieChartsPresenter.
You can use the useCallback hook to improve this: then the function/closure will change only when the specified dependencies change. For example:
const handleAdd = React.useCallback(()=>{
setFormset([...formset,singleForm])
}, [formset, singleForm]);
However, note that this callback function will still change whenever formset or singleForm change, so it will still cause a rerender when this state changes.
So hopefully it's at least clear why rerenders are happening, and why they are difficult to prevent. Unfortunately, I'm not familiar enough with React transitions to figure out why they're triggering for each rerender.
Related
Update:
I think the problem is that Child 2 components are not rerendering because there are the same props when handleReset() is triggered in parent component.. but how can I make them rerendering? I tried with useEffect but nothing happens.
I'm struggling for several hours with the following issue:
I have a parent component in which I have some values passed from useState to child 1 (CustomerData) then to child 2 (-AddressForm and -CustomerForm).
My problem is that when the handleReset() from parent component is triggered, the values from child2 (AddressForm and CustomerForm) are not updated and I don't understand why. I Think that Child 2 components are not rerendering when I reset the values and somehow I need to pass in some other way the stated in the set hook from handleReset().
If somebody can help me solve this issue please. Here are my components:
Parent component:
function WidgetCustomerData(props): JSX.Element {
const customerDataTest = {
customerFirstName: 'firstNameTest',
customerLastName: 'lastNameTest',
customerPhone: '1313212',
customerEmail: 'test#test.com',
customerAddress: 'Musterstraße',
customerAddressNo: '12',
customerZip: '80335',
customerCity: 'Berlin',
deviceAddress: 'Straße',
deviceAddressNo: '152',
deviceZip: '32214',
deviceCity: 'Hamburg',
};
const [customerData, setCustomerData] = useState({
addressValue: customerDataTest.customerAddress,
addressValueNr: customerDataTest.customerAddressNo,
addressValueZip: customerDataTest.customerZip,
addressValueCity: customerDataTest.customerCity,
customerFirstName: customerDataTest.customerFirstName,
customerLastName: customerDataTest.customerLastName,
customerPhone: customerDataTest.customerPhone,
customerEmail: customerDataTest.customerEmail,
deviceAddress: customerDataTest.deviceAddress,
deviceAddressNo: customerDataTest.deviceAddressNo,
deviceZip: customerDataTest.deviceZip,
deviceCity: customerDataTest.deviceCity
})
const handleClose = () => {
props.handleClose();
}
const handleSubmit = async (e) => {
e && e.preventDefault();
const formUrl = 'https://fetch.mock/widget-customer-data/addCustomerData';
const formData = new FormData(e.target);
for (const [name, value] of formData) {
console.log(`${name}: ${value}`);
}
const response = await fetch(formUrl.toString(), {
method: 'POST',
body: formData,
credentials: 'same-origin',
});
const data = await response.json();
};
const handleReset = () => {
console.log('handleReset ----');
setCustomerData({
addressValue: customerDataTest.customerAddress,
addressValueNr: customerDataTest.customerAddressNo,
addressValueZip: customerDataTest.customerZip,
addressValueCity: customerDataTest.customerCity,
customerFirstName: customerDataTest.customerFirstName,
customerLastName: customerDataTest.customerLastName,
customerPhone: customerDataTest.customerPhone,
customerEmail: customerDataTest.customerEmail,
deviceAddress: customerDataTest.deviceAddress,
deviceAddressNo: customerDataTest.deviceAddressNo,
deviceZip: customerDataTest.deviceZip,
deviceCity: customerDataTest.deviceCity
})
};
return (
<div className="customer-data">
<Dialog
onClose={handleClose}
open={props.open}
aria-labelledby="customized-dialog-title"
PaperProps={{
style: { borderRadius: 20, minWidth: '80%', maxHeight: 'fit-content' },
}}>
<DialogTitle id="customized-dialog-title" onClose={handleClose} />
<Typography style={{ marginTop: 20, paddingLeft: 48, fontSize: 32, fontWeight: 600 }}>Kundendaten bearbeiten</Typography>
{/* <DialogContent> */}
<form onSubmit={handleSubmit}>
<div style={{ paddingLeft: 48, paddingRight:48 }}>
<CustomerData
customerData={customerData}
/>
</div>
<DialogActions>
<ResetButton onClick={handleReset} variant="contained" color="primary">
Reset
</ResetButton>
<SaveButton type="submit" variant="contained" color="primary">
Save
</SaveButton>
</DialogActions>
</form>
{/* </DialogContent> */}
</Dialog>
</div>
);
}
export default WidgetCustomerData;
Child1
export default ({nextStepAction, customerData }: MoleculeSystemManagementCustomerDataProps): JSX.Element => {
return (
<div className='c-data'>
<CustomerForm customerData={customerData} />
<AddressForm formId='customer' customerData={customerData} />
</div>
);
}
Child2
function AddressForm({formId customerData }): JSX.Element {
const classes = useStyles();
const { addressValue, addressValueNr, addressValueZip, addressValueCity, deviceAddress, deviceAddressNo, deviceZip, deviceCity } = customerData;
return (
<Grid container className={classes.borderedRow}>
<Grid item xs={12} sm={6} md={3} className={classes.formColumn}>
<div>
<InputField type='label' id='address' name={`${formId}-address`} value={formId === 'customer' ? addressValue : deviceAddress} label={'Street'} />
</div>
</Grid>
<Grid item xs={12} sm={6} md={3} className={classes.formColumn}>
<InputField type='label' id='city' name={`${formId}-city`} value={formId === 'customer' ? addressValueNr : deviceAddressNo} label={'Number'} />
</Grid>
<Grid item xs={12} sm={6} md={3} className={classes.formColumn}>
<InputField type='label' id='state' name={`${formId}-state`} value={formId === 'customer' ? addressValueZip : deviceZip} label={'ZIP'} />
</Grid>
<Grid item xs={12} sm={6} md={3} className={classes.formColumn}>
<InputField type='label' id='zip' name={`${formId}-zip`} value={formId === 'customer' ? addressValueCity : deviceCity} label={'ORT'} />
</Grid>
</Grid>
);
}
export default AddressForm;
I think the problem is occurring because the state is only being passed to the first child component.
If you look carefully, you'll see that during the passage from the child (1) to child(2) there's no state dealing with it. React is not going to update the value of your props if it's not a state.
Probably, you just need to create a state within the first child. Look the example:
Child 1
export default ({nextStepAction, customerData }: MoleculeSystemManagementCustomerDataProps): JSX.Element => {
const [addressIsDifferent, setAddressIsDifferent] = useState(true);
const [customerDataState, setCustomerDataState] = useState(customerData)
return (
<div className='c-data'>
<CustomerForm customerData={customerData} />
<AddressForm formId='customer' customerData={customerDataState} />
</div>
);
}
Extra tips: I don't what are the use cases of your project, but I highly recommend you to take a look at React Context API
It's a great idea to use it if you need to pass data through components that need the same data and do cohesive functions.
In a typical React application, data is passed top-down (parent to child) via props, but such usage can be cumbersome for certain types of props (e.g. locale preference, UI theme) that are required by many components within an application. Context provides a way to share values like these between components without having to explicitly pass a prop through every level of the tree.
it looks like you are setting the state to the same data as it was initialised. you initialise it with customerDataTest and then set it to customerDataTest. same thing
Okay there is definitely a quick solution for this I just can't figure out.
Just a description of what I am trying to do:
Whenever I hover over a certain card, I would like to see the description of that item and only that item. But instead what's obviously happening, as you can see from my code, is every single cards description is showing.
I rewrote a simpler version of the code by taking out any unnecessary pieces. Everything is imported correctly, styling and classNames were removed as well.
export function Items() {
const [items, setItems] = useState([])
const [isHovering, setIsHovering] = useState(false)
useEffect(() => {
setItems(Data)
}, [])
function handleMouseOver() {
setIsHovering(true)
}
function handleMouseOut() {
setIsHovering(false)
}
return(
<div>
{items.map(item => {
return(
<Card onMouseOver={handleMouseOver} onMouseOut={handleMouseOut} key={item.id}>
{isHovering ?
<Card.Body>
<p>{item.item_description}</p>
</Card.Body>
:
<Card.Body>
</Card.Body>
}
<Card.Footer>
</Card.Footer>
</Card>
)
})}
</div>
)
}
As far as I can see you don't need to put this logic into parent component, and also it makes everything more complex, since it's hard to manage hovering. I would create new chlid component and manage this state out there internally.
export function Item({item}) {
const [isHovering, setIsHovering] = useState(false);
useEffect(() => {
setItems(Data);
}, []);
function handleMouseOver() {
setIsHovering(true);
}
function handleMouseOut() {
setIsHovering(false);
}
return (
<Card onMouseOver={handleMouseOver} onMouseOut={handleMouseOut}>
{isHovering ? (
<Card.Body>
<p>{item.item_description}</p>
</Card.Body>
) : (
<Card.Body></Card.Body>
)}
<Card.Footer></Card.Footer>
</Card>
);
}
export function Items() {
const [items, setItems] = useState([]);
return (
<div>
{items.map(item => (
<Item key={item.id} item={item} />
))}
</div>
);
}
Your "isHovering" state should also be an array, where you store the hover state for every card. Then on hover set "isHovering" to true only for the right card.
I am doing a project and I want to show a modal window to compulsorily accept some Cookies.
I need that cookie window to be launched automatically, without pressing any button, when the application starts, that is, before the dashboard is loaded.
This is my code:
ModalCookie.js
imports ...
const style = {
position: 'absolute',
top: '50%',
left: '50%',
};
export default function CookiesModal() {
const handleOpen = () => setOpen(true);
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setCookiesPreview(false);
setOpen(false);
};
const handleNotClose = (event, reason) => {
if (reason && reason == "backdropClick")
return;
}
const [value, setValue] = React.useState('1');
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<ThemeProvider theme={theme}>
<div>
<Fade in={open}>
<Box>
<BootstrapDialog
onClose={handleNotClose}
aria-labelledby="customized-dialog-title"
open={open}
closeAfterTransition
BackdropComponent={Backdrop}
BackdropProps={{
timeout: 500,
}}
>
<BootstrapDialogTitle id="customized-dialog-title" onClose={handleClose}>
</BootstrapDialogTitle>
<TabContext value={value}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<TabList onChange={handleChange} aria-label="lab API tabs example">
<Tab
label="AAAAA"
value="1" />
</TabList>
</Box>
<TabPanel value="1">
<p>...</p>
</TabPanel>
</TabContext>
<Button
onClick={handleClose}
variant="contained"
color="secondary"
>
Aceptar
</Button>
</BootstrapDialog>
</Box>
</Fade>
</div>
</ThemeProvider>
);
}
Config.js
// COOKIES
export const setCookiesPreview = ( state ) => {
localStorage.setItem('cookiesPreview', state)
}
// --
export const isCookiesPreview = () => {
const active=localStorage.getItem('cookiesPreview') ? localStorage.getItem('cookiesPreview') : true;
setCookiesPreview(false);
return(
active
);
}
And my dashboard:
imports...
// MAIN
export const Dashboard = () => {
const state = useSelector( state => state);
console.log('isCookiesPreview='+isCookiesPreview());
if(isCookiesPreview()){
console.log('CookiesPreview ON ------------------------------------------------')
setTimeout(() => {
CookiesModal.handleOpen();
}, 15000);
}
return (
<>
<ThemeProvider theme={theme}>
<div>
<HeaderBar/>
{(alertMessage != null) && (alertMessage.length>0) ? <Alert severity={alertSeverity}>{alertMessage}</Alert> : <></>}
<Grid container item={true} xs={12}>
<BodyGrid/>
</Grid>
<Grid container item={true} xs={12} pt={4}>
<BottomBar/>
</Grid>
</div>
</ThemeProvider>
</>
)
I am trying to use the handleOpen() constant from ModalCookie.js to open the window from Dashboard.js and save the locale that those cookies have been accepted so as not to show it the following times
I can't get the window to show up, but it does show me the logs I've put on the Dashboard related to cookies.
It tells me that HandleOpen is not a function.
I have researched this topic for quite some time now, yet I still cannot make the 'Remove' button in the child component (ControlledOpenSelect) only remove the item with the key it was passed - by using the callback function.
My ControlledOpenSelect (the child component):
const example={'skittle':10,"carrots":20,"cars":50,"potatoes":30}
export default function ControlledOpenSelect({ourObject=example,onRemove,key}) {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const [product,setProduct]=React.useState('None')
const [quantity,setQuantity]=React.useState(0)
const [price,setPrice]=React.useState('')
const [subTotal,setSubTotal]=React.useState(0)
const handleChange = (event) => {
setProduct(event.target.value);
};
const handleClose = () => {
setOpen(false);
};
const handleOpen = () => {
setOpen(true);
};
const handleQuantity=(event)=>{
setQuantity(event.target.value)
}
//const productList=Object.keys(ourObject)
//const correct_price=ourObject[product]
//React.useEffect(()=>{
//setPrice(correct_price)
//},[correct_price])
//React.useEffect(()=>{
//setSubTotal(price*quantity)
//},[quantity,product,price])
return (
<div>
<Button className={classes.button} onClick={handleOpen}>
Open the select
</Button>
<FormControl className={classes.formControl}>
<InputLabel id="demo-controlled-open-select-label">Product</InputLabel>
<Select
labelId="demo-controlled-open-select-label"
id="demo-controlled-open-select"
open={open}
onClose={handleClose}
onOpen={handleOpen}
value={product}
onChange={handleChange}
>
{productList.map(
item => <MenuItem value={item}>{item}</MenuItem>
)}
</Select>
<div>
<TextField id="outlined-basic" label="Quantity" variant="outlined" onChange={handleQuantity}/>
<TextField id="outlined-basic" label="Price" variant="outlined" value={price} />
<p>{subTotal}</p>
</div>
</FormControl>
<button onClick={()=>onRemove(key)}>Remove</button>
</div>
);
}
My parent component FullComponent:
const example={'skittle':10,"carrots":20,"cars":50,"potatoes":30}
const FullComponent=({ourObject=example})=>{
const [add,setAdd]=React.useState([])
// const [remove,setRemove]=React.useState([])
const id=React.useState(_uniqueId('prefix-'));
const handleClick=(event)=>{
setAdd([...add,
<ControlledOpenSelect ourObject={ourObject} id={id}/>])
}
const handleRemove=(id)=>{
const newAdd=add.filter((item)=> item.id !== id)
setAdd(newAdd)
}
return (
<>
{add.map((item)=>{
return (
<>
<ControlledOpenSelect ourObject={ourObject} key={item.id} onRemove={handleRemove} />
</> )
})}
<button type="button" onClick={handleClick}>Add</button>
</>
)
}
export default FullComponent
Thank you so much!
You are not passing the id to the handleRemove method . you need to pass an inline function which passes the item.id as argument
onRemove={() => handleRemove(item.id)}
I am trying to trigger the Redirect React Dom
that is my button component in the handleMenuItemClick() function. But nothing happens.
I have tried a bunch of stuff but but still no success.
How can I make the both work together? My best try was to make a function that return the Redirect component as I saw in one post around, but still no success.
My Code:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { Grid, Button, ButtonGroup, ArrowDropDownIcon, ClickAwayListener, Grow, Paper, Popper, MenuItem, MenuList, Link } from '#material-ui/core/Grid';
const SplitButton = (props) => {
const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef(null);
const [selectedIndex, setSelectedIndex] = React.useState(1);
const myGroups = props.myGroups
const handleMenuItemClick = (event, index) => {
setSelectedIndex(index);
setOpen(false);
return <Redirect to={`/groups/${index}`} />
};
const handleToggle = () => {
setOpen((prevOpen) => !prevOpen);
};
const handleClose = (event) => {
if (anchorRef.current && anchorRef.current.contains(event.target)) {
return;
}
setOpen(false);
};
return (
<>
<ButtonGroup variant="contained" color="primary" ref={anchorRef} aria-label="split button">
<Button onClick={null}>My Groups</Button>
<Button
color="primary"
size="small"
aria-controls={open ? 'split-button-menu' : undefined}
aria-expanded={open ? 'true' : undefined}
aria-label="select merge strategy"
aria-haspopup="menu"
onClick={handleToggle}
>
<ArrowDropDownIcon />
</Button>
</ButtonGroup>
<Popper open={open} anchorEl={anchorRef.current} role={undefined} transition disablePortal>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom',
}}
>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList id="split-button-menu">
{ myGroups.map((group) => (
<MenuItem
key={group.id}
onClick={(event) => handleMenuItemClick(event, group.id)}
>
{group.title}
</MenuItem>
))}
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</>
);
}
export default SplitButton
You can redirect user via 2 methods: useHistory or <Redirect />
useHistory hook
If you want to redirect the user directly on click, you can treat the code imperatively and tell React what to do:
const history = useHistory();
const handleMenuItemClick = (event, index) => {
setSelectedIndex(index);
setOpen(false);
history.push(`/groups/${index}`)
};
More info https://reactrouter.com/web/api/Hooks/usehistory
Redirect component
Or if you feel more comfortable using React's default declarative model, you can say what's changed and allow your code to react to this change:
const [redirectUrl, setRedirectUrl] = useState('')
const handleMenuItemClick = (event, index) => {
setSelectedIndex(index);
setOpen(false);
setRedirectUrl(`/groups/${index}`)
};
if (redirectUrl) {
return <Redirect to={redirectUrl} />
}
return (
<>
<ButtonGroup variant="contained" color="primary" ref={anchorRef} aria-label="split button">
<Button onClick={null}>My Groups</Button>
<Button
...
More info https://reactrouter.com/web/api/Redirect