1

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]
      }
    ]
  },
realhat
  • 177
  • 1
  • 1
  • 9
  • 3
    There is a lot of improvement you can make, but something that jumps out at me is that `useEffect` second argument is supposed to be an array. try changing `businessState` to `[businessState]` and so forth – azium Feb 24 '20 at 18:54
  • also using `index` in your keys might be causing a rendering issue. does the data not contain unique information? – azium Feb 24 '20 at 18:56
  • @azium Haha fair enough... I did have it as `[businessState]` originally, but useEffect does not get called if I do. Any idea why that might be? – realhat Feb 24 '20 at 19:00
  • Probably the order of operations? Your effects are defined above where the variables are created. move all your `useState` calls to the top, above your effects – azium Feb 24 '20 at 19:23
  • @azium Ok that did it, they are now working with [businessState], [informationState], etc. THANK YOU! The issue now is when I switch to the purchase tab, things get messy. Namely, if I select an item on the subscription tab, then switch to the purchase tab and delect it, instead of filtering it out and removing the service and cost, it adds it. So, must be an issue with my logic, but I can't see it :( – realhat Feb 24 '20 at 19:30

0 Answers0