Skip to content

Commit

Permalink
fix(StepIndicator): avoid re-assigning functions that can cause title…
Browse files Browse the repository at this point in the history
… to be not in sync
  • Loading branch information
tujoworker committed Jun 12, 2024
1 parent f6b1224 commit a54d09a
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 161 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,10 @@ export const AsyncWizardContainer = () => {

return (
<Form.Handler onSubmit={onSubmit}>
<Wizard.Container onStepChange={onStepChange}>
<Wizard.Container
onStepChange={onStepChange}
variant="drawer"
>
<Step1 />
<Step2 />
</Wizard.Container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@
*
*/

import React, { useContext, useEffect, useRef, useState } from 'react'
import React, {
useCallback,
useContext,
useEffect,
useMemo,
useReducer,
useRef,
useState,
} from 'react'
import Context, { ContextProps } from '../../shared/Context'
import { stepIndicatorDefaultProps } from './StepIndicatorProps'
import { extendPropsWithContext } from '../../shared/component-helper'
Expand Down Expand Up @@ -95,90 +103,35 @@ export function StepIndicatorProvider({
isSidebar = false,
...restOfProps
}: StepIndicatorProviderProps) {
const props = { isSidebar, ...restOfProps }
const props = useMemo(() => {
return { isSidebar, ...restOfProps }
}, [isSidebar, restOfProps])

const data = getData(props)
const countSteps = data.length
const data = useMemo(() => {
if (typeof props.data === 'string') {
return props.data[0] === '[' ? JSON.parse(props.data) : []
}

return props.data || []
}, [props])

const [hasSidebar, setHasSidebar] = useState<boolean>(true)
const [hideSidebar, setHideSidebar] = useState<boolean>(false)
const [activeStep, setActiveStep] = useState<number>(
getActiveStepFromProps()
)
const [openState, setOpenState] = useState<boolean>(false)

const listOfReachedSteps = useRef([activeStep].filter(Boolean)).current

const mediaQueryListener = useRef(null)

const context = useContext(Context)
const contextValue = makeContextValue() as StepIndicatorContextValues

// Mount and dismount
useEffect(() => {
const container = document?.getElementById(
'sidebar__' + props.sidebar_id
)

setHasSidebar(Boolean(container))

mediaQueryListener.current = onMediaQueryChange(
{
min: '0',
max: 'medium',
},
(hideSidebar) => {
setHideSidebar(hideSidebar)
},
{ runOnInit: true }
)

return () => {
if (mediaQueryListener.current) {
mediaQueryListener.current()
}
}
}, [])

// Keeps the activeStep state updated with changes to the current_step and data props
useEffect(() => {
const currentStepFromProps = getActiveStepFromProps()

if (currentStepFromProps !== activeStep) {
setActiveStep(currentStepFromProps)
}
}, [props.current_step, data])

// Keeps the listOfReachedSteps state up to date with the activeStep state
useEffect(() => {
if (!listOfReachedSteps.includes(activeStep)) {
listOfReachedSteps.push(activeStep)
}
}, [activeStep])

function onChangeState() {
const onChangeState = useCallback(() => {
setOpenState(false)
}
}, [])

function openHandler() {
const openHandler = useCallback(() => {
setOpenState(true)
}
}, [])

function closeHandler() {
const closeHandler = useCallback(() => {
setOpenState(false)
}

function getData(
props: StepIndicatorProviderProps
): string[] | StepIndicatorDataItem[] {
if (typeof props.data === 'string') {
return props.data[0] === '[' ? JSON.parse(props.data) : []
}

return props.data || []
}
}, [])

function getActiveStepFromProps() {
const getActiveStepFromProps = useCallback(() => {
if (typeof data[0] === 'string') {
return props.current_step
}
Expand All @@ -192,9 +145,31 @@ export function StepIndicatorProvider({
return itemWithCurrentStep
? dataWithItems.indexOf(itemWithCurrentStep)
: props.current_step
}
}, [data, props.current_step])

const countSteps = data.length
const activeStepRef = useRef<number>(getActiveStepFromProps())
const [, forceUpdate] = useReducer(() => ({}), {})
const setActiveStep = useCallback((step: number) => {
activeStepRef.current = step
forceUpdate()
}, [])
const listOfReachedSteps = useRef(
[activeStepRef.current].filter(Boolean)
).current
const mediaQueryListener = useRef(null)
const context = useContext(Context)

const updateStepTitle = useCallback(
(title: string) => {
return title
?.replace('%step', String((activeStepRef.current || 0) + 1))
.replace('%count', String(data?.length || 1))
},
[data?.length]
)

function makeContextValue() {
const makeContextValue = useCallback(() => {
const globalContext = extendPropsWithContext(
props,
stepIndicatorDefaultProps,
Expand All @@ -215,7 +190,7 @@ export function StepIndicatorProvider({
{
hasSidebar,
hideSidebar,
activeStep,
activeStep: activeStepRef.current,
openState,
listOfReachedSteps,
data,
Expand All @@ -237,13 +212,66 @@ export function StepIndicatorProvider({
value.sidebarIsVisible = value.hasSidebar && !value.hideSidebar

return value
}
}, [
closeHandler,
context,
countSteps,
data,
hasSidebar,
hideSidebar,
listOfReachedSteps,
onChangeState,
openHandler,
openState,
props,
setActiveStep,
updateStepTitle,
])

function updateStepTitle(title: string) {
return title
?.replace('%step', String((activeStep || 0) + 1))
.replace('%count', String(data?.length || 1))
}
const contextValue = makeContextValue() as StepIndicatorContextValues

// Mount and dismount
useEffect(() => {
const container = document?.getElementById(
'sidebar__' + props.sidebar_id
)

setHasSidebar(Boolean(container))

mediaQueryListener.current = onMediaQueryChange(
{
min: '0',
max: 'medium',
},
(hideSidebar) => {
setHideSidebar(hideSidebar)
},
{ runOnInit: true }
)

return () => {
if (mediaQueryListener.current) {
mediaQueryListener.current()
}
}
}, [props.sidebar_id])

// Keeps the activeStep state updated with changes to the current_step and data props
useEffect(() => {
const currentStepFromProps = getActiveStepFromProps()

if (currentStepFromProps !== activeStepRef.current) {
setActiveStep(currentStepFromProps)
}
}, [props.current_step, data, getActiveStepFromProps, setActiveStep])

// Keeps the listOfReachedSteps state up to date with the activeStep state
const activeStep = activeStepRef.current
useEffect(() => {
if (!listOfReachedSteps.includes(activeStep)) {
listOfReachedSteps.push(activeStep)
}
}, [activeStep, listOfReachedSteps])

if (typeof window !== 'undefined' && window['IS_TEST']) {
contextValue['no_animation'] = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,7 @@
*
*/

import React, {
HTMLProps,
useContext,
useEffect,
useRef,
useState,
} from 'react'
import React, { HTMLProps, useCallback, useContext, useMemo } from 'react'

import classnames from 'classnames'
import {
Expand Down Expand Up @@ -84,67 +78,62 @@ function StepIndicatorItem({
disabled: disabled_default = false,
...restOfProps
}: StepIndicatorItemProps) {
const props: StepIndicatorItemProps = {
status_state: status_state_default,
inactive: inactive_default,
disabled: disabled_default,
...restOfProps,
}
const props: StepIndicatorItemProps = useMemo(() => {
return {
status_state: status_state_default,
inactive: inactive_default,
disabled: disabled_default,
...restOfProps,
}
}, [
disabled_default,
inactive_default,
restOfProps,
status_state_default,
])

const context = useContext(StepIndicatorContext)

const [previousStep, setPreviousStep] = useState<number>(
context.activeStep
)

const ref = useRef(null)

const thisReference = {
context,
props,
onClickHandler,
}

// Effect used to keep track of previous activeStep from context
useEffect(() => {
if (previousStep !== context.activeStep) {
setPreviousStep(context.activeStep)
}
}, [context.activeStep, previousStep])

function onClickHandler({ event, item, currentItemNum }) {
const params = {
event,
item,
current_step: currentItemNum,
currentStep: currentItemNum,
}

const onClickItem = dispatchCustomElementEvent(
thisReference,
'on_click',
params
)
const onClickHandler = useCallback(
({ event, item, currentItemNum }) => {
const params = {
event,
item,
current_step: currentItemNum,
currentStep: currentItemNum,
}

const onClickGlobal = dispatchCustomElementEvent(
context,
'on_click',
params
)
const onClickItem = dispatchCustomElementEvent(
{
context,
props,
onClickHandler,
},
'on_click',
params
)

const onClickGlobal = dispatchCustomElementEvent(
context,
'on_click',
params
)

if (onClickItem === false || onClickGlobal === false) {
return // stop here
}

if (onClickItem === false || onClickGlobal === false) {
return // stop here
}
if (context.activeStep !== currentItemNum) {
context.setActiveStep(currentItemNum)
if (typeof context.onChangeState === 'function') {
context.onChangeState()
}

if (context.activeStep !== currentItemNum) {
context.setActiveStep(currentItemNum)
if (typeof context.onChangeState === 'function') {
context.onChangeState()
dispatchCustomElementEvent(context, 'on_change', params)
}

dispatchCustomElementEvent(context, 'on_change', params)
}
}
},
[context, props]
)

const {
mode,
Expand Down Expand Up @@ -266,9 +255,7 @@ function StepIndicatorItem({
itemParams.className
)}
>
<StepItemButton {...buttonParams} inner_ref={ref}>
{element}
</StepItemButton>
<StepItemButton {...buttonParams}>{element}</StepItemButton>
<span id={id} aria-hidden className="dnb-sr-only">
{ariaLabel}
</span>
Expand Down
Loading

0 comments on commit a54d09a

Please sign in to comment.