diff --git a/lib/interviewer/components/Navigation.tsx b/lib/interviewer/components/Navigation.tsx index bbe3e329a..0893f8b94 100644 --- a/lib/interviewer/components/Navigation.tsx +++ b/lib/interviewer/components/Navigation.tsx @@ -1,140 +1,9 @@ -import React, { useCallback, useEffect } from 'react'; +import { useEffect } from 'react'; import ProgressBar from '~/lib/ui/components/ProgressBar'; import { ChevronDown, ChevronUp, SettingsIcon } from 'lucide-react'; import { cn } from '~/utils/shadcn'; -import { useDispatch, useSelector } from 'react-redux'; -import { getNavigationInfo } from '../selectors/session'; -import { getSkipMap } from '../selectors/skip-logic'; import { parseAsInteger, useQueryState } from 'next-usequerystate'; -import { actionCreators as sessionActions } from '../ducks/modules/session'; -import useReadyForNextStage from '../hooks/useReadyForNextStage'; -import usePrevious from '~/hooks/usePrevious'; - -export const useNavigationHelpers = ( - currentStage: number, - setCurrentStage: (stage: number) => void, -) => { - const dispatch = useDispatch(); - const skipMap = useSelector(getSkipMap); - - const { isReady: isReadyForNextStage } = useReadyForNextStage(); - - const { - progress, - currentStep, - isLastPrompt, - isFirstPrompt, - isLastStage, - promptIndex, - canMoveBackward, - canMoveForward, - } = useSelector(getNavigationInfo); - - // const prevStageIndex = usePrevious(currentStep); - - const calculateNextStage = useCallback(() => { - const nextStage = Object.keys(skipMap).find( - (stage) => - parseInt(stage) > currentStage && skipMap[parseInt(stage)] === false, - ); - - if (!nextStage) { - return currentStage; - } - - return parseInt(nextStage); - }, [currentStage, skipMap]); - - const calculatePreviousStage = useCallback(() => { - const previousStage = Object.keys(skipMap) - .reverse() - .find((stage) => parseInt(stage) < currentStage); - - if (!previousStage) { - return currentStage; - } - - return parseInt(previousStage); - }, [currentStage, skipMap]); - - const validateCurrentStage = useCallback(() => { - if (!skipMap[currentStage] === false) { - const previousValidStage = calculatePreviousStage(); - - if (previousValidStage) { - setCurrentStage(previousValidStage); - } - } - }, [calculatePreviousStage, setCurrentStage, currentStage, skipMap]); - - const moveForward = useCallback(() => { - if (isLastPrompt) { - const nextStage = calculateNextStage(); - setCurrentStage(nextStage); - return; - } - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - dispatch(sessionActions.updatePrompt(promptIndex + 1)); - }, [ - dispatch, - isLastPrompt, - promptIndex, - calculateNextStage, - setCurrentStage, - ]); - - // Move to the previous available stage in the interview based on the current stage and skip logic - const moveBackward = () => { - if (isFirstPrompt) { - const previousStage = calculatePreviousStage(); - setCurrentStage(previousStage); - return; - } - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - dispatch(sessionActions.updatePrompt(promptIndex - 1)); - }; - - const prevCurrentStage = usePrevious(currentStage); - - const needToDispatch = useCallback(() => { - if (currentStage === prevCurrentStage) { - return false; - } - - if (currentStage === currentStep) { - return false; - } - - return true; - }, [currentStage, prevCurrentStage, currentStep]); - - useEffect(() => { - if (!needToDispatch()) { - return; - } - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - dispatch(sessionActions.updateStage(currentStage)); - }, [currentStage, dispatch, needToDispatch]); - - return { - progress, - isReadyForNextStage, - canMoveForward, - canMoveBackward, - moveForward, - moveBackward, - validateCurrentStage, - isFirstPrompt, - isLastPrompt, - isLastStage, - }; -}; +import { useNavigationHelpers } from '../hooks/useNavigationHelpers'; const NavigationButton = ({ disabled, diff --git a/lib/interviewer/containers/Interfaces/EgoForm.js b/lib/interviewer/containers/Interfaces/EgoForm.js index 70b8a6937..c5197086e 100644 --- a/lib/interviewer/containers/Interfaces/EgoForm.js +++ b/lib/interviewer/containers/Interfaces/EgoForm.js @@ -234,14 +234,16 @@ EgoForm.propTypes = { form: PropTypes.object.isRequired, introductionPanel: PropTypes.object.isRequired, ego: PropTypes.object, - currentStep: PropTypes.number.isRequired, + stage: PropTypes.shape({ + id: PropTypes.string.isRequired, + }).isRequired, submitForm: PropTypes.func.isRequired, updateEgo: PropTypes.func.isRequired, }; function mapStateToProps(state, props) { const ego = getNetworkEgo(state); - const formName = getFormName(props.currentStep); + const formName = getFormName(props.stage.id); const isFormValid = isValid(formName)(state); const isFormDirty = () => isDirty(formName)(state); const { isFirstStage } = getSessionProgress(state); diff --git a/lib/interviewer/containers/ProtocolScreen.js b/lib/interviewer/containers/ProtocolScreen.js index 026b2b045..677cbf318 100644 --- a/lib/interviewer/containers/ProtocolScreen.js +++ b/lib/interviewer/containers/ProtocolScreen.js @@ -1,6 +1,6 @@ import { AnimatePresence, motion } from 'framer-motion'; import { parseAsInteger, useQueryState } from 'next-usequerystate'; -import { useState } from 'react'; +import { useRef, useState } from 'react'; import { useSelector } from 'react-redux'; import Navigation, { useNavigationHelpers } from '../components/Navigation'; import { getCurrentStage } from '../selectors/session'; @@ -8,7 +8,7 @@ import Stage from './Stage'; import { get } from '../utils/lodash-replacements'; const ProtocolScreen = () => { - let beforeNextFunctions = {}; + const beforeNextFunctions = useRef({}); const currentStage = useSelector(getCurrentStage); const [pendingDirection, setPendingDirection] = useState(1); const [currentStageNumber, setCurrentStageNumber] = useQueryState( @@ -22,6 +22,7 @@ const ProtocolScreen = () => { ); const onComplete = (directionOverride) => { + console.log('onComplete', directionOverride); const nextDirection = directionOverride || pendingDirection; const beforeNext = get(beforeNextFunctions, currentStage?.id); @@ -38,11 +39,19 @@ const ProtocolScreen = () => { setPendingDirection(1); }; - const registerBeforeNext = (beforeNext, stageId) => { - if (beforeNext === null) return; - beforeNextFunctions = { ...beforeNextFunctions, [stageId]: beforeNext }; + const registerBeforeNext = (beforeNext) => { + console.log('registerbeforenext', currentStage.id); + + if (beforeNext === null || !currentStage.id) return; + + // Ignore if function already exists + if (beforeNextFunctions.current[currentStage.id]) return; + + beforeNextFunctions.current = { ...beforeNextFunctions.current, [currentStage.id]: beforeNext }; }; + console.log('beforeNextFunctions', beforeNextFunctions.current); + return ( { - const { stage, registerBeforeNext: register } = props; - - const registerBeforeNext = useCallback((beforeNext) => register( - beforeNext, - stage.id, - ), [register, stage.id]); - - useLayoutEffect(() => { - return () => { - register(null, stage.id) - } - }, [register, stage.id]) - + const { stage, registerBeforeNext } = props; const CurrentInterface = getInterface(stage.type); return ( diff --git a/lib/interviewer/hooks/useNavigationHelpers.ts b/lib/interviewer/hooks/useNavigationHelpers.ts new file mode 100644 index 000000000..0c4eec8a2 --- /dev/null +++ b/lib/interviewer/hooks/useNavigationHelpers.ts @@ -0,0 +1,133 @@ +import { useCallback, useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { getNavigationInfo } from '../selectors/session'; +import { getSkipMap } from '../selectors/skip-logic'; +import { actionCreators as sessionActions } from '../ducks/modules/session'; +import useReadyForNextStage from '../hooks/useReadyForNextStage'; +import usePrevious from '~/hooks/usePrevious'; + +export const useNavigationHelpers = ( + currentStage: number, + setCurrentStage: (stage: number) => void, +) => { + const dispatch = useDispatch(); + const skipMap = useSelector(getSkipMap); + + const { isReady: isReadyForNextStage } = useReadyForNextStage(); + + const { + progress, + currentStep, + isLastPrompt, + isFirstPrompt, + isLastStage, + promptIndex, + canMoveBackward, + canMoveForward, + } = useSelector(getNavigationInfo); + + // const prevStageIndex = usePrevious(currentStep); + + const calculateNextStage = useCallback(() => { + const nextStage = Object.keys(skipMap).find( + (stage) => + parseInt(stage) > currentStage && skipMap[parseInt(stage)] === false, + ); + + if (!nextStage) { + return currentStage; + } + + return parseInt(nextStage); + }, [currentStage, skipMap]); + + const calculatePreviousStage = useCallback(() => { + const previousStage = Object.keys(skipMap) + .reverse() + .find((stage) => parseInt(stage) < currentStage); + + if (!previousStage) { + return currentStage; + } + + return parseInt(previousStage); + }, [currentStage, skipMap]); + + const validateCurrentStage = useCallback(() => { + if (!skipMap[currentStage] === false) { + const previousValidStage = calculatePreviousStage(); + + if (previousValidStage) { + setCurrentStage(previousValidStage); + } + } + }, [calculatePreviousStage, setCurrentStage, currentStage, skipMap]); + + const moveForward = useCallback(() => { + if (isLastPrompt) { + const nextStage = calculateNextStage(); + setCurrentStage(nextStage); + return; + } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + dispatch(sessionActions.updatePrompt(promptIndex + 1)); + }, [ + dispatch, + isLastPrompt, + promptIndex, + calculateNextStage, + setCurrentStage, + ]); + + // Move to the previous available stage in the interview based on the current stage and skip logic + const moveBackward = () => { + if (isFirstPrompt) { + const previousStage = calculatePreviousStage(); + setCurrentStage(previousStage); + return; + } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + dispatch(sessionActions.updatePrompt(promptIndex - 1)); + }; + + const prevCurrentStage = usePrevious(currentStage); + + const needToDispatch = useCallback(() => { + if (currentStage === prevCurrentStage) { + return false; + } + + if (currentStage === currentStep) { + return false; + } + + return true; + }, [currentStage, prevCurrentStage, currentStep]); + + useEffect(() => { + if (!needToDispatch()) { + return; + } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + dispatch(sessionActions.updateStage(currentStage)); + }, [currentStage, dispatch, needToDispatch]); + + return { + progress, + isReadyForNextStage, + canMoveForward, + canMoveBackward, + moveForward, + moveBackward, + validateCurrentStage, + isFirstPrompt, + isLastPrompt, + isLastStage, + }; +};