Skip to content

Commit b91697e

Browse files
authored
Fix handling of controlled state in toggle-able controls (#3834)
* Split up checkbox tests into their own files * Change files * Add new Checkbox test * Fix controlled toggling * Remove console.log * Change files * isChecked should always follow internal state
1 parent 7bec589 commit b91697e

File tree

4 files changed

+58
-20
lines changed

4 files changed

+58
-20
lines changed
Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import React from 'react';
2-
import { Platform, View } from 'react-native';
2+
import { Platform, Pressable, View, type ViewStyle } from 'react-native';
33

44
import { ButtonV1 as Button } from '@fluentui-react-native/button';
55
import { Checkbox } from '@fluentui-react-native/experimental-checkbox';
6+
import { TextV1 } from '@fluentui-react-native/text';
67

78
import { mobileStyles } from '../Common/styles';
89

10+
const pressableStyle: ViewStyle = { borderWidth: 1, padding: 4, gap: 4, flexDirection: 'row', alignItems: 'center' };
11+
912
export const OtherCheckbox: React.FunctionComponent = () => {
1013
const [isChecked, setisChecked] = React.useState(false);
1114

@@ -17,24 +20,44 @@ export const OtherCheckbox: React.FunctionComponent = () => {
1720
setisChecked(false);
1821
}, []);
1922

20-
const memoizedStyles = React.useMemo(() => (Platform.OS === 'android' ? { ...mobileStyles.containerSpacedEvenly, height: 150 } : {}), []);
23+
const onChange = React.useCallback((_e, checked: boolean) => {
24+
setisChecked(checked);
25+
}, []);
26+
27+
const onPress = React.useCallback(() => {
28+
setisChecked(!isChecked);
29+
}, [isChecked]);
30+
31+
const memoizedStyles = React.useMemo(
32+
() => (Platform.OS === 'android' ? { ...mobileStyles.containerSpacedEvenly, height: 150 } : { gap: 8 }),
33+
[],
34+
);
2135

2236
return (
2337
<View style={memoizedStyles}>
24-
<Button onClick={setCheckedTrue} size="small">
25-
Check controlled checkboxes below
26-
</Button>
27-
<Button onClick={setCheckedFalse} size="small">
28-
Uncheck controlled checkboxes below
29-
</Button>
30-
31-
<Checkbox label="This is a controlled Checkbox" checked={isChecked} />
32-
{Platform.OS !== 'android' && (
33-
<>
34-
<Checkbox label="Checkbox rendered with labelPosition 'before' (controlled)" labelPosition="before" checked={isChecked} />
35-
<Checkbox label="A required checkbox with other required text" required="**" />
36-
</>
37-
)}
38+
<View>
39+
<Button onClick={setCheckedTrue} size="small">
40+
Check controlled checkboxes below
41+
</Button>
42+
<Button onClick={setCheckedFalse} size="small">
43+
Uncheck controlled checkboxes below
44+
</Button>
45+
</View>
46+
47+
<View>
48+
<Checkbox label="This is a controlled Checkbox" checked={isChecked} onChange={onChange} />
49+
<Pressable style={pressableStyle} onPress={onPress}>
50+
<Checkbox checked={isChecked} onChange={onChange} />
51+
<TextV1>A controlled checkbox in a Pressable. Pressing Pressable also toggles Checkbox</TextV1>
52+
</Pressable>
53+
54+
{Platform.OS !== 'android' && (
55+
<>
56+
<Checkbox label="Checkbox rendered with labelPosition 'before' (controlled)" labelPosition="before" checked={isChecked} />
57+
<Checkbox label="A required checkbox with other required text" required="**" />
58+
</>
59+
)}
60+
</View>
3861
</View>
3962
);
4063
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "Fix controlled toggling",
4+
"packageName": "@fluentui-react-native/interactive-hooks",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "Add new Checkbox test",
4+
"packageName": "@fluentui-react-native/tester",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

packages/utils/interactive-hooks/src/useAsToggleWithEvent.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as React from 'react';
22

3-
import type { InteractionEvent } from '.';
3+
import type { InteractionEvent } from './events.types';
4+
import { useControllableValue } from './useControllableValue';
45

56
export type OnToggleWithEventCallback = (e: InteractionEvent, value?: boolean) => void;
67
export type OnChangeWithEventCallback = (e: InteractionEvent) => void;
@@ -21,15 +22,15 @@ export function useAsToggleWithEvent(
2122
checked?: boolean,
2223
userCallback?: OnToggleWithEventCallback,
2324
): [boolean, OnChangeWithEventCallback] {
24-
const [isChecked, setChecked] = React.useState(defaultChecked ?? checked);
25+
const [isChecked, setChecked] = useControllableValue(checked, defaultChecked);
2526

2627
const onChange = React.useCallback(
2728
(e: any) => {
2829
userCallback && userCallback(e, !isChecked);
2930
setChecked(!isChecked);
3031
},
31-
[isChecked, setChecked],
32+
[isChecked, setChecked, userCallback],
3233
);
3334

34-
return [checked ?? isChecked, onChange];
35+
return [isChecked, onChange];
3536
}

0 commit comments

Comments
 (0)