Skip to content

Commit 23b7951

Browse files
committed
temp useWizardControlledValue
1 parent abe76d5 commit 23b7951

File tree

3 files changed

+105
-9
lines changed

3 files changed

+105
-9
lines changed

packages/wizard/src/Wizard/Wizard.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
1-
import React, { Children, cloneElement, isValidElement, useState } from 'react';
1+
import React, { Children, cloneElement, isValidElement } from 'react';
22

3+
import { useWizardControlledValue } from '../utils/useWizardControlledValue/useWizardControlledValue';
34
import { WizardFooter } from '../WizardFooter';
45
import { WizardStep } from '../WizardStep';
56

67
import { stepContentStyles, wizardContainerStyles } from './Wizard.styles';
78
import { WizardProps } from './Wizard.types';
89

910
export function Wizard({
10-
activeStep: controlledActiveStep,
11+
activeStep: activeStepProp,
1112
onStepChange,
1213
children,
1314
}: WizardProps) {
14-
// TODO: replace with `useControlledValue`
15-
// Internal state for uncontrolled mode
16-
const [internalActiveStep, setInternalActiveStep] = useState<number>(0);
17-
18-
// Use controlled prop if provided, otherwise use internal state
19-
const isControlled = controlledActiveStep !== undefined;
20-
const activeStep = isControlled ? controlledActiveStep : internalActiveStep;
15+
// Controlled/Uncontrolled activeStep value
16+
const {
17+
isControlled,
18+
value: activeStep,
19+
setValue: setInternalActiveStep,
20+
} = useWizardControlledValue<number>(activeStepProp, undefined, 0);
2121

2222
// Handle step changes
2323
const handleStepChange = (newStep: number) => {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { useControlledValue } from './useControlledValue';
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { useEffect, useMemo, useState } from 'react';
2+
import isUndefined from 'lodash/isUndefined';
3+
4+
import { usePrevious } from '@leafygreen-ui/hooks';
5+
import { consoleOnce } from '@leafygreen-ui/lib';
6+
7+
interface ControlledValueReturnObject<T extends any> {
8+
/** Whether the value is controlled */
9+
isControlled: boolean;
10+
11+
/** The controlled or uncontrolled value */
12+
value: T;
13+
14+
/**
15+
* Either updates the uncontrolled value,
16+
* or calls the provided `onChange` callback
17+
*/
18+
setValue: (newVal?: T, ...args: Array<any>) => void;
19+
}
20+
21+
/**
22+
* A hook that enables a component to be both controlled or uncontrolled.
23+
*
24+
* Returns a {@link ControlledValueReturnObject}
25+
* @deprecated Use `useControlled` from `@leafygreen-ui/hooks` instead
26+
* https://github.com/mongodb/leafygreen-ui/pull/3153
27+
*/
28+
export const useWizardControlledValue = <T extends any>(
29+
valueProp?: T,
30+
onChange?: (val?: T, ...args: Array<any>) => void,
31+
initialProp?: T,
32+
): ControlledValueReturnObject<T> => {
33+
// Initially set isControlled to the existence of `valueProp`.
34+
// If the value prop changes from undefined to something defined,
35+
// then isControlled is set to true,
36+
// and will remain true for the life of the component
37+
const [isControlled, setControlled] = useState(!isUndefined(valueProp));
38+
useEffect(() => {
39+
setControlled(isControlled || !isUndefined(valueProp));
40+
}, [isControlled, valueProp]);
41+
42+
const wasControlled = usePrevious(isControlled);
43+
44+
useEffect(() => {
45+
if (isUndefined(isControlled) || isUndefined(wasControlled)) return;
46+
47+
if (isControlled !== wasControlled) {
48+
const err = `WARN: A component changed from ${
49+
wasControlled ? 'controlled' : 'uncontrolled'
50+
} to ${
51+
isControlled ? 'controlled' : 'uncontrolled'
52+
}. This can cause issues with React states. ${
53+
isControlled
54+
? 'To control a component, but have an initially empty input, consider setting the `value` prop to `null`.'
55+
: ''
56+
}`;
57+
58+
consoleOnce.warn(err);
59+
}
60+
}, [isControlled, wasControlled]);
61+
62+
// We set the initial value to either the `value`
63+
// or the temporary `initialValue` prop
64+
const initialValue = useMemo(
65+
() => (isControlled ? valueProp : initialProp),
66+
[initialProp, isControlled, valueProp],
67+
);
68+
69+
// Keep track of the internal value state
70+
const [uncontrolledValue, setUncontrolledValue] = useState<T | undefined>(
71+
initialValue,
72+
);
73+
74+
// The returned value is wither the provided value prop
75+
// or the uncontrolled value
76+
const value = useMemo(
77+
() => (isControlled ? (valueProp as T) : (uncontrolledValue as T)),
78+
[isControlled, uncontrolledValue, valueProp],
79+
);
80+
81+
// A wrapper around `handleChange` that fires a simulated event
82+
const setValue = (newVal: T | undefined) => {
83+
if (!isControlled) {
84+
setUncontrolledValue(newVal);
85+
}
86+
87+
onChange?.(newVal);
88+
};
89+
90+
return {
91+
isControlled,
92+
value,
93+
setValue,
94+
};
95+
};

0 commit comments

Comments
 (0)