简体   繁体   中英

Problem with React Joyride on production Cannot read properties of undefined (reading '0')

We decided to implement a simple onboarding tour on our application, everything was going well on local but once we deploy it to production its crashing showing the next error on devtools:

 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading '0')

it's kind of hard to debug this and see what is the problem because it's caused on production and its like debugging with the eyes closed.

the bug/error that the console show it's and undefined and by logic will crash too on local, but this it's not the case

The error is caused after we pass the first step like you can see on this video(also you can se how it's working locally: VIDEO WITH THE ISSUE

They way the joyride its implemented consists on two big keys the first one its the joyride wrapper :

import { useEffect, useState } from 'react';
import Joyride, { CallBackProps, STATUS, Step } from 'react-joyride';
import { Tooltip } from './components/tool-tip';
import { OnboardingTourJoyrideProps, ValidSteps } from '../../types/onboarding-tour';
import { useGuidedTour } from '../../hooks/use-guided-tour';

export function OnboardingTourJoyride({ start, stepToCheck }: OnboardingTourJoyrideProps): JSX.Element {
  const { dashBoardSteps, terminalAppSteps, cloudAppSteps } = useGuidedTour();

  const [steps, setSteps] = useState<Step[]>(dashBoardSteps);
  const [run, setRun] = useState<boolean>(false);
  const [resetTour, setResetTour] = useState<boolean>(false);
  const [stepToInitRestart, setStepToInitRestart] = useState<number>(0);

  function handleSteps() {
    switch (stepToCheck) {
      case ValidSteps.DASHBOARD_STEPS:
        setSteps(dashBoardSteps);
        setStepToInitRestart(dashBoardSteps.length - 1);
        break;
      case ValidSteps.TERMINAL_APP_STEPS:
        setSteps(terminalAppSteps);
        setStepToInitRestart(terminalAppSteps.length - 1);
        break;
      case ValidSteps.CLOUD_APP_STEPS:
        setSteps(cloudAppSteps);
        setStepToInitRestart(cloudAppSteps.length - 1);
    }
  }

  useEffect(() => {
    const onboardinIsCompletedOrSkipped = handleCheckIfTourIsComplete();
    if (!onboardinIsCompletedOrSkipped) {
      handleSteps();
      if (start) setRun(true);
    }
  }, [start]);

  useEffect(() => {
    if (resetTour) {
      setRun(true);
      setResetTour(false);
    }
  }, [resetTour]);

  function handlePropertyToSaveOnLocalStorage() {
    switch (stepToCheck) {
      case ValidSteps.DASHBOARD_STEPS:
        localStorage.setItem('ONBOARDING_TOUR_DASHBOARD', 'true');
        break;
      case ValidSteps.TERMINAL_APP_STEPS:
        localStorage.setItem('ONBOARDING_TOUR_TERMINAL_APP', 'true');
        break;
      case ValidSteps.CLOUD_APP_STEPS:
        localStorage.setItem('ONBOARDING_TOUR_CLOUD_APP', 'true');
        break;
    }
  }

  function handleCheckIfTourIsComplete() {
    if (stepToCheck === ValidSteps.DASHBOARD_STEPS) {
      return localStorage.getItem('ONBOARDING_TOUR_DASHBOARD');
    } else if (stepToCheck === ValidSteps.TERMINAL_APP_STEPS) {
      return localStorage.getItem('ONBOARDING_TOUR_TERMINAL_APP');
    } else if (stepToCheck === ValidSteps.CLOUD_APP_STEPS) {
      return localStorage.getItem('ONBOARDING_TOUR_CLOUD_APP');
    }
  }

  function handleJoyrideCallback(data: CallBackProps) {
    const { status, action, index } = data;
    console.log(data);
    const finishedStatuses: string[] = [STATUS.FINISHED, STATUS.SKIPPED];

    if (index === stepToInitRestart && status === STATUS.SKIPPED) {
      setRun(false);
      setResetTour(true);
    }

    if (finishedStatuses.includes(status)) {
      if (action === 'skip') {
        setRun(false);
        handlePropertyToSaveOnLocalStorage();
      }
      setRun(false);
      handlePropertyToSaveOnLocalStorage();
    }
  }

  return (
    <Joyride
      tooltipComponent={Tooltip}
      disableScrolling={true}
      callback={handleJoyrideCallback}
      continuous
      hideCloseButton
      run={run}
      scrollToFirstStep
      showProgress={false}
      showSkipButton={true}
      steps={steps}
      styles={{
        options: {
          zIndex: 10000,
        },
      }}
    />
  );
}

which its a simple wrapper that contains all the logic to rehuse the same component across the app

and the other one it's the custom tooltip component:

import { Box, CloseButton, Flex, Text } from '@chakra-ui/react';
import { TooltipRenderProps } from 'react-joyride';
import { Button } from '../../button';
import { useIntl } from 'react-intl';

export function Tooltip({
  backProps,
  continuous,
  index,
  isLastStep,
  primaryProps,
  skipProps,
  step,
  tooltipProps,
  size,
}: TooltipRenderProps): JSX.Element {
  const intl = useIntl();

  const steps = {
    currentStep: index,
    stepsLength: size - 2,
  };

  function handleResetOrBackStep(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) {
    if (isLastStep) {
      skipProps.onClick(e);
    } else {
      backProps.onClick(e);
    }
  }

  function handleCloseButton(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) {
    if (isLastStep) {
      primaryProps.onClick(e);
    } else {
      skipProps.onClick(e);
    }
  }

  function handleFowardButton() {
    return continuous
      ? index !== 0 && !isLastStep
        ? intl.formatMessage({ id: 'buttons.next' })
        : isLastStep
        ? intl.formatMessage({ id: 'buttons.finish' })
        : intl.formatMessage({ id: 'buttons.startTour' })
      : null;
  }

  function handleShowStepsCounter() {
    return index !== 0 && !isLastStep ? (
      <Text fontSize="16px">
        {intl.formatMessage(
          { id: 'onboardingTour.toolTip.stepsCounter' },
          {
            currentStep: steps.currentStep,
            totalSteps: steps.stepsLength,
          },
        )}
      </Text>
    ) : (
      !isLastStep && (
        <Button variant="outline" {...skipProps}>
          {intl.formatMessage({ id: 'buttons.skipTour' })}
        </Button>
      )
    );
  }

  function handleBackwardButton() {
    return isLastStep
      ? intl.formatMessage({ id: 'buttons.restartTour' })
      : intl.formatMessage({ id: 'buttons.previous' });
  }

  return (
    <Box {...tooltipProps} background="white" p="48px" w="100%" borderRadius="6px">
      <Box padding="md">
        <Flex justifyContent="space-between" align="baseline" marginBottom="15px">
          {step.title && (
            <Text fontSize="20px" fontWeight="700">
              {step.title}
            </Text>
          )}
          <CloseButton onClick={e => handleCloseButton(e)} />
        </Flex>
        {step.content && (
          <Box>
            <Text w="504px" fontSize="16px" fontWeight="400" lineHeight="26px" marginBottom="48px">
              {step.content}
            </Text>
          </Box>
        )}
      </Box>
      <Box>
        <Flex justifyContent="space-between" align="center">
          {handleShowStepsCounter()}
          <Flex w={isLastStep ? '100%' : ''} justifyContent="space-between">
            {index !== 0 && (
              <Button marginRight="12px" variant="outline" onClick={e => handleResetOrBackStep(e)}>
                {handleBackwardButton()}
              </Button>
            )}
            <Button h="35px" {...primaryProps}>
              {handleFowardButton()}
            </Button>
          </Flex>
        </Flex>
      </Box>
    </Box>
  );
}

I don't know if anyone have experience with a similar issue, I google it and search all across SO and the issues on the library repository. Also it's weird because on the pass I worked with react-joyride and deployed to production and there is no any problem. To put you all in context, we are using next with a framework called Gasket .

Had the same issue. Here is the solution - https://github.com/gilbarbara/react-joyride/issues/857 (also updating nextjs to 13.1.1 fixes the problem)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM