I'm running into a performance issue. I'm not sure if it's because I'm using multiple useEffect's (which I'm trying to limit their action by having them watch the relevant state)?
Or maybe because I am using a bunch of if statements? Or all the filters I'm performing?
The goal is for the user to be able to check and uncheck different features and a resulting price be computed. The computed price woud be different depending on a selected option (annual / subscription).
You can see the issue here: https://prisma2-frontend-project.now.sh/
If you scroll down to the pricing section, you can see that if you start selecting and unselecting one or two checkboxes it's not really an issue. But when you start doing more, or switch to the purchase tab, things start to really bog down. The math even starts going wonky! haha
The relevant code:
import React, { useState, useEffect, useRef } from "react";
import PropTypes from "prop-types";
import Link from "next/link";
import Box from "reusecore/src/elements/Box";
import Text from "reusecore/src/elements/Text";
import Heading from "reusecore/src/elements/Heading";
import Checkbox from "reusecore/src/elements/Checkbox";
import Button from "reusecore/src/elements/Button";
import Container from "common/src/components/UI/Container";
import GlideCarousel from "common/src/components/GlideCarousel";
import GlideSlide from "common/src/components/GlideCarousel/glideSlide";
import { useToggle } from "reusecore/src/hooks";
import {
MONTHLY_PRICING_TABLE,
YEARLY_PRICING_TABLE
} from "common/src/data/SaasClassic";
import PricingTable, {
PricingHead,
PricingPrice,
PricingButton,
PricingList,
ListItem,
PricingButtonWrapper,
PricingTableWrapper
} from "./pricing.style";
const PricingSection = ({
sectionWrapper,
isChecked,
secTitleWrapper,
secHeading,
secText,
nameStyle,
descriptionStyle,
priceStyle,
priceLabelStyle,
buttonFillStyle,
listContentStyle
}) => {
const [state, setState] = useState({
data: MONTHLY_PRICING_TABLE,
active: true
});
const [loading, setLoading] = useState(false);
useEffect(() => {
const sumSubscriptionBusiness = businessState.subscription.cost.reduce(
(total, next) => total + Number(next),
0
);
const sumPurchaseBusiness = businessState.purchase.cost.reduce(
(total, next) => total + Number(next),
0
);
setBusinessTotal({
purchase: sumPurchaseBusiness,
subscription: sumSubscriptionBusiness
});
}, businessState);
useEffect(() => {
const sumSubscriptionInformation = informationState.subscription.cost.reduce(
(total, next) => total + Number(next),
0
);
const sumPurchaseInformation = informationState.purchase.cost.reduce(
(total, next) => total + Number(next),
0
);
setInformationTotal({
purchase: sumPurchaseInformation,
subscription: sumSubscriptionInformation
});
}, informationState);
useEffect(() => {
const sumSubscriptionEcommerce = ecommerceState.subscription.cost.reduce(
(total, next) => total + Number(next),
0
);
const sumPurchaseEcommerce = ecommerceState.purchase.cost.reduce(
(total, next) => total + Number(next),
0
);
setEcommerceTotal({
purchase: sumPurchaseEcommerce,
subscription: sumSubscriptionEcommerce
});
}, ecommerceState);
const [toggleValue, toggleHandler] = useToggle(isChecked);
const [businessState, setBusinessState] = useState({
purchase: {
service: [],
cost: []
},
subscription: {
service: [],
cost: []
}
});
const [informationState, setInformationState] = useState({
purchase: {
service: [],
cost: []
},
subscription: {
service: [],
cost: []
}
});
const [ecommerceState, setEcommerceState] = useState({
purchase: {
service: [],
cost: []
},
subscription: {
service: [],
cost: []
}
});
const [businessTotal, setBusinessTotal] = useState({
purchase: [],
subscription: []
});
const [informationTotal, setInformationTotal] = useState({
purchase: [],
subscription: []
});
const [ecommerceTotal, setEcommerceTotal] = useState({
purchase: [],
subscription: []
});
const handleChecking = (e, item) => {
const { name } = e.target;
const { id } = e.target;
const newService = item.service;
const newCost = item.cost;
const newPCost = item.pcost;
if (
name === "business" &&
businessState.subscription.service[0] !== undefined &&
!Object.values(businessState.subscription.service).includes(newService)
) {
const oldBusinessPurchaseService = businessState.purchase.service;
const oldBusinessPurchaseCost = businessState.purchase.cost;
const oldBusinessSubscriptionService = businessState.subscription.service;
const oldBusinessSubscriptionCost = businessState.subscription.cost;
setBusinessState({
purchase: {
service: [...oldBusinessPurchaseService, newService],
cost: [...oldBusinessPurchaseCost, newPCost]
},
subscription: {
service: [...oldBusinessSubscriptionService, newService],
cost: [...oldBusinessSubscriptionCost, newCost]
}
});
}
if (
name === "business" &&
businessState.subscription.service[0] !== undefined &&
Object.values(businessState.subscription.service).includes(newService)
) {
const removeBusinessIndex = businessState.subscription.service.indexOf(
newService
);
const reducedBusinessSubscriptionService = businessState.subscription.service.filter(
(s, i) => i !== removeBusinessIndex
);
const reducedBusinessSubscriptionCost = businessState.subscription.cost.filter(
(s, i) => i !== removeBusinessIndex
);
const reducedBusinessPurchaseService = businessState.purchase.service.filter(
(s, i) => i !== removeBusinessIndex
);
const reducedBusinessPurchaseCost = businessState.purchase.cost.filter(
(s, i) => i !== removeBusinessIndex
);
setBusinessState({
purchase: {
service: [...reducedBusinessPurchaseService],
cost: [...reducedBusinessPurchaseCost]
},
subscription: {
service: [...reducedBusinessSubscriptionService],
cost: [...reducedBusinessSubscriptionCost]
}
});
}
if (
name === "business" &&
businessState.subscription.service[0] === undefined
) {
setBusinessState({
purchase: {
service: [newService],
cost: [newPCost]
},
subscription: {
service: [newService],
cost: [newCost]
}
});
}
if (
name === "information" &&
informationState.subscription.service[0] !== undefined &&
!Object.values(informationState.subscription.service).includes(newService)
) {
const oldInformationPurchaseService = informationState.purchase.service;
const oldInformationPurchaseCost = informationState.purchase.cost;
const oldInformationSubscriptionService =
informationState.subscription.service;
const oldInformationSubscriptionCost = informationState.subscription.cost;
setInformationState({
purchase: {
service: [...oldInformationPurchaseService, newService],
cost: [...oldInformationPurchaseCost, newPCost]
},
subscription: {
service: [...oldInformationSubscriptionService, newService],
cost: [...oldInformationSubscriptionCost, newCost]
}
});
}
if (
name === "information" &&
informationState.subscription.service[0] !== undefined &&
Object.values(informationState.subscription.service).includes(newService)
) {
const removeInformationIndex = informationState.subscription.service.indexOf(
newService
);
const reducedInformationSubscriptionService = informationState.subscription.service.filter(
(s, i) => i !== removeInformationIndex
);
const reducedInformationSubscriptionCost = informationState.subscription.cost.filter(
(s, i) => i !== removeInformationIndex
);
const reducedInformationPurchaseService = informationState.purchase.service.filter(
(s, i) => i !== removeInformationIndex
);
const reducedInformationPurchaseCost = informationState.purchase.cost.filter(
(s, i) => i !== removeInformationIndex
);
setInformationState({
purchase: {
service: [...reducedInformationPurchaseService],
cost: [...reducedInformationPurchaseCost]
},
subscription: {
service: [...reducedInformationSubscriptionService],
cost: [...reducedInformationSubscriptionCost]
}
});
}
if (
name === "information" &&
informationState.subscription.service[0] === undefined
) {
setInformationState({
purchase: {
service: [newService],
cost: [newPCost]
},
subscription: {
service: [newService],
cost: [newCost]
}
});
}
if (
name === "ecommerce" &&
ecommerceState.subscription.service[0] !== undefined &&
!Object.values(ecommerceState.subscription.service).includes(newService)
) {
const oldEcommercePurchaseService = ecommerceState.purchase.service;
const oldEcommercePurchaseCost = ecommerceState.purchase.cost;
const oldEcommerceSubscriptionService =
ecommerceState.subscription.service;
const oldEcommerceSubscriptionCost = ecommerceState.subscription.cost;
setEcommerceState({
purchase: {
service: [...oldEcommercePurchaseService, newService],
cost: [...oldEcommercePurchaseCost, newPCost]
},
subscription: {
service: [...oldEcommerceSubscriptionService, newService],
cost: [...oldEcommerceSubscriptionCost, newCost]
}
});
}
if (
name === "ecommerce" &&
ecommerceState.subscription.service[0] !== undefined &&
Object.values(ecommerceState.subscription.service).includes(newService)
) {
const removeEcommerceIndex = ecommerceState.subscription.service.indexOf(
newService
);
const reducedEcommerceSubscriptionService = ecommerceState.subscription.service.filter(
(s, i) => i !== removeEcommerceIndex
);
const reducedEcommerceSubscriptionCost = ecommerceState.subscription.cost.filter(
(s, i) => i !== removeEcommerceIndex
);
const reducedEcommercePurchaseService = ecommerceState.purchase.service.filter(
(s, i) => i !== removeEcommerceIndex
);
const reducedEcommercePurchaseCost = ecommerceState.purchase.cost.filter(
(s, i) => i !== removeEcommerceIndex
);
setEcommerceState({
purchase: {
service: [...reducedEcommercePurchaseService],
cost: [...reducedEcommercePurchaseCost]
},
subscription: {
service: [...reducedEcommerceSubscriptionService],
cost: [...reducedEcommerceSubscriptionCost]
}
});
}
if (
name === "ecommerce" &&
ecommerceState.subscription.service[0] === undefined
) {
setEcommerceState({
purchase: {
service: [newService],
cost: [newPCost]
},
subscription: {
service: [newService],
cost: [newCost]
}
});
}
};
const data = state.data;
const activeStatus = state.active;
const pricingCarouselOptions = {
type: "slider",
perView: 3,
gap: 30,
bound: true,
breakpoints: {
1199: {
perView: 2,
type: "carousel",
peek: {
before: 100,
after: 100
}
},
990: {
type: "carousel",
perView: 1,
peek: {
before: 160,
after: 160
}
},
767: {
type: "carousel",
perView: 1,
peek: {
before: 80,
after: 80
}
},
575: {
type: "carousel",
perView: 1,
gap: 15,
peek: {
before: 20,
after: 20
}
}
}
};
return (
<Box {...sectionWrapper} id="pricing_section">
<Container>
<Box {...secTitleWrapper}>
<Text {...secText} content="PRICING PLAN" />
<Heading
{...secHeading}
content="Customize your website according to your needs"
/>
<PricingButtonWrapper>
<Button
title="Monthly Subscription Pricing"
className={activeStatus ? "active-item" : ""}
onClick={() =>
setState({ data: MONTHLY_PRICING_TABLE, active: true })
}
/>
<Button
title="One-Time Purchase Pricing"
className={activeStatus === false ? "active-item" : ""}
onClick={() =>
setState({ data: YEARLY_PRICING_TABLE, active: false })
}
/>
<Link href="#">
<a>+ Custom Plan</a>
</Link>
</PricingButtonWrapper>
</Box>
<PricingTableWrapper>
<GlideCarousel
carouselSelector="pricing-carousel"
options={pricingCarouselOptions}
controls={false}
>
<>
{data.map((pricingTable, index) => (
<GlideSlide key={`pricing-table-${index}`}>
<PricingTable
freePlan={pricingTable.freePlan}
className="pricing_table"
>
<PricingHead>
<Heading content={pricingTable.name} {...nameStyle} />
<Text
content={pricingTable.description}
{...descriptionStyle}
/>
</PricingHead>
<PricingPrice>
{pricingTable.name === "Informational Website" &&
pricingTable.type === "subscription" ? (
<Text
content={`$${informationTotal.subscription}`}
{...priceStyle}
/>
) : pricingTable.name === "Informational Website" &&
pricingTable.type === "purchase" ? (
<Text
content={`$${informationTotal.purchase}`}
{...priceStyle}
/>
) : pricingTable.name === "Business Website" &&
pricingTable.type === "subscription" ? (
<Text
content={`$${businessTotal.subscription}`}
{...priceStyle}
/>
) : pricingTable.name === "Business Website" &&
pricingTable.type === "purchase" ? (
<Text
content={`$${businessTotal.purchase}`}
{...priceStyle}
/>
) : pricingTable.category === "ecommerce" &&
pricingTable.type === "subscription" ? (
<Text
content={`$${ecommerceTotal.subscription}`}
{...priceStyle}
/>
) : pricingTable.category === "ecommerce" &&
pricingTable.type === "purchase" ? (
<Text
content={`$${ecommerceTotal.purchase}`}
{...priceStyle}
/>
) : (
<Text content="error" {...priceStyle} />
)}
<Text
content={pricingTable.priceLabel}
{...priceLabelStyle}
/>
</PricingPrice>
<PricingList>
{pricingTable.listItems.map((item, index) => (
<ListItem key={`pricing-table-list-${index}`}>
<Checkbox
name={`${pricingTable.category}`}
id={`${pricingTable.type}`}
labelText={item.service}
checked={toggleValue}
value={item}
onChange={e => {
toggleHandler;
handleChecking(e, item);
}}
// onChange={e => {
// toggleHandler;
// handleChecking(e);
// }}
/>
{/* <Text content={item.content} {...listContentStyle} /> */}
{pricingTable.type === "subscription" ? (
<Text
content={`$${item.cost}`}
{...listContentStyle}
/>
) : (
<Text
content={`$${item.pcost}`}
{...listContentStyle}
/>
)}
</ListItem>
))}
</PricingList>
<PricingButton>
<Link href={pricingTable.url}>
<a>
<Button
title={pricingTable.buttonLabel}
{...buttonFillStyle}
/>
</a>
</Link>
{pricingTable.trialButtonLabel ? (
<Link href={pricingTable.trialURL || "#"}>
<a className="trial_button">
{pricingTable.trialButtonLabel}
</a>
</Link>
) : (
""
)}
</PricingButton>
</PricingTable>
</GlideSlide>
))}
</>
</GlideCarousel>
</PricingTableWrapper>
</Container>
</Box>
);
};
export default PricingSection;
An example of the data:
{
name: "Informational Website",
category: "information",
type: "subscription",
description:
" Beautiful landing page for small businesses or personal portfolios",
price: "$0",
priceLabel: "Per month",
buttonLabel: "Start for free",
url: "#",
listItems: [
{
service: ["Mobile-ready, Responsive Design"],
cost: [6],
pcost: [600]
},
{
service: ["Blog Articles"],
cost: [14],
pcost: [1300]
},
{
service: ["Collect visitor information (email / phone)"],
cost: [10],
pcost: [1000]
},
{
service: ["eCommerce Store "],
cost: [25],
pcost: [3200]
},
{
service: ["30+ Webmaster Tools"],
cost: [2],
pcost: [500]
}
]
},