diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Container/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Container/Examples.tsx
index aa1ebdefa82..efecf9a81f4 100644
--- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Container/Examples.tsx
+++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Container/Examples.tsx
@@ -213,7 +213,10 @@ export const AsyncWizardContainer = () => {
return (
-
+
diff --git a/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorContext.tsx b/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorContext.tsx
index 89cc7a7647b..f2fa1b5f380 100644
--- a/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorContext.tsx
+++ b/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorContext.tsx
@@ -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'
@@ -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(true)
const [hideSidebar, setHideSidebar] = useState(false)
- const [activeStep, setActiveStep] = useState(
- getActiveStepFromProps()
- )
const [openState, setOpenState] = useState(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
}
@@ -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(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,
@@ -215,7 +190,7 @@ export function StepIndicatorProvider({
{
hasSidebar,
hideSidebar,
- activeStep,
+ activeStep: activeStepRef.current,
openState,
listOfReachedSteps,
data,
@@ -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
diff --git a/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorItem.tsx b/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorItem.tsx
index e4feb039c18..abd7add0384 100644
--- a/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorItem.tsx
+++ b/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorItem.tsx
@@ -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 {
@@ -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(
- 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,
@@ -266,9 +255,7 @@ function StepIndicatorItem({
itemParams.className
)}
>
-
- {element}
-
+ {element}
{ariaLabel}
diff --git a/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorModal.tsx b/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorModal.tsx
index a603ab4eba7..5b5d4abc340 100644
--- a/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorModal.tsx
+++ b/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorModal.tsx
@@ -3,7 +3,13 @@
*
*/
-import React, { useContext, useEffect, useRef, useState } from 'react'
+import React, {
+ useCallback,
+ useContext,
+ useEffect,
+ useRef,
+ useState,
+} from 'react'
import ReactDOM from 'react-dom'
import Drawer from '../drawer/Drawer'
import StepIndicatorTriggerButton from './StepIndicatorTriggerButton'
@@ -23,22 +29,22 @@ function StepIndicatorModal() {
)
setContainer(container)
- }, [])
+ }, [context.sidebar_id])
- function closeHandler() {
+ const closeHandler = useCallback(() => {
if (context.hasSidebar) {
triggerRef.current?.focus()
}
context.closeHandler()
- }
+ }, [context])
- function renderPortal() {
+ const renderPortal = useCallback(() => {
if (!container) {
return null
}
return ReactDOM.createPortal(, container)
- }
+ }, [container])
if (context.sidebarIsVisible) {
return renderPortal()
diff --git a/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorSidebar.tsx b/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorSidebar.tsx
index d018754abfe..0293b0c698e 100644
--- a/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorSidebar.tsx
+++ b/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorSidebar.tsx
@@ -3,7 +3,14 @@
*
*/
-import React, { useContext, useEffect, useRef, useState } from 'react'
+import React, {
+ useCallback,
+ useContext,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from 'react'
import classnames from 'classnames'
import { extendPropsWithContext } from '../../shared/component-helper'
@@ -45,7 +52,9 @@ function StepIndicatorSidebar({
data = stepIndicatorDefaultProps.data,
...restOfProps
}: StepIndicatorSidebarProps) {
- const props = { current_step, data, ...restOfProps }
+ const props = useMemo(() => {
+ return { current_step, data, ...restOfProps }
+ }, [current_step, data, restOfProps])
const context = useContext(Context)
@@ -57,9 +66,9 @@ function StepIndicatorSidebar({
if (!props.showInitialData) {
setShowInitialData(false)
}
- }, [])
+ }, [props.showInitialData])
- function getContextAndProps() {
+ const getContextAndProps = useCallback(() => {
const providerProps = extendPropsWithContext(
props,
stepIndicatorDefaultProps,
@@ -76,7 +85,7 @@ function StepIndicatorSidebar({
}
return providerProps
- }
+ }, [context, props])
const providerProps = showInitialData ? getContextAndProps() : null
diff --git a/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorTriggerButton.tsx b/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorTriggerButton.tsx
index 5c80ac5880f..47c62ce3cba 100644
--- a/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorTriggerButton.tsx
+++ b/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorTriggerButton.tsx
@@ -39,7 +39,10 @@ function StepIndicatorTriggerButton(
const item = context.data[context.activeStep || 0]
const label = context.stepsLabel
- const { data, ...contextWithoutData } = context
+ const {
+ data, // eslint-disable-line
+ ...contextWithoutData
+ } = context
const triggerParams = {
...contextWithoutData,
diff --git a/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.test.tsx
index 783955bb4c7..1833599ab82 100644
--- a/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.test.tsx
+++ b/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.test.tsx
@@ -653,9 +653,9 @@ describe('Wizard.Container', () => {
expect(wizardList()).not.toBeInTheDocument()
rerender(
-
+
-
+
@@ -664,6 +664,7 @@ describe('Wizard.Container', () => {
)
+ expect(output()).toHaveTextContent('ensure re-render')
expect(stepTrigger()).toBeInTheDocument()
expect(wizardList()).not.toBeInTheDocument()
})