I have a MenuComponent.js component which brings me data from an API and draws the products through a map.
import ProductCard from './Cards/ProductCard'
import {Container, Row, Col, Button} from 'react-bootstrap'
import '../styles/MenuComponent.css'
const MenuComponent = () => {
const [cart, setCart] = useState([])
const addToCart = (product) =>{
setCart([...cart, product])
}
const [productos, setProductos] = useState([])
useEffect(() => {
getProducts();
localStorage.setItem('cart', JSON.stringify(cart));
},[cart])
const getProducts = async() => {
try {
const res = await fetch('https://my-json-server.typicode.com/Fawkes11/FAKE_API_services/menu');
const data = await res.json();
setProductos(data);
} catch (error) {
console.log(error);
}
}
return (
<Container fluid className="menuComponentContainer p-5">
<Row style={{paddingLeft: '150px', paddingRight: '150px'}}>
<Col style={{display: 'flex', justifyContent:'center'}}>
<h2>Menú</h2>
</Col>
<Col style={{display: 'flex', justifyContent:'center'}}>
<Button className='productCardButton'>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-download me-2" viewBox="0 0 16 16">
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/>
<path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/>
</svg>
Descargar
</Button>
</Col>
</Row>
<Row style={{marginTop: '20px'}}>
{
productos.map((pro) => {
return(
<Col className='cElementsMenu' key={pro.id}>
{cart.length}
<ProductCard
addToCart={addToCart}
id={pro.id}
photo={pro.imagen}
name={pro.product}
type={pro.type}
size={pro.tamaño}
price={pro.precio}
/>
</Col>
)
})
}
</Row>
</Container>
)
}
export default MenuComponent;
Next the ProductCard.jsx component which is the structure of each product.
import {Card, Button} from 'react-bootstrap'
import '../../styles/productCard.css'
const ProductCard = (props) => {
const [hover, setHover] = useState(false);
return (
<Card className='productCard' style={{ width: '18rem', borderRadius: '20px' }}>
<div
className='productOverflow'
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
{hover && <Button className='buttonPreviewProduct'>Vista previa</Button>}
<Card.Img
className={`productCardImg ${hover && 'hover'}`}
variant="top"
src={props.photo}
/>
</div>
<Card.Body className='productCardBody'>
<Card.Title className='productCardtitle'>{props.name} {props.type}</Card.Title>
<Card.Text>$ {props.price}</Card.Text>
<Button className='productCardButton' onClick={() => props.addToCart(props)}>Agregar al carrito</Button>
</Card.Body>
</Card>
)
}
export default ProductCard;
now the problem, when adding to the cart it works fine and it even reflects the amount of products added, but when adding them to the LocalStorage and then trying to read them from the next component it does not render automatically, I have to update the page (F5) to make it look reflected my product account.
CartCounterIcon.jsx
import React, { useEffect } from 'react'
import { getCartItems } from '../../modules/cart'
function CartCounterIcon(props) {
return (
<div style={countIcon.div}><p style={countIcon.p}>{getCartItems().length}</p></div>
)
}
const countIcon = {
div: {
margin: '0',
padding: '0px 6px',
position: 'absolute',
right: '0',
top: '0',
backgroundColor: '#91491a',
borderRadius: '20%',
},
p: {
margin: '0',
color: '#eadac0',
fontWeight: 'bold',
}
}
export default CartCounterIcon
the following is the function that I import to get the data from localStorage. the problem is not saving them, the problem is that when reading the local data they do not render the CartCounterIcon.jsx component beforehand thanks for your help
cart.js
export function getCartItems(){
const data = localStorage.getItem("cart");
if(!data) return [];
return JSON.parse(data);
}
Related
I have no clue how to display the elements selected in my treeview. Ideas or tips ? please
The purpose of this treeview is to filter the data for export purposes.
The origin of the code comes from here (https://mui.com/material-ui/react-tree-view/#customization)
import React, {useEffect, useState} from 'react';
import ReactDOMServer from 'react-dom/server';
import TreeView from '#mui/lab/TreeView';
import TreeItem from '#mui/lab/TreeItem';
function MinusSquare(props) {
return (
<SvgIcon fontSize="inherit" style={{ width: 25, height: 25 }} {...props}>
{/* tslint:disable-next-line: max-line-length */}
<path d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z" />
</SvgIcon>
);
}
function PlusSquare(props) {
return (
<SvgIcon fontSize="inherit" style={{ width: 25, height: 25 }} {...props}>
{/* tslint:disable-next-line: max-line-length */}
<path d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 12.977h-4.923v4.896q0 .401-.281.682t-.682.281v0q-.375 0-.669-.281t-.294-.682v-4.896h-4.923q-.401 0-.682-.294t-.281-.669v0q0-.401.281-.682t.682-.281h4.923v-4.896q0-.401.294-.682t.669-.281v0q.401 0 .682.281t.281.682v4.896h4.923q.401 0 .682.281t.281.682v0q0 .375-.281.669t-.682.294z" />
</SvgIcon>
);
}
function CloseSquare(props) {
return (
<SvgIcon
className="close"
fontSize="inherit"
style={{ width: 25, height: 25 }}
{...props}
>
{/* tslint:disable-next-line: max-line-length */}
<path d="M17.485 17.512q-.281.281-.682.281t-.696-.268l-4.12-4.147-4.12 4.147q-.294.268-.696.268t-.682-.281-.281-.682.294-.669l4.12-4.147-4.12-4.147q-.294-.268-.294-.669t.281-.682.682-.281.696 .268l4.12 4.147 4.12-4.147q.294-.268.696-.268t.682.281 .281.669-.294.682l-4.12 4.147 4.12 4.147q.294.268 .294.669t-.281.682zM22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0z" />
</SvgIcon>
);
}
function TransitionComponent(props) {
const style = useSpring({
from: {
opacity: 0,
transform: 'translate3d(20px,0,0)',
},
to: {
opacity: props.in ? 1 : 0,
transform: `translate3d(${props.in ? 0 : 20}px,0,0)`,
},
});
return (
<animated.div style={style}>
<Collapse {...props} />
</animated.div>
);
}
TransitionComponent.propTypes = {
/**
* Show the component; triggers the enter or exit states
*/
in: PropTypes.bool,
};
const StyledTreeItem = styled((props) => (
<TreeItem {...props} TransitionComponent={TransitionComponent} />
))(({ theme }) => ({
[`& .${treeItemClasses.iconContainer}`]: {
'& .close': {
opacity: 0.3,
},
},
[`& .${treeItemClasses.group}`]: {
marginLeft: 15,
paddingLeft: 18,
borderLeft: `1px dashed ${alpha(theme.palette.text.primary, 0.4)}`,
},
}));
const TreeViewBuilder = ({data, ChoiceDate, ChoiceModality}) => {
return (
<>
{data.map((key, index) => {
if (ChoiceModality.includes(data[index]['modality']) && (Date.parse(ChoiceDate[0]) <= Date.parse(data[index]['startDate']) && Date.parse(data[index]['startDate']) <= Date.parse(ChoiceDate[1]))) {
return <StyledTreeItem nodeId={data[index]['series'][0] + "_" + index} label={data[index]['series'][0]}>
<StyledTreeItem nodeId={data[index]['series'][0] + "_" + data[index]['modality']} label={data[index]['modality']}/>
</StyledTreeItem>
}
})
}
</>
);
};
export default TreeViewBuilder;
Here you will find an example of the display I have.
you can get selected node/nodes of treeview from the onNodeSelect event on TreeView element like this
<TreeView
aria-label="customized"
defaultExpanded={['1']}
defaultCollapseIcon={<MinusSquare />}
defaultExpandIcon={<PlusSquare />}
defaultEndIcon={<CloseSquare />}
sx={{ height: 264, flexGrow: 1, maxWidth: 400, overflowY: 'auto' }}
onNodeSelect={handleSelectedItems}
>
The callback event will recieve nodeId/nodeIds like this
const handleSelectedItems = (event, nodeId) => {
console.log(nodeId);
}
If you are using multiSelect prop, nodeId will be an array containing the nodeIds of all selected nodes. If you are not using multiSelect it will be string with the selected node's nodeId.
I am trying to fetch data with RTK Query in next.js project and everything were fine until I had to fetch some more data from /api/exams endpoint. I have fetched almost everything from that endpoint and i know that's working fine but i still can't fetch some data from it. I'll provide screenshots of all code that's related to it. ok so here is the code where endpoints are:
Then let's continue with exams-response where i define body of endpoint:
Now I will provide code in my custom hook where i import that data from api/exams endpoint query:
And now i will show code of the actual page where i use them and where i think problem may lie also with another file which i will provide after this:
import { memo } from "react"
import { useIntl } from "react-intl"
import Stack from "#mui/material/Stack"
import Typography from "#mui/material/Typography"
import { ExamoTypesEnum } from "src/common/types/examo-types-enum"
import { rolesEnum } from "src/core/roles-enum"
import { useExamAssign } from "src/features/exams/hooks/use-exam-assign"
import { useExams } from "src/features/exams/hooks/use-exams"
import { useStartExam } from "src/features/exams/hooks/use-start-exam"
import { useIsMobile } from "src/helpers/use-is-mobile"
import { useAppSelector } from "src/redux/hooks"
import { ExamoCard } from "src/ui/examo/examo-card"
import { ExamoCardsGrid } from "src/ui/examo/examo-cards-grid"
import { ExamoHeader } from "src/ui/examo/examo-header"
import { CustomizedDialogs } from "src/ui/filter"
import { LoadingSpinner } from "src/ui/loading-spinner"
import { styled } from "src/ui/theme"
import { UnauthenticatedComponent } from "src/ui/unauthenticated"
import { useTags } from "../tags/hooks/use-tags"
import { useActiveExamQuery } from "./api"
const Ourbox = styled.div`
display: flex;
justify-content: space-between;
`
export const ExamsPage = memo(() => {
const isMobile = useIsMobile()
const userRole = useAppSelector((state) => state.auth.role)
const intl = useIntl()
const {
exams,
isLoadingExams,
selectedTags,
setSelectedTags,
checkedFilterTags,
setCheckedFilterTags,
} = useExams()
const { availableTags } = useTags()
const isLoadingAnExam = useAppSelector((state) => state.exam.isLoadingAnExam)
const { startAsync } = useStartExam()
const { data: activeExam, isFetching: isFetchingActiveExam } =
useActiveExamQuery(undefined, { refetchOnMountOrArgChange: 1 })
if (userRole === rolesEnum.None) {
return <UnauthenticatedComponent />
}
return (
<Stack sx={{ paddingX: isMobile ? 3 : "10vw", paddingY: 4 }} gap={4}>
<Ourbox>
<ExamoHeader
header={intl.formatMessage({
id: "exams-header",
defaultMessage: "Choose your exam",
})}
subtitle={intl.formatMessage({
id: "exams-header-subtitle",
defaultMessage:
"Our operators make quizzes and tests to help you upgrade and test your skills.",
})}
/>
<CustomizedDialogs
id="exams-page-filter"
selectedTags={selectedTags}
setSelectedTags={setSelectedTags}
availableTags={availableTags || []}
checkedFilterTags={checkedFilterTags}
setCheckedFilterTags={setCheckedFilterTags}
/>
</Ourbox>
{isLoadingExams && <LoadingSpinner />}
{!isLoadingExams && (!exams || exams.length === 0) && (
<Typography>
{intl.formatMessage({
id: "no-exams-available",
defaultMessage: "No exams available",
})}
</Typography>
)}
{exams && exams.length > 0 && (
<ExamoCardsGrid>
{exams.map((exam) => (
<ExamoCard
key={exam.id}
type={ExamoTypesEnum.EXAM}
useAssign={useExamAssign}
isStartButtonDisabled={
isLoadingAnExam ||
isFetchingActiveExam ||
(activeExam?.exam?.id !== undefined &&
exam.id !== activeExam.exam.id)
}
isResuming={
activeExam?.exam?.id !== undefined &&
exam.id === activeExam.exam.id
}
handleStart={() => startAsync(exam.id)}
isLoading={isLoadingAnExam}
title={exam.title}
duration={exam.duration}
tags={[
...new Set(exam.templates?.flatMap((et) => et.tags) || []),
]}
numberOfQuestions={exam.templates.reduce(
(total, current) => total + current.numberOfQuestions,
0,
)}
deadline-start={exam["deadline-start"]}
deadline-end={exam["deadline-end"]}
i={exam.id}
/>
))}
</ExamoCardsGrid>
)}
</Stack>
)
})
and the last one which is as mapped through in above the code. so it's :
import { memo } from "react"
import * as React from "react"
import { useIntl } from "react-intl"
import AccessTimeIcon from "#mui/icons-material/AccessTime"
import ExpandMoreIcon from "#mui/icons-material/ExpandMore"
import MoreHorizIcon from "#mui/icons-material/MoreHoriz"
import Box from "#mui/material/Box"
import Card from "#mui/material/Card"
import MuiChip from "#mui/material/Chip"
import Collapse from "#mui/material/Collapse"
import Grid from "#mui/material/Grid"
import IconButton, { IconButtonProps } from "#mui/material/IconButton"
import Stack from "#mui/material/Stack"
import { styled } from "#mui/material/styles"
import Typography from "#mui/material/Typography"
import { ExamoTypesEnum } from "src/common/types/examo-types-enum"
import { useAssignType } from "src/common/types/use-assign-type"
import { useAppSelector } from "src/redux/hooks"
import { ExamoAssignTo } from "src/ui/examo/examo-assign-to"
import { ExamoStartDialogBtn } from "src/ui/examo/examo-start-btn"
interface props {
duration: string | null
title: string
tags: string[] | null
numberOfQuestions: number | null
isLoading: boolean
handleStart: () => void
useAssign: useAssignType
isStartButtonDisabled: boolean
isResuming: boolean
type: ExamoTypesEnum
i: number
"deadline-start": string
"deadline-end": string
}
export const ExamoCard = memo(
({
duration,
tags,
title,
isLoading,
numberOfQuestions,
isStartButtonDisabled,
isResuming,
type,
handleStart,
useAssign,
i,
"deadline-end": deadlineEnd,
"deadline-start": deadlineStart,
}: props) => {
console.log(deadlineStart)
const intl = useIntl()
const user = useAppSelector((state) => state.auth)
const durationHours = duration?.split(":")[0]
const durationMinutes = duration?.split(":")[1]
// const [expanded, setExpanded] = React.useState(false)
const [expandedId, setExpandedId] = React.useState(-1)
// const handleExpandClick = () => {
// setExpanded(!expanded)
// }
const handleExpandClick = (i: number) => {
setExpandedId(expandedId === i ? -1 : i)
}
const preventParentOnClick = (event: React.MouseEvent<HTMLElement>) => {
event.stopPropagation()
}
interface ExpandMoreProps extends IconButtonProps {
expand: boolean
}
const ExpandMore = styled((props: ExpandMoreProps) => {
const { expand, ...other } = props
return <IconButton {...other} />
})(({ theme, expand }) => ({
transform: !expand ? "rotate(0deg)" : "rotate(180deg)",
transition: theme.transitions.create("transform", {
duration: theme.transitions.duration.shortest,
}),
}))
return (
<Grid item xs={12} lg={6}>
<Card
onClick={() => handleExpandClick(i)}
aria-expanded={expandedId === i}
elevation={2}
sx={{
padding: "1rem",
height: "100%",
borderRadius: "1rem",
border: "solid 1px var(--palette-grey-400)",
transition: "all 0.1s ease-in-out",
":hover": {
backgroundColor: "var(--palette-grey-100)",
cursor: "pointer",
},
}}
>
<Stack direction="row" justifyContent="space-between">
<Stack
gap={numberOfQuestions ? 1.5 : 6}
sx={{
width: "100%",
}}
>
<Stack
direction="row"
sx={{
justifyContent: "space-between",
}}
>
<Typography
variant="h5"
sx={{ whiteSpace: "pre-wrap", wordBreak: "break-all" }}
>
{title}
</Typography>
<ExpandMore expand={expandedId === i}>
<ExpandMoreIcon />
</ExpandMore>
<Stack
direction="row"
gap={1}
sx={{
justifyContent: "flex-end",
alignItems: "center",
marginTop: "-1.75rem",
}}
>
<AccessTimeIcon />
<Typography
whiteSpace="nowrap"
variant="h6"
>{`${durationHours}h ${durationMinutes}m`}</Typography>
</Stack>
</Stack>
<Collapse in={expandedId === i} timeout="auto" unmountOnExit>
<Stack direction="row" gap={4}>
{numberOfQuestions && (
<Typography variant="h6">
{`${numberOfQuestions} ${intl.formatMessage({
id: "questions",
defaultMessage: "Questions",
})}`}
</Typography>
)}
</Stack>
<Stack
direction="row"
sx={{ flexWrap: "wrap", gap: 1, marginTop: "1rem" }}
>
{tags?.map((t, index) => (
<MuiChip
key={index}
label={t}
variant="filled"
sx={{ fontWeight: "bold" }}
color="secondary"
size="small"
/>
))}
</Stack>
</Collapse>
</Stack>
<Stack
sx={{ marginTop: "-0.5rem" }}
justifyContent="space-between"
alignItems="center"
spacing={user.role === "Operator" ? 0.1 : 1}
>
<MoreHorizIcon sx={{ marginLeft: "2rem" }} />
<Box onClick={preventParentOnClick}>
<Stack sx={{ marginLeft: "1.5rem" }}>
{user.role === "Operator" && (
<ExamoAssignTo useAssign={useAssign} title={title} />
)}
</Stack>
</Box>
<Box onClick={preventParentOnClick}>
<ExamoStartDialogBtn
type={type}
isResuming={isResuming}
handleStart={handleStart}
isLoading={isLoading}
isDisabled={isStartButtonDisabled}
/>
</Box>
</Stack>
</Stack>
</Card>
</Grid>
)
},
)
To sum up guys, I want to also fetch deadlineStart and deadlineEnd but i can't. I think problem is in last file or second to last, because maybe am not defining them properly in interface props in last code. And also i almost forgot to mention that when I try to console.log(deadlineStart) in the last code it says undefined in the browser. Edited Network Pic :
here is screenshot when i console log single exams :
As I am new in react js, in advance I am thankful to all of you.
I am using React table, and I have TablestableRows.js so my button for delete is here but when I call API in this child component it's not working. when I call my API in the parent component AllLeads.js then it's working the API, so now I want to delete one of my elements through an API call. this is the code and API that I am using for this tutorial.
This is the Mapping component that all my data mapping through it:
import {
Table,
Tbody,
Text,
Th,
Thead,
Tr,
useColorModeValue,
} from "#chakra-ui/react";
// Custom components
import Card from "../Card/Card.js";
import CardBody from "../Card/CardBody.js";
import TablesTableRow from "./TablesTableRow";
import React from "react";
const LeadsTable = ({captions, data }) => {
const textColor = useColorModeValue("gray.700", "white");
return (
<Card overflowX={{ sm: "scroll" }} p={6} borderRadius='lg' h={"85vh"}>
<CardBody>
<Table variant='simple' color={textColor} >
<Thead>
<Tr my='.8rem' pl='0px' color='gray.400'>
{captions.map((caption, idx) => {
return (
<Th color='gray.400' key={idx} ps={idx === 0 ? "0px" : null}>
{caption}
</Th>
);
})}
</Tr>
</Thead>
<Tbody>
{data.map((row) => {
return (
<TablesTableRow
key={`${row._id}`}
id={`${row._id}`}
name={row.name}
email={row.email}
phone_no={row.phone_no}
domain={row.domain}
lead_state={row.lead_state}
lead_date={row.lead_date}
graduation_status={row.graduation_status}
// graduation_status
/>
);
})}
</Tbody>
</Table>
</CardBody>
</Card>
);
};
export default LeadsTable;
This is the component that I have CTA Button on it:
import React, { useEffect } from "react";
import {
Badge,
Button,
Flex,
Td,
Text,
Tr,
useColorModeValue,
} from "#chakra-ui/react";
import { Link } from 'react-router-dom';
import { useDispatch } from "react-redux";
import { deleteLeadsAdmin, getLeadsById } from "../../redux/Leads/actionCreator";
const TablesTableRow = (props) => {
const dispatch = useDispatch
const {id, name, email, phone_no, lead_state, lead_date, graduation_status } = props;
const textColor = useColorModeValue("gray.700", "white");
const colorStatus = useColorModeValue("white", "gray.400");
const createdProjectDate = lead_date.split("T", 1);
const onDeleteLead = (e)=>{
e.preventDefault();
console.log(id,'id')
dispatch(deleteLeadsAdmin(id)).then(res=>{
console.log(res,'leads deleted by is')
})
}
return (
<Tr>
<Td pl="0px">
<Flex align="center" py=".8rem" minWidth="100%" flexWrap="nowrap">
<Flex direction="column">
<Text
fontSize="md"
color={textColor}
fontWeight="bold"
minWidth="100%"
>
{name}
</Text>
</Flex>
</Flex>
</Td>
<Td>
<Flex direction="column">
<Text fontSize="sm" color={textColor}
fontWeight="normal">
{email}
</Text>
</Flex>
</Td>
<Td>
<Flex direction="column">
<Text fontSize="sm" color={textColor}
fontWeight="normal">
{phone_no}
</Text>
</Flex>
</Td>
<Td>
<Badge
// bg={status === "Active" ? "green.400" : bgStatus}
bg={lead_state === 0 ? 'pink.500' : lead_state === 1 ? 'green.400' : lead_state === 2 ? 'gray.600' : 'orange.200'}
// color={status === "Active" ? "white" : colorStatus}
color={lead_state === 0 && 1 && 2 ? 'white' : colorStatus}
fontSize="16px"
p="3px 10px"
borderRadius="8px"
>
{lead_state === 0 ? 'Raw Lead' : lead_state === 1 ? 'Converted Leads' : lead_state === 2 ? 'Registerd Leads' : '-'}
</Badge>
</Td>
<Td>
<Flex direction="column">
<Text fontSize="sm" color={textColor}
fontWeight="normal">
{graduation_status}
</Text>
</Flex>
</Td>
{/* graduation_status */}
<Td>
<Flex direction="column">
<Text fontSize="sm" color={textColor}
fontWeight="normal">
{createdProjectDate} // this is the date of each new leads
</Text>
</Flex>
</Td>
<Td>
<Button p="0px" bg="transparent" variant="hover" _hover={{ bg: "#1e91ff", color: "#fff" }} me="10px">
<Link to={`/leads/all-leads/${id}`} >
<Text
fontSize="md"
color={textColor}
fontWeight="bold"
cursor="pointer"
_hover={{ color: "#fff" }}
p={5}
>
Edit
</Text>
</Link>
</Button>
<Button p="0px" bg="transparent" variant="hover" _hover={{ bg: "#f54b4b", color: "#fff" }}
onClick={onDeleteLead}>
<Link to={`/leads/all-leads/${id}`} >
<Text
fontSize="md"
color={textColor}
fontWeight="bold"
cursor="pointer"
_hover={{ color: "#fff" }}
p={5}
>
Delete
</Text>
</Link>
</Button>
</Td>
</Tr>
);
}
export default TablesTableRow;
This is the parent where I call my APIs:
import React, { useState, useEffect } from "react";
import {
Flex,
WrapItem,
Button,
Box,
useColorModeValue,
} from "#chakra-ui/react";
import LeadsTable from "../../component/LeadsTable//LeadsTable";
import { useDispatch } from "react-redux";
import {
deleteLeadsAdmin,
getLeadsForAdmin,
} from "../../redux/Leads/actionCreator";
import HeadTitle from "../../component/HeadTitle/HeadTitle";
import { BodyMain } from "../../component/HeadTitle/BodyMain";
import { useParams } from "react-router";
function AllLeads() {
const dispatch = useDispatch();
const query = useParams();
const [listingLeadsList, setListingLeadsList] = useState([]); // use in listing project list
const [pageLeads, setPageLeads] = useState(5); // use in listing project list
console.log(pageLeads, "page number");
const [id, setID] = useState([]);
console.log(id, "this is the id of leads all leads=");
const chagneNextPage = () => {
setPageLeads(pageLeads + 1);
};
const chagnePreviousPage = () => {
setPageLeads(pageLeads - 1);
};
// console.log(currentPage,' currentPage')
// console.log('this is the leads', listingLeadsList)
const ProjectsForAdmin = () => {
dispatch(getLeadsForAdmin(pageLeads))
.then((res) => {
if (res.success) {
//set data in state to use in listing
setListingLeadsList(res.data.lead);
console.log(res.data.lead._id, "LEADS LIST FRON END");
// setID(res.data.lead[1]._id);
setPageLeads(res.data.next.page);
// setCurrentPage(res.data.next.pag);
} else {
console.log("err Leads", res);
}
})
.catch((err) => console.log("errr leads", err));
};
useEffect(() => {
ProjectsForAdmin();
}, []);
// const onDeleteLead = (e)=>{
// e.preventDefault();
// console.log(id,'id')
// dispatch(deleteLeadsAdmin(id)).then(res=>{
// console.log(res,'leads deleted by is')
// })
// }
const pagesQuantity = 12;
return (
<Flex
flexDirection="column"
h={"100vh"}
px="30px"
bg={useColorModeValue("#f3f2f1", "gray.900")}
>
<HeadTitle title="Leads List" />
<BodyMain>
<WrapItem w={"100%"}>
<Box
bg={useColorModeValue("#fff", "gray.800")}
borderRadius="lg"
w={"100%"}
>
<LeadsTable
padding={20}
title={"Leads Table"}
captions={[
"Name",
"Email",
"Phone",
"Status",
"Graduation Status",
"Date",
"Action",
]}
data={listingLeadsList}
/>
{/* <Button type='submit' onClick={onDeleteLead} me={10}>delete</Button> */}
<Button type="submit" onClick={chagnePreviousPage}>
Previous
</Button>
</Box>
</WrapItem>
</BodyMain>
</Flex>
);
}
export default AllLeads;
Issue resolved: it was about hooks and now I am able to send the id to the API URL useDispatch()
you can try moving you delete function up a level to the parent and passing it to your child table as a prop, then the function will be called at a level above.
How can I send the value of the checkbox to the checkout.js page? This is the PaymentForm page. I tried my best but it's not working correctly. Basically, I want to use the PaymentForm fields in checkout.js page because my submit button is there.
PaymentForm.js
import React from 'react';
import Typography from '#material-ui/core/Typography';
import Grid from '#material-ui/core/Grid';
import TextField from '#material-ui/core/TextField';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import Checkbox from '#material-ui/core/Checkbox';
import { createStyles } from '#material-ui/core/styles';
import StripeCheckout from 'react-stripe-checkout'
import 'react-toastify/dist/ReactToastify.css';
const styles = createStyles({
formControlLabel: {
fontSize: '1.5rem',
'& label': { fontSize: '5rem' }
}
});
const handleToken = (token) => {
console.log(token);
}
const PaymentForm = ({ PaymentForm, changePaymentForm }) => {
const [state, setState] = React.useState({
checkedA: false,
});
const handleChange = (event) => {
setState({ ...state, [event.target.name]: event.target.checked });
};
return (
<React.Fragment>
<Typography variant="h4" gutterBottom>
Payment method
</Typography><br />
<Grid container spacing={3}>
<Grid item xs={12}>
<FormControlLabel
control={<Checkbox checked={state.checkedA} onChange={handleChange} name="checkedA"/>}
label={<Typography style={styles.formControlLabel}>Cash on delivery</Typography>}
/>
</Grid>
<Grid item xs={12}>
<StripeCheckout
stripeKey="pk_test_51I9XPQAesAg2GfzQyVB7VgP0IbmWwgcfeFJSuCpB2kbNu60AFTbFhC7dxwje8YF4w2ILMJ6o2InB9ENczpd4dCSa00e09XoDbw"
token={handleToken}
amount={2 * 100}
name="All Products"
/>
</Grid>
</Grid>
</React.Fragment>
);
};
export default PaymentForm;
Checkout.js
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import CssBaseline from '#material-ui/core/CssBaseline';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import Paper from '#material-ui/core/Paper';
import Stepper from '#material-ui/core/Stepper';
import Step from '#material-ui/core/Step';
import StepLabel from '#material-ui/core/StepLabel';
import Button from '#material-ui/core/Button';
import Link from '#material-ui/core/Link';
import Typography from '#material-ui/core/Typography';
import axios from '../../axios-orders';
import AddressForm from './CheckoutForm';
import PaymentForm from './PaymentForm';
import Review from './Review';
const useStyles = makeStyles((theme) => ({
appBar: {
position: 'relative',
},
layout: {
width: 'auto',
marginLeft: theme.spacing(2),
marginRight: theme.spacing(2),
[theme.breakpoints.up(1000 + theme.spacing(2) * 2)]: {
width: 1100,
marginLeft: 'auto',
marginRight: 'auto',
},
},
paper: {
marginTop: theme.spacing(3),
marginBottom: theme.spacing(3),
padding: theme.spacing(2),
[theme.breakpoints.up(700 + theme.spacing(3) * 2)]: {
marginTop: theme.spacing(6),
marginBottom: theme.spacing(6),
padding: theme.spacing(3),
backgroundColor: 'rgb(248, 246, 244)',
},
},
stepper: {
padding: theme.spacing(5, 0, 5),
fontWeight: 'bold',
backgroundColor: 'rgb(248, 246, 244)',
},
buttons: {
display: 'flex',
justifyContent: 'flex-end',
},
button: {
marginTop: theme.spacing(3),
marginLeft: theme.spacing(1),
border: "none"
},
}));
const steps = ['Shipping address', 'Payment details', 'Review your order'];
function Copyright() {
return (
<Typography variant="body2" color="textSecondary" align="center">
{'Copyright © '}
<Link color="inherit" href="https://material-ui.com/">
Your Website
</Link>{' '}
{new Date().getFullYear()}
{'.'}
</Typography>
);
}
function getStepContent(step, formValues = null, changeFormValue = null, paymentValues = null, changePaymentValue = null) {
switch (step) {
case 0:
return <AddressForm addressValues={formValues} changeAddressValue={changeFormValue} />;
case 1:
return <PaymentForm PaymentForm={paymentValues} changePaymentForm={changePaymentValue}/>;
case 2:
return <Review />;
default:
throw new Error('Unknown step');
}
}
export default function Checkout(props) {
const classes = useStyles();
const [addressFormValues, setAddressFormValues] = React.useState({});
const [paymentFormValues, setPaymentFormValues] = React.useState({});
const [paymentFormNewValues, setPaymentFormNewValues] = React.useState({});
const [activeStep, setActiveStep] = React.useState(0);
if(paymentFormValues === true){
setPaymentFormNewValues('Cash')
}
if(paymentFormValues === false){
setPaymentFormNewValues('Online')
}
console.log('[paymentFormNewValues: ]', paymentFormNewValues)
console.log('[paymentFormValues: ]', paymentFormValues)
const handleNext = () => {
setActiveStep(activeStep + 1);
axios.post('/UserPortal/CartItems/checkout_details_check.php', {
customer_id: localStorage.getItem('Id'),
})
.then((response) => {
if(response.data === null)
{
axios.post('/UserPortal/CartItems/checkout_details.php', {
customer_id: localStorage.getItem('Id'),
firstname: addressFormValues.firstname,
lastname: addressFormValues.lastname,
address: addressFormValues.address,
city: addressFormValues.city,
state: addressFormValues.state
})
.then((response) => {
console.log(response.data);
})
}
else{
axios.post('/UserPortal/CartItems/checkout_details_update.php', {
customer_id: localStorage.getItem('Id'),
firstname: addressFormValues.firstname,
lastname: addressFormValues.lastname,
address: addressFormValues.address,
city: addressFormValues.city,
state: addressFormValues.state,
payment_method: paymentFormNewValues
})
.then((response) => {
console.log(response.data);
})
}
})
};
const handleBack = () => {
setActiveStep(activeStep - 1);
};
const changeAddressFormValue = (key, value) => {
let values = { ...addressFormValues };
values[key] = value;
setAddressFormValues(values);
};
const changePaymentFormValue = (key, value) => {
let values = { ...addressFormValues };
values[key] = value;
setPaymentFormValues(values);
};
return (
<React.Fragment>
<CssBaseline />
<AppBar position="absolute" color="default" className={classes.appBar}></AppBar>
<main className={classes.layout}>
<Paper className={classes.paper}>
<Typography component="h1" variant="h3" align="center">
Checkout
</Typography>
<Stepper activeStep={activeStep} className={classes.stepper}>
{steps.map((label) => (
<Step key={label}>
<StepLabel><Typography component="h1" variant="h5" align="center">
{label} </Typography></StepLabel>
</Step>
))}
</Stepper>
<React.Fragment>
{activeStep === steps.length ? (
<React.Fragment>
<Typography variant="h5" gutterBottom>
Thank you for your order.
</Typography>
<Typography variant="subtitle1">
Your order number is #2001539. We have emailed your order
confirmation, and will
send you an update when your order has shipped.
</Typography>
</React.Fragment>
) : (
<React.Fragment>
{activeStep === 0 ? getStepContent(activeStep, addressFormValues, changeAddressFormValue , paymentFormValues, changePaymentFormValue) : getStepContent(activeStep)}
{ <div className={classes.buttons}>
{ activeStep !== 0 && (
<Button variant="contained" style={{outline: 'none'}}
className={classes.button}
onClick={handleBack}
>
Back
</Button>
)}
<Button style={{outline: 'none'}}
variant="contained"
color="secondary"
onClick={handleNext}
className={classes.button}
>
{activeStep === steps.length - 1 ? 'Place order' : 'Next'}
</Button>
</div> }
</React.Fragment>
)}
</React.Fragment>
</Paper>
<Copyright />
</main>
</React.Fragment>
);
}
It seems you have checked the activeStep wrong.
Maybe the right code must be like the following:
<React.Fragment>
{activeStep !== 0 ?getStepContent(activeStep, addressFormValues, changeAddressFormValue , paymentFormValues, changePaymentFormValue) : getStepContent(activeStep)}
Do you ever consider using Context API?
React Context API
But also you can use create a checkboxValue with useState in Checkout.js and pass setCheckBoxValue to PaymentForm in getStepContent as prop. When checkbox checked trigger setCheckBoxValue and it will trigger parent component's state (Checkbox).
I just implemented a global search in my website and I started having issues with React-Router. It is not updating the view if the url changes parameters.
For example, navigating from /users/454545 to /teams/555555 works as expected. However, navigating from /teams/111111 to teams/222222 changes the url but the component is still /teams/111111.
Here is my code fo the Search Input field.
const SearchResult = ({ id, url, selectResult, text, type }) => (
<Row key={id} onClick={() => selectResult(url)} width='100%' padding='5px 15px 5px 15px' style={{cursor: 'pointer'}}>
<Column alignItems='flex-start' style={{width: '100%'}}>
<Label textAlign='left' color='#ffffff'>{text}</Label>
</Column>
<Column style={{width: '100%'}}>
<Label textAlign='right' color='#ffffff'>{type}</Label>
</Column>
</Row>
)
const SearchInput = (props) => {
const { isSearching, name, onChange, onClear, results } = props;
return (
<Section width='100%' style={{display: 'flex', position: 'relative'}}>
<Wrapper height={props.height} margin={props.margin}>
<i className="fas fa-search" style={{color: 'white'}} />
<input id='search_input' placeholder={'Search for a team, circuit, or user'} name={name} onChange={onChange} style={{outline: 'none', backgroundColor: 'transparent', borderColor: 'transparent', color: '#ffffff', width: '100%'}} />
{onClear && !isSearching && <i onClick={onClear} className="fas fa-times-circle" style={{color: '#50E3C2'}} />}
{isSearching &&
<Spinner viewBox="0 0 50 50" style={{marginBottom: '0px', height: '50px', width: '50px'}}>
<circle
className="path"
cx="25"
cy="25"
r="10"
fill="none"
strokeWidth="4"
/>
</Spinner>
}
</Wrapper>
{results && <Section backgroundColor='#00121A' border='1px solid #004464' style={{maxHeight: '400px', position: 'absolute', top: '100%', left: '0px', width: '97%', overflowY: 'scroll'}}>
<Section backgroundColor='#00121A' style={{display: 'flex', flexDirection: 'column', padding: '15px 0px 0px 0px', justifyContent: 'center', alignItems: 'center', width: '100%'}}>
{results.length === 0 && <Text padding='0px 0px 15px 0px' color='#ffffff' fontSize='16px'>We didn't find anything...</Text>}
{results.length !== 0 && results.map(r => <SearchResult selectResult={props.selectResult} id={r._id} url={r.url} text={r.text} type={r.type} />)}
</Section>
</Section>}
</Section>
)
}
export default SearchInput;
The parent component is a nav bar which looks something like this. I've slimmed it down for readability.
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import SearchInput from '../shared/inputs/SearchInput';
const TopNav = (props) => {
const [search, setSearch] = useState(null);
const [searchResults, setSearchResults] = useState(null);
const debouncedSearchTerm = useDebounce(search, 300);
const [isSearching, setIsSearching] = useState(false);
function clearSearch() {
document.getElementById('search_input').value = '';
setSearchResults(null);
}
function searchChange(e) {
if (!e.target.value) return setSearchResults(null);
setSearch(e.target.value);
setIsSearching(true);
}
async function updateQuery(query) {
const data = {
search: query
}
const results = await api.search.query(data);
setSearchResults(results);
setIsSearching(false);
}
function selectResult(url) {
props.history.push(url);
setSearchResults(null);
}
function useDebounce(value, delay) {
// State and setters for debounced value
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(
() => {
// Update debounced value after delay
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
// Cancel the timeout if value changes (also on delay change or unmount)
// This is how we prevent debounced value from updating if value is changed ...
// .. within the delay period. Timeout gets cleared and restarted.
return () => {
clearTimeout(handler);
};
},
[value, delay] // Only re-call effect if value or delay changes
);
return debouncedValue;
}
useEffect(() => {
if (debouncedSearchTerm) {
updateQuery(debouncedSearchTerm);
} else {
setSearchResults(null);
}
}, [user, debouncedSearchTerm])
return (
<ContentContainer style={{boxShadow: '0 0px 0px 0 #000000', position: 'fixed', zIndex: 1000}} backgroundColor='#00121A' borderRadius='0px' width='100%'>
<Section style={{display: 'flex', justifyContent: 'center', alignItems: 'center', height: '50px'}} width='1200px'>
<SearchInput height={'30px'} margin='0px 20px 0px 0px' isSearching={isSearching} selectResult={selectResult} onChange={searchChange} onClear={clearSearch} results={searchResults} />
</Section>
</ContentContainer>
)
}
function mapStateToProps(state) {
return {
user: state.user.data,
notifs: state.notifs
}
}
export default connect(mapStateToProps, { logout, fetchNotifs, updateNotifs })(TopNav);
Tl;DR
Using react-router for site navigation. Doesn't update component if navigating from /teams/111111 to /teams/222222 but does update if navigating from /users/111111 to /teams/222222.
Any and all help appreciated!
When a URL's path changes, the current Component is unmounted and the new component pointed by the new URL is mounted. However, when a URL's param changes, since the old and new URL path points to the same component, no unmount-remount takes place; only the already mounted component receives new props. One can make use of these new props to fetch new data and render updated UI.
Suppose your param id is parameter.
With hooks:
useEffect(() => {
// ... write code to get new data using new prop, also update your state
}, [props.match.params.parameter]);
With class components:
componentDidUpdate(prevProps){
if(this.props.match.params.parameter!== prevProps.match.params.parameter){
// ... write code to get new data using new prop, also update your state
}
}
Use KEY:
Another approach could be to use the unique key prop. Passing a new key will force a
component to remount.
<Route path="/teams/:parameter" render={(props) => (
<Team key={props.match.params.parameter} {...props} />
)} />
Re-render does not cause component to re-mount so use useEffect hook to call initializing logic in your component whenever props changes and update your state in the callback.
useEffect(() => {
//Re initialize your component with new url parameter
}, [props]);