redux and react unwanted effect - javascript

Hello everyone đź‘‹
I'm trying to make a cart functionality with redux.
2.Description of problem:
The problem is that as soon as I want to remove a product that is not the last from my basket.
Redux does delete the desired product from the store, but at the frontend I still see the product and react deletes the last product from the list (the one I didn't want to delete).
I have to go to another page without reloading and returning to the basket to see react to reset
If I remove the last product from the list the removal works as it should.
I have made a video link that are served by google drive:
https://drive.google.com/file/d/1miZA4B1Ay5OZZBGPj1bCcQHsGv21oVW_/view
Here is the redux effect I want or expected to have ( this is the action of removing the latest product from cart list :
If I have several products in my cart and I just want to delete a product without it being the last ( unwanted effect ):
The ADD_TO_CART_ACTION dispatch
store.dispatch({
type: ADD_PRODUCT_TO_CART_ACTION,
payload: {
data: {
productID: data.product.id,
attributeID: RadioState,
price: data.price,
quantity: 1
}
}
})
this is my cart reducer:
export function CartReducer(state = [], action){
const cart = [...state]
switch (action.type){
case 'ADD_PRODUCT_TO_CART_ACTION':
return [...state, {...action.payload.data}];
case 'UPDATE_QUANTITY_FROM_CART_ACTION':
return cart.map(product => {
if (product.attributeID === action.payload.attributeID){
product.quantity++
return {...product}
} else {
return product
}
})
case 'REMOVE_QUANTITY_FROM_CART_ACTION':
return cart.map(product => {
if (product.attributeID === action.payload.attributeID){
product.quantity--
return {...product}
} else {
return product
}
})
case 'TRASH_PRODUCT_FROM_CART_ACTION':
return cart.filter(product => product.attributeID !== action.payload)
default:
return state;
}
}
Here is the cart component who them is connected from redux first:
export function Cart (props)
{
const [productCount, setProductCount] = useState(0)
useEffect(() => {
setProductCount(props.count)
}, [])
if (props.cart.length === 0){
return <div className="home"><p className={'text-center text-green-500'}>Add product to cart.</p></div>
}
return (
<React.Fragment className="home">
<Grid container spacing={3}>
{props.cart.map((item, index) => {
return (
<Grid item xs={6}>
<ProductCartDetails productCount={productCount} key={index} attributes={item}/>
</Grid>
)
})}
</Grid>
</React.Fragment>
)
}
export const CartStore = connect(
(state) => ({
cart: cartSelectors(state),
count: cartCount(state)
})
)(Cart)
This is ProductCartDetails ( The card of the product, and here who action are dispatch )
export default function ProductCartDetails (props){
const [productCount, setProductCount] = useState(0)
const [product, setProduct] = useState([])
const [requestReady, setRequestReady] = useState(false)
useEffect(() => {
axios.get(`product/${props.attributes.productID}`)
.then(({data}) => {
setProduct(data)
setRequestReady(! requestReady)
})
.catch((err) => {
console.log(err)
})
}, [props.productCount])
useEffect(() => {
setProductCount(props.productCount)
}, [props.productCount])
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
width: "100%",
marginTop: 4,
backgroundColor: "#faf7f7",
boxSizing: 'border-box',
},
details: {
display: 'flex',
flexDirection: 'column',
},
content: {
flex: '1',
},
cover: {
width: 151,
height: '100%'
},
}));
const onClickAddMoreQuantity = () => {
let cart = [...store.getState()]
let updatedQuantity = false;
cart.map(product => {
if (product.attributeID === props.attributes.attributeID){
store.dispatch(
{
type: UPDATE_QUANTITY_FROM_CART_ACTION,
payload: {
attributeID: props.attributes.attributeID
}
}
)
updatedQuantity = true
}
})
if (updatedQuantity === false){
swal({
icon: 'error',
title: 'Cart',
text: 'Product quantity cannot be bigger than the product stock.',
})
}
}
const onClickRemoveQuantityFromCart = () => {
let cart = [...store.getState()]
let updatedQuantity = false;
cart.map(product => {
if (product.attributeID === props.attributes.attributeID){
store.dispatch(
{
type: REMOVE_QUANTITY_FROM_CART_ACTION,
payload: {
attributeID: props.attributes.attributeID
}
}
)
updatedQuantity = true
}
})
if (updatedQuantity === false){
swal({
icon: 'error',
title: 'Cart',
text: 'Product quantity has not been removed.',
})
}
}
const onClickTrashProductFromCart = () => {
let cart = [...store.getState()]
let updatedQuantity = false;
cart.map(product => {
if (product.attributeID === props.attributes.attributeID){
store.dispatch(
{
type: TRASH_PRODUCT_FROM_CART_ACTION,
payload: props.attributes.attributeID
}
)
updatedQuantity = true
}
})
if (updatedQuantity === false){
swal({
icon: 'error',
title: 'Cart',
text: 'Product has not been removed.',
})
}
}
const classes = useStyles();
if (productCount !== 0){
return (
<>
<Card className={classes.root}>
<Link to={requestReady ? `/details/${product.slug}` : null}>
<img
className={classes.cover}
src={requestReady ? axios.defaults.baseURL+product.image[0].url+"?h=600" : null}
alt="image cover product cart"
/>
</Link>
<div className={classes.details}>
<CardContent className={classes.content}>
<Typography className="text-center text-gray-700" component="h6" variant="h6">
{requestReady ? product.name : null}
</Typography>
<p className="text-center text-gray-600">
Details Of Product
</p>
<div>
<Typography variant="subtitle1" color="textSecondary">
Category: {requestReady ? product.category.label : null}
</Typography>
<Typography variant="subtitle1" color="textSecondary">
<ProductCartAttributeDetails attributes={props.attributes} />
</Typography>
</div>
</CardContent>
<CardActions>
<button id={requestReady ? product.id : null} onClick={onClickAddMoreQuantity}>
<Add height={10} />Add quantity
</button>
<button>
<Delete height={10} onClick={onClickTrashProductFromCart} />Trash
</button>
<button onClick={onClickRemoveQuantityFromCart}>
<Remove height={10} />Remove quantity
</button>
</CardActions>
</div>
</Card>
</>
)
} else {
return (
<>
<Card className={classes.root}>
<Link to={requestReady ? `/details/${product.slug}` : null}>
<img
className={classes.cover}
src={requestReady ? axios.defaults.baseURL+product.image[0].url+"?h=600" : null}
alt="image cover product cart"
/>
</Link>
<div className={classes.details}>
<CardContent className={classes.content}>
<Typography className="text-center text-gray-700" component="h6" variant="h6">
{requestReady ? product.name : null}
</Typography>
<p className="text-center text-gray-600">
Details Of Product
</p>
<div>
<Typography variant="subtitle1" color="textSecondary">
Category: {requestReady ? product.category.label : null}
</Typography>
<Typography variant="subtitle1" color="textSecondary">
<ProductCartAttributeDetails attributes={props.attributes} />
</Typography>
</div>
</CardContent>
<CardActions>
<button id={requestReady ? product.id : null} onClick={onClickAddMoreQuantity}>
<Add height={10} />Add quantity
</button>
<button>
<Delete height={10} onClick={onClickTrashProductFromCart} />Trash
</button>
<button onClick={onClickRemoveQuantityFromCart}>
<Remove height={10} />Remove quantity
</button>
</CardActions>
</div>
</Card>
</>
)
}
}
And the ProductCartAttributeDetails if needed
export default function ProductCartAttributeDetails({attributes}){
const [attribute, setAttribute] = useState([])
const [requestReady, setRequestReady] = useState(false)
useEffect(() => {
axios.get(`attributes/${attributes.attributeID}`)
.then(({data}) => {
setAttribute(data)
setRequestReady(! requestReady)
})
.catch((err) => {
console.log(err)
})
}, [])
return (
<>
<Typography variant="subtitle1" color="textSecondary">
<p><span className="capitalize">{requestReady ? attribute.attribute : null}</span> : {requestReady ? attribute.value : null}</p>
</Typography>
<Typography variant="subtitle1" color="textSecondary">
<p>Quantity: {requestReady ? attributes.quantity : null}</p>
</Typography>
<Typography variant="subtitle1" color="textSecondary">
<p>Total Price: {requestReady ? attribute.price * attributes.quantity : null}</p>
</Typography>
</>
)
}

Problem was fixed by changing the default index of react js by your own index, for me i have use the index that i receive from my store to be sure them is unique

Related

How to send/receive props to BasicLayout (#devexpress/dx-react-scheduler)

I'm from Angular and new to React. Im doing well but here is a problem I'm stuck at. As you can see I have BasicLayout and AppointmentForm, both are in one file. BasicLayout is being used inside AppointmentForm but not like an element i.e <BasicLayout/> so I'm not able to understand how to pass props or its even possible now. I want to trigger commitChanges(inside AppointmentForm) function when onSubmit(inside Basic Layout) function is triggered. How can I pass props between these components?
const BasicLayout = (props) => {
const formik = useFormik({
initialValues: {
title: '',
agenda: '',
description: '',
participants: [],
host: user?.id,
guest: '',
location: '',
},
validationSchema,
onSubmit: async (values) => {
values.startDate = props.appointmentData.startDate;
values.endDate = props.appointmentData.endDate;
values.guest = values.guest?._id;
createAppointment(values);
console.log(values);
},
});
return (
<Container>
<Typography sx={{ fontSize: 24, fontWeight: 'bold' }} color="text.primary" gutterBottom>
Create Appointment
</Typography>
<Box sx={{ flexGrow: 1 }}>
<FormikProvider value={formik}>
<Form autoComplete="off" onSubmit={handleSubmit}>
<Grid container spacing={2}>
<Grid item xs={6} md={6}>
<TextField
label="Title"
color="secondary"
id="title"
type="text"
key="title"
value={formik.values.title}
onChange={formik.handleChange}
{...getFieldProps('title')}
error={Boolean(touched.title && errors.title)}
helperText={touched.title && errors.title}
fullWidth
/>
</Grid>
<Grid item container xs={12} md={12} direction="row" justifyContent="center" alignItems="center">
<LoadingButton size="medium" type="submit" variant="contained" loading={isSubmitting}>
Create
</LoadingButton>
</Grid>
</Grid>
</Form>
</FormikProvider>
</Box>
<ToastContainer />
</Container>
);
};
const AppointmentsDashboard = (props) => {
const commitChanges = ({ added, changed, deleted }) => {
console.log(props);
console.log({ added, changed, deleted });
if (added) {
if (!isValidate) {
notify('Please fill all required fields', 'error');
return;
}
const startingAddedId = data.length > 0 ? data[data.length - 1].id + 1 : 0;
setData([...data, { id: startingAddedId, ...added }]);
}
if (changed) {
setData(
data.map((appointment) =>
changed[appointment.id] ? { ...appointment, ...changed[appointment.id] } : appointment
)
);
}
if (deleted !== undefined) {
setData(data.filter((appointment) => appointment.id !== deleted));
}
return data;
};
return (
<>
<Paper>
<Scheduler data={data} height={660}>
<ViewState currentDate={currentDate} />
<EditingState
onCommitChanges={commitChanges}
addedAppointment={addedAppointment}
onAddedAppointmentChange={changeAddedAppointment}
appointmentChanges={appointmentChanges}
onAppointmentChangesChange={changeAppointmentChanges}
editingAppointment={editingAppointment}
onEditingAppointmentChange={changeEditingAppointment}
onAppointmentFormClosing={() => {
console.log('asdasd');
}}
allowAdding={true}
/>
<WeekView startDayHour={9} endDayHour={17} />
<AllDayPanel />
<EditRecurrenceMenu />
<ConfirmationDialog />
<Appointments />
<AppointmentTooltip showOpenButton showDeleteButton />
<AppointmentForm basicLayoutComponent={BasicLayout} />
</Scheduler>
</Paper>
</>
);
};
export default AppointmentsDashboard;

How can I make onClick events work with specific elements in a .map() function?

I'm trying to make a list of users inside a chat. Each user is supposed to have a button opening a menu, allowing admins to ban that specific user. I'm mapping an array of users to get the list, but the issue is that whenever I try to click on the 'ban' button, the user data seems to always be for the last user in the array, instead of the user on which I clicked. Here's the code for the entire component:
const UserList: React.FC<{channel: string}> = ({channel}) => {
const [channelMembers, setChannelMembers] = useState<any[] | null>(null)
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
const [isAdmin, setIsAdmin] = useState(false)
const navigate = useNavigate()
const open = Boolean(anchorEl)
const baseUrl = useContext(UrlContext)
useEffect(() => {
axios.get(baseUrl + `chat/${channel}/users`, {withCredentials: true}).then((response) => {
setChannelMembers(response.data)
})
}, [baseUrl, channel, anchorEl])
useEffect(() => {
axios.get(baseUrl + `users/me`, {withCredentials: true}).then((response) => {
axios.get(baseUrl + `chat/${channel}/admins`, {withCredentials: true}).then((resp2) => {
if (resp2.data.includes(response.data.id)) {
setIsAdmin(true)
}
}).catch((error) => {
console.log(error)
})
}).catch((error) => {
console.log(error)
})
}, [baseUrl, anchorEl])
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget)
}
const handleClose = () => {
setAnchorEl(null)
}
const handleBan = (user: any) => () => {
let banUser = {...user}
console.log(banUser)
axios.post(baseUrl + `chat/${channel}/ban`, banUser, {withCredentials: true}).catch((error) => {
console.log(error)
})
handleClose()
}
return (
<>
<Drawer
sx={{
width: drawerWidth,
flexShrink: 0,
'& .MuiDrawer-paper': {
width: drawerWidth,
boxSizing: 'border-box',
},
}}
variant="permanent"
anchor="right"
>
<Toolbar />
<List>
{channelMembers?.map((user: any, index: number) => {
return (
<Fragment key={index}>
<ListItem>
<ListItemText primary={user.name} />
<ListItemIcon>
{(user.status === Status.ONLINE) ? <CircleIcon style={{color: "green"}} fontSize="small" /> : <RadioButtonUncheckedIcon style={{color: "grey"}} fontSize="small" />}
</ListItemIcon>
<IconButton
aria-label="more"
id="long-button"
aria-controls={open ? 'long-menu' : undefined}
aria-expanded={open ? 'true' : undefined}
aria-haspopup="true"
onClick={handleClick}
>
<MoreVertIcon />
</IconButton>
<Menu
id="long-menu"
MenuListProps={{
'aria-labelledby': 'long-button'
}}
anchorEl={anchorEl}
open={open}
onClose={handleClose}
>
{isAdmin &&
[<MenuItem onClick={handleClose}>
<Typography>
Make admin
</Typography>
</MenuItem>,
<MenuItem onClick={handleClose}>
<Typography>
Mute
</Typography>
</MenuItem>,
<MenuItem onClick={handleClose}>
<Typography>
Kick
</Typography>
</MenuItem>,
<MenuItem onClick={handleBan(user)}>
<Typography>
Ban
</Typography>
</MenuItem>]}
<MenuItem onClick={handleClose}>
<Typography>
View profile
</Typography>
</MenuItem>
<MenuItem onClick={handleClose}>
<Typography>
Invite to play
</Typography>
</MenuItem>
</Menu>
</ListItem>
<hr />
</Fragment>
)
} )}
</List>
</Drawer>
</>
)
}
You're rendering multiple menus in your map and since you're using only one state when opening one menu it sets open to true which means all menus are open because of this prop open={open}.
You should try to render only one Menu and possibly store in a state the selected user. There's probably a better way to do this but it should solve your problem.
You're calling the ban handleBan(user) instead of () => handleBan(user), function instead and using the return value of it.
Try this code
const UserList: React.FC<{ channel: string }> = ({ channel }) => {
const [channelMembers, setChannelMembers] = useState<any[] | null>(null);
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [isAdmin, setIsAdmin] = useState(false);
const navigate = useNavigate();
const open = Boolean(anchorEl);
const baseUrl = useContext(UrlContext);
useEffect(() => {
axios
.get(baseUrl + `chat/${channel}/users`, { withCredentials: true })
.then((response) => {
setChannelMembers(response.data);
});
}, [baseUrl, channel, anchorEl]);
useEffect(() => {
axios
.get(baseUrl + `users/me`, { withCredentials: true })
.then((response) => {
axios
.get(baseUrl + `chat/${channel}/admins`, { withCredentials: true })
.then((resp2) => {
if (resp2.data.includes(response.data.id)) {
setIsAdmin(true);
}
})
.catch((error) => {
console.log(error);
});
})
.catch((error) => {
console.log(error);
});
}, [baseUrl, anchorEl]);
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const handleBan = (user: any) => () => {
let banUser = { ...user };
console.log(banUser);
axios
.post(baseUrl + `chat/${channel}/ban`, banUser, { withCredentials: true })
.catch((error) => {
console.log(error);
});
handleClose();
};
return (
<>
<Drawer
sx={{
width: drawerWidth,
flexShrink: 0,
"& .MuiDrawer-paper": {
width: drawerWidth,
boxSizing: "border-box"
}
}}
variant="permanent"
anchor="right"
>
<Toolbar />
<List>
{channelMembers?.map((user: any, index: number) => {
return (
<Fragment key={index}>
<ListItem>
<ListItemText primary={user.name} />
<ListItemIcon>
{user.status === Status.ONLINE ? (
<CircleIcon style={{ color: "green" }} fontSize="small" />
) : (
<RadioButtonUncheckedIcon
style={{ color: "grey" }}
fontSize="small"
/>
)}
</ListItemIcon>
<IconButton
aria-label="more"
id="long-button"
aria-controls={open ? "long-menu" : undefined}
aria-expanded={open ? "true" : undefined}
aria-haspopup="true"
onClick={handleClick}
>
<MoreVertIcon />
</IconButton>
<Menu
id="long-menu"
MenuListProps={{
"aria-labelledby": "long-button"
}}
anchorEl={anchorEl}
open={open}
onClose={handleClose}
>
{isAdmin && [
<MenuItem onClick={handleClose}>
<Typography>Make admin</Typography>
</MenuItem>,
<MenuItem onClick={handleClose}>
<Typography>Mute</Typography>
</MenuItem>,
<MenuItem onClick={handleClose}>
<Typography>Kick</Typography>
</MenuItem>,
<MenuItem onClick={() => handleBan(user)}>
<Typography>Ban</Typography>
</MenuItem>
]}
<MenuItem onClick={handleClose}>
<Typography>View profile</Typography>
</MenuItem>
<MenuItem onClick={handleClose}>
<Typography>Invite to play</Typography>
</MenuItem>
</Menu>
</ListItem>
<hr />
</Fragment>
);
})}
</List>
</Drawer>
</>
);
};
Instead of doing this:
onClick={handleBan(user)}
Pass an anonymous function to your on click, so you can pass the correct user like this:
onClick={() => handleBan(user)}
Here's a code sandbox showing how the 2 approaches differ.
Here's the answer that works

Remove repeated elements

I'm displaying US region incidents from Google Cloud API, but the problem is that it displays also repeated data, example:enter image description here
How can I remove the repeated ones? Here's the code.
DATA RETRIEVING:
export const getGoogleStatus = async () => {
const response = await axios.get('https://status.cloud.google.com/incidents.json')
console.log('Raw Data: ', response.data)
const status = response.data.map((e) => {
return {
name: e.affected_products.map((e) => {
return e.title
}),
status: e.most_recent_update.status,
location: e.most_recent_update.affected_locations.map((e) => {
return e.title
}).filter((r) => r.includes("(us-"))
}
})
return status
}
DATA DISPLAYING IN FRONTEND:
export default function GoogleStatusSummary({ listStatus }) {
if (!listStatus) return <div>Loading...</div>
return (
<Grid container padding={2} justifyContent='center'>
<Grid item padding={2}>
<Typography variant='h1' marginLeft={5}>
<TwitterIcon sx={{ mr: 2, mt: 1 }} />
Google API Status
</Typography>
{listStatus.map((item, index) => {
const { name, status, location } = item
const colorStatus = status === 'AVAILABLE' ? 'success' : 'danger'
return (
location.length > 0 && (
<Grid display='flex' m={3} key={index}>
<RadioButtonCheckedIcon sx={{ ml: 2, mt: 1 }} color={colorStatus} />
<Grid ml={2}>
<Typography variant='h4'>
{name}
</Typography>
<Typography variant='h6' style={{ fontSize: 12 }}>
{status.charAt(0).toUpperCase() + status.slice(1)}
</Typography>
</Grid>
</Grid>
)
)
})
}
</Grid>
</Grid>
)

React - Why Props do not update

I just noticed that the props I send for my PayPal Sandbox are not updated.
Context:
I have an add cart page where the user can increase / decrease the quantity of a product they have in their cart.
I also display the total amount of the cart.
These two values: Quantity and Total are sent by props to the PayPal component (A button).
Except that if the user changes the quantity, these new values are not taken into account by my PayPal button. So I have to manually refresh the page. Which is a huge problem.
Here is my code:
export default function PanierPage() {
let MonTotal = '';
const classPanier = PanierStyles();
const [total, setTotal] = useState([])
// Récupération des détails des paniers
const [paniers, setPaniers] = useState([])
const getPaniersDetails = () => [
getDetails(JSON.parse(localStorage.getItem('User')).id).then(response => {
setPaniers(response.data)
}).catch(err => console.log(err))
]
const handleIncrement = (id, quantite) => {
updateQuantite(id, quantite + 1 ).then(response => {
getDetails(JSON.parse(localStorage.getItem('User')).id).then(response => {
setPaniers(response.data)
}).catch(err => console.log(err))
getTotal(JSON.parse(localStorage.getItem('User')).id).then(response => {
setTotal(response.data)
}).catch(err => console.log(err))
}).catch(err => console.log(err))
}
const handleDecrement = (id, quantite) => {
updateQuantite(id, quantite - 1 ).then(response => {
getDetails(JSON.parse(localStorage.getItem('User')).id).then(response => {
setPaniers(response.data)
}).catch(err => console.log(err))
getTotal(JSON.parse(localStorage.getItem('User')).id).then(response => {
setTotal(response.data)
}).catch(err => console.log(err))
}).catch(err => console.log(err))
}
const SupprPanier = (id) => {
deletePanier(id).then(response => {
getDetails(JSON.parse(localStorage.getItem('User')).id).then(response => {
setPaniers(response.data)
}).catch(err => console.log(err))
getTotal(JSON.parse(localStorage.getItem('User')).id).then(response => {
setTotal(response.data)
}).catch(err => console.log(err))
}).catch(err => console.log(err))
}
// Affichage des détails des paniers
const paniersAffichage = paniers.map((panier) => (
<Grid container style={{justifyContent:'center', padding: '1em'}}>
<Card key={panier.PAN_ID} style={{height: '100%', display: 'flex', flexDirection: 'row'}} variant='outlined'>
<CardHeader title={panier.PRO_NOM}/>
<CardMedia style={{height: 150, width: 150, marginLeft: 'auto', marginRight: 'auto'}} image={`http://localhost:8090/${panier.PRO_URL}`}/>
<Grid direction="row" justify="center" alignItems="center">
<CardContent>
{panier.PAN_QUANTITE === panier.PRO_QTESTOCK
?
<Button variant='outlined' color="primary" disabled onClick={() => handleIncrement(panier.PAN_ID, panier.PAN_QUANTITE)}> + </Button>
:
<Button variant='outlined' color="primary" onClick={() => handleIncrement(panier.PAN_ID, panier.PAN_QUANTITE)}> + </Button>
}
<Typography style={{textAlign: 'center'}}>
{panier.PAN_QUANTITE}
</Typography>
{panier.PAN_QUANTITE === 1
?
<Button variant='outlined' color="secondary" disabled onClick={() => handleDecrement(panier.PAN_ID, panier.PAN_QUANTITE)}> - </Button>
:
<Button variant='outlined' color="secondary" onClick={() => handleDecrement(panier.PAN_ID, panier.PAN_QUANTITE)}> - </Button>
}
</CardContent>
</Grid>
<Box>
<Grid container direction="row" justify="center" alignItems="center" >
<Typography style={{textAlign: 'center'}}>
{panier.PRO_PRIX} €
</Typography>
<CardActions>
<Button onClick={() => SupprPanier(panier.PAN_ID)} className={classPanier.iconDelete}><DeleteIcon/></Button>
</CardActions>
</Grid>
</Box>
</Card>
</Grid>
));
const PanierTotal = () => {
getTotal(JSON.parse(localStorage.getItem('User')).id).then(response => {
setTotal(response.data)
}).catch(err => console.log(err))
}
const TotalAffiche = total.map((total) => (
<Grid style={{color: 'white', marginTop: '2%', marginBottom: '2%',}}>
Montant total de votre panier : {total.TOTAL} €
</Grid>
))
// Chargement des produits
useEffect(() => {
getPaniersDetails();
PanierTotal();
}, [])
return (
<>
{paniersAffichage}
<Grid container style={{justifyContent:'center'}}>
{TotalAffiche}
<Grid style={{marginLeft: '2%'}}>
{(total.length > 0 && paniers.length > 0) && <PayPal prixTotal={total[0].TOTAL} paniers={paniers} />}
</Grid>
</Grid>
</>
);
}
And my PayPal button :
export default function Paypal({prixTotal, paniers})
{
const PDFGeneration = () => {
var doc = new jsPDF('p','px','a4','false');
var lesPaniers = paniers.map(panier => [panier.PRO_NOM, panier.PAN_QUANTITE, panier.PRO_PRIX ]);
doc.text(10,15,'Résumé de votre commande : ');
doc.autoTable({
theme: 'grid',
startY: 20,
head: [['Nom', 'Quantité', 'Prix (€)']],
body: lesPaniers,
});
doc.text(10,120,`Total de la commande : ${prixTotal} €`);
doc.text(10,150,'A la prochaine fois !');
doc.save('CommandeInfo.pdf');
}
const paypal = useRef()
useEffect(() => {
window.paypal.Buttons({
style: {
color: 'gold',
},
createOrder: (data, actions) => {
return actions.order.create({
intent: 'CAPTURE',
purchase_units: [
{
description: 'Transaction du site E-Commerce.',
amount: {
currency_code: 'EUR',
value: prixTotal
}
}]
})
},
onApprove: (data, actions) => {
createCommande(true).then(response => {
PDFGeneration();
window.location.reload(false);
});
return actions.order.capture();
},
onCancel: (data, actions) => {
console.log("Transaction annulé");
console.log(prixTotal)
}
}).render(paypal.current)
}, [])
return (
<div>
<div ref={paypal}></div>
</div>
);
}
However, each product in the shopping cart is assigned a key that NORMALLY should trigger a re-render of the values for PayPal.
If anyone can explain/find me the problem, I would be grateful! Thanks again for your time.
In your PayPal component you do not pass any list to the useEffect hook as a second argument. That way it will be executed only when the component is mounted. If you want to update it when the props change you should pass the props that need to trigger it as a second argument list:
export default function Paypal({prixTotal, paniers})
{
// ...
useEffect(() => {
if (window.paypalBtn) window.paypalBtn.close();
window.paypalBtn = window.paypal.Buttons({
// ...
});
window.paypalBtn.render(paypal.current)
}, [prixTotal, paniers]);
// ...
}

How to manage multiple navigation items states?

I'm trying to implement a horizontal menu with antd components.
When clicking the nav items the submenu is not showing correctly.
Codesandbox demo.
const MenuList = [
{
name: "Navigation two - Submenu",
subMenuRoutes: [
{
name: "A- item1",
url: "/item1Url1"
},
{
name: "A - item2",
url: "/item1Url2"
}
]
},
{
name: "Navigation Three - Submenu",
subMenuRoutes: [
{
name: "B- item1",
url: "/item1Url1"
},
{
name: "B - item2",
url: "/item1Url2"
}
]
}
];
function TextAreaManager() {
const [showMenu, setShowMenu] = useState(false);
return (
<Tabs onTabClick={() => setShowMenu(prev => !prev)}>
{MenuList.map(item => {
return (
<TabPane
tab={
<>
<Icon type="setting" />
{item.name}
<Icon
type={showMenu ? "up" : "down"}
style={{ marginLeft: "10px" }}
/>
</>
}
>
{showMenu && (
<Menu>
{item.subMenuRoutes.map(childItem => {
return (
<Menu.Item key={childItem.url}>{childItem.name}</Menu.Item>
);
})}
</Menu>
)}
</TabPane>
);
})}
</Tabs>
);
There are a few issues that need to be handled:
Assign unique key for every array item in order to render components correctly.
menuList.map(item => <TabPane key={item.name}></TabPane>);
You need to manage every menu's state in order to show menus correctly with the corresponding icon showMenuManager[item.name]:
<Tabs
onTabClick={e =>
setShowMenuManager(prev => {
const newState = { ...initMenuState, [e]: !prev[e] };
console.log(newState);
return newState;
})
}
/>;
const initMenuState = {
"Navigation two - Submenu": false,
"Navigation Three - Submenu": false
};
function TopMenuManager() {
const [showMenuManager, setShowMenuManager] = useState(initMenuState);
return (
<Tabs ... >
{menuList.map(item => (
<TabPane
key={item.name}
tab={
<>
...
<Icon
type={showMenuManager[item.name] ? "up" : "down"}
/>
</>
}
>
{showMenuManager[item.name] && ...}
</TabPane>
))}
</Tabs>
);
}
Check the final example and forked sandbox:

Categories

Resources