Skip to content

Commit e28b3e2

Browse files
committed
fix: rtl mirror styles cause unnecessary rerenders
1 parent 7f1bec6 commit e28b3e2

8 files changed

Lines changed: 60 additions & 57 deletions

File tree

examples/SampleApp/src/components/MenuDrawer.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';
1919
import { Group } from '../icons/Group.tsx';
2020
import { User } from '../icons/User';
2121
import { useLegacyColors } from '../theme/useLegacyColors';
22-
import { getRtlMirrorSwitchStyle } from '../utils/rtlMirrorSwitchStyle';
22+
import { useRtlMirrorSwitchStyle } from '../utils/rtlMirrorSwitchStyle';
2323

2424
export const styles = StyleSheet.create({
2525
avatar: {
@@ -101,6 +101,7 @@ export const MenuDrawer = ({ navigation }: DrawerContentComponentProps) => {
101101
}, []);
102102

103103
const { chatClient, logout, rtlEnabled, setRTLEnabled } = useAppContext();
104+
const rtlMirrorSwitchStyle = useRtlMirrorSwitchStyle();
104105

105106
if (!chatClient) {
106107
return null;
@@ -221,7 +222,7 @@ export const MenuDrawer = ({ navigation }: DrawerContentComponentProps) => {
221222
</View>
222223
<Switch
223224
onValueChange={setRTLEnabled}
224-
style={getRtlMirrorSwitchStyle()}
225+
style={rtlMirrorSwitchStyle}
225226
thumbColor={white}
226227
trackColor={{
227228
false: grey,

examples/SampleApp/src/screens/GroupChannelDetailsScreen.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import { LeaveGroup } from '../icons/LeaveGroup';
3434
import { Mute } from '../icons/Mute';
3535
import { Picture } from '../icons/Picture';
3636
import type { StackNavigatorParamList } from '../types';
37-
import { getRtlMirrorSwitchStyle } from '../utils/rtlMirrorSwitchStyle';
37+
import { useRtlMirrorSwitchStyle } from '../utils/rtlMirrorSwitchStyle';
3838

3939
const MAX_VISIBLE_MEMBERS = 5;
4040

@@ -60,6 +60,7 @@ export const GroupChannelDetailsScreen: React.FC<GroupChannelDetailsProps> = ({
6060
const {
6161
theme: { semantics },
6262
} = useTheme();
63+
const rtlMirrorSwitchStyle = useRtlMirrorSwitchStyle();
6364
const { muted: channelMuted } = useIsChannelMuted(channel);
6465

6566
const [muted, setMuted] = useState(
@@ -279,7 +280,7 @@ export const GroupChannelDetailsScreen: React.FC<GroupChannelDetailsProps> = ({
279280
trailing={
280281
<Switch
281282
onValueChange={handleMuteToggle}
282-
style={getRtlMirrorSwitchStyle()}
283+
style={rtlMirrorSwitchStyle}
283284
trackColor={{
284285
false: semantics.controlToggleSwitchBg,
285286
true: semantics.accentPrimary,

examples/SampleApp/src/screens/OneOnOneChannelDetailScreen.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { Mute } from '../icons/Mute';
2626
import { Picture } from '../icons/Picture';
2727
import type { StackNavigatorParamList } from '../types';
2828
import { getUserActivityStatus } from '../utils/getUserActivityStatus';
29-
import { getRtlMirrorSwitchStyle } from '../utils/rtlMirrorSwitchStyle';
29+
import { useRtlMirrorSwitchStyle } from '../utils/rtlMirrorSwitchStyle';
3030

3131
type OneOnOneChannelDetailScreenRouteProp = RouteProp<
3232
StackNavigatorParamList,
@@ -52,6 +52,7 @@ export const OneOnOneChannelDetailScreen: React.FC<Props> = ({
5252
const {
5353
theme: { semantics },
5454
} = useTheme();
55+
const rtlMirrorSwitchStyle = useRtlMirrorSwitchStyle();
5556
const { chatClient } = useAppContext();
5657
const userMuted = useChannelMuteActive(channel);
5758

@@ -184,7 +185,7 @@ export const OneOnOneChannelDetailScreen: React.FC<Props> = ({
184185
trailing={
185186
<Switch
186187
onValueChange={handleMuteToggle}
187-
style={getRtlMirrorSwitchStyle()}
188+
style={rtlMirrorSwitchStyle}
188189
trackColor={{
189190
false: semantics.controlToggleSwitchBg,
190191
true: semantics.accentPrimary,
Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1+
import { useMemo } from 'react';
12
import { I18nManager, Platform, ViewStyle } from 'react-native';
23

3-
/** Mirrors Switch horizontally in RTL on iOS so thumb/track match layout direction. */
4-
export function getRtlMirrorSwitchStyle(): Pick<ViewStyle, 'transform'> {
5-
if (Platform.OS !== 'ios' || !I18nManager.isRTL) {
6-
return {};
7-
}
8-
return {
9-
transform: [{ scaleX: -1 }],
10-
};
4+
type RtlMirrorSwitchStyle = Pick<ViewStyle, 'transform'>;
5+
6+
/**
7+
* Style that mirrors `Switch` horizontally in RTL on iOS so thumb/track match layout direction.
8+
* The returned object is stable across renders while `I18nManager.isRTL` is unchanged (`useMemo`).
9+
*/
10+
export function useRtlMirrorSwitchStyle(): RtlMirrorSwitchStyle {
11+
const isRTL = I18nManager.isRTL;
12+
return useMemo(() => {
13+
if (Platform.OS !== 'ios' || !isRTL) {
14+
return {};
15+
}
16+
return {
17+
transform: [{ scaleX: -1 }],
18+
};
19+
}, [isRTL]);
1120
}

package/src/components/Poll/CreatePollContent.tsx

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
import { useMessageComposer } from '../../contexts/messageInputContext/hooks/useMessageComposer';
2525
import { useStateStore } from '../../hooks/useStateStore';
2626
import { primitives } from '../../theme';
27-
import { getRtlMirrorSwitchStyle } from '../../utils/rtlMirrorSwitchStyle';
27+
import { useRtlMirrorSwitchStyle } from '../../utils/rtlMirrorSwitchStyle';
2828

2929
const pollComposerStateSelector = (state: PollComposerState) => ({
3030
options: state.data.options,
@@ -172,11 +172,7 @@ export const CreatePollContent = () => {
172172
<Switch
173173
onValueChange={onAnonymousPollChangeHandler}
174174
value={isAnonymousPoll}
175-
style={[
176-
styles.optionCardSwitch,
177-
getRtlMirrorSwitchStyle(),
178-
anonymousPoll.optionCardSwitch,
179-
]}
175+
style={[styles.optionCardSwitch, anonymousPoll.optionCardSwitch]}
180176
/>
181177
</View>
182178
<View style={[styles.optionCard, suggestOption.wrapper]}>
@@ -190,11 +186,7 @@ export const CreatePollContent = () => {
190186
<Switch
191187
onValueChange={onAllowUserSuggestedOptionsChangeHandler}
192188
value={allowUserSuggestedOptions}
193-
style={[
194-
styles.optionCardSwitch,
195-
getRtlMirrorSwitchStyle(),
196-
suggestOption.optionCardSwitch,
197-
]}
189+
style={[styles.optionCardSwitch, suggestOption.optionCardSwitch]}
198190
/>
199191
</View>
200192
<View style={[styles.optionCard, addComment.wrapper]}>
@@ -208,11 +200,7 @@ export const CreatePollContent = () => {
208200
<Switch
209201
onValueChange={onAllowAnswersChangeHandler}
210202
value={allowAnswers}
211-
style={[
212-
styles.optionCardSwitch,
213-
getRtlMirrorSwitchStyle(),
214-
addComment.optionCardSwitch,
215-
]}
203+
style={[styles.optionCardSwitch, addComment.optionCardSwitch]}
216204
/>
217205
</View>
218206
</Animated.View>
@@ -290,6 +278,7 @@ const useStyles = () => {
290278
const {
291279
theme: { semantics },
292280
} = useTheme();
281+
const rtlMirrorSwitchStyle = useRtlMirrorSwitchStyle();
293282
return useMemo(() => {
294283
return StyleSheet.create({
295284
scrollView: {
@@ -333,7 +322,7 @@ const useStyles = () => {
333322
optionCardWrapper: {
334323
gap: primitives.spacingMd,
335324
},
336-
optionCardSwitch: { width: 64 },
325+
optionCardSwitch: { width: 64, ...rtlMirrorSwitchStyle },
337326
});
338-
}, [semantics]);
327+
}, [rtlMirrorSwitchStyle, semantics]);
339328
};

package/src/components/Poll/components/MultipleAnswersField.tsx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ import { MultipleVotesSettings } from './MultipleVotesSettings';
88
import { useTheme, useTranslationContext } from '../../../contexts';
99
import { useMessageComposer } from '../../../contexts/messageInputContext/hooks/useMessageComposer';
1010
import { primitives } from '../../../theme';
11-
import { getRtlMirrorSwitchStyle } from '../../../utils/rtlMirrorSwitchStyle';
12-
11+
import { useRtlMirrorSwitchStyle } from '../../../utils/rtlMirrorSwitchStyle';
1312
export const MultipleAnswersField = () => {
1413
const [allowMultipleVotes, setAllowMultipleVotes] = useState<boolean>(false);
1514
const { t } = useTranslationContext();
@@ -50,11 +49,7 @@ export const MultipleAnswersField = () => {
5049
<Switch
5150
onValueChange={onEnforceUniqueVoteHandler}
5251
value={allowMultipleVotes}
53-
style={[
54-
styles.optionCardSwitch,
55-
getRtlMirrorSwitchStyle(),
56-
multipleAnswers.optionCardSwitch,
57-
]}
52+
style={[styles.optionCardSwitch, multipleAnswers.optionCardSwitch]}
5853
/>
5954
</View>
6055
{allowMultipleVotes ? <MultipleVotesSettings /> : null}
@@ -66,6 +61,7 @@ const useStyles = () => {
6661
const {
6762
theme: { semantics },
6863
} = useTheme();
64+
const rtlMirrorSwitchStyle = useRtlMirrorSwitchStyle();
6965
return useMemo(() => {
7066
return StyleSheet.create({
7167
multipleAnswersWrapper: {
@@ -98,7 +94,7 @@ const useStyles = () => {
9894
justifyContent: 'space-between',
9995
flexDirection: 'row',
10096
},
101-
optionCardSwitch: { width: 64 },
97+
optionCardSwitch: { width: 64, ...rtlMirrorSwitchStyle },
10298
});
103-
}, [semantics]);
99+
}, [rtlMirrorSwitchStyle, semantics]);
104100
};

package/src/components/Poll/components/MultipleVotesSettings.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { useStableCallback } from '../../../hooks';
1111
import { useStateStore } from '../../../hooks/useStateStore';
1212
import { Minus, Plus } from '../../../icons';
1313
import { primitives } from '../../../theme';
14-
import { getRtlMirrorSwitchStyle } from '../../../utils/rtlMirrorSwitchStyle';
14+
import { useRtlMirrorSwitchStyle } from '../../../utils/rtlMirrorSwitchStyle';
1515
import { Button } from '../../ui';
1616

1717
const pollComposerStateSelector = (state: PollComposerState) => ({
@@ -153,11 +153,7 @@ export const MultipleVotesSettings = () => {
153153
<Switch
154154
onValueChange={onMaxVotesPerPersonHandler}
155155
value={allowMaxVotesPerPerson}
156-
style={[
157-
styles.optionCardSwitch,
158-
getRtlMirrorSwitchStyle(),
159-
multipleAnswers.optionCardSwitch,
160-
]}
156+
style={[styles.optionCardSwitch, multipleAnswers.optionCardSwitch]}
161157
/>
162158
</View>
163159
{allowMaxVotesPerPerson ? (
@@ -199,6 +195,7 @@ const useStyles = () => {
199195
const {
200196
theme: { semantics },
201197
} = useTheme();
198+
const rtlMirrorSwitchStyle = useRtlMirrorSwitchStyle();
202199

203200
return useMemo(() => {
204201
return StyleSheet.create({
@@ -226,7 +223,7 @@ const useStyles = () => {
226223
justifyContent: 'space-between',
227224
flexDirection: 'row',
228225
},
229-
optionCardSwitch: { width: 64 },
226+
optionCardSwitch: { width: 64, ...rtlMirrorSwitchStyle },
230227
settingsWrapper: {
231228
gap: primitives.spacingMd,
232229
},
@@ -249,5 +246,5 @@ const useStyles = () => {
249246
borderColor: semantics.borderUtilityDisabledOnSurface,
250247
},
251248
});
252-
}, [semantics]);
249+
}, [rtlMirrorSwitchStyle, semantics]);
253250
};
Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1+
import { useMemo } from 'react';
12
import { I18nManager, Platform, ViewStyle } from 'react-native';
23

3-
/** Mirrors Switch horizontally in RTL on iOS so thumb/track match layout direction. */
4-
export function getRtlMirrorSwitchStyle(): Pick<ViewStyle, 'transform'> {
5-
if (Platform.OS !== 'ios' || !I18nManager.isRTL) {
6-
return {};
7-
}
8-
return {
9-
transform: [{ scaleX: -1 }],
10-
};
4+
type RtlMirrorSwitchStyle = Pick<ViewStyle, 'transform'>;
5+
6+
/**
7+
* Style that mirrors `Switch` horizontally in RTL on iOS so thumb/track match layout direction.
8+
* The returned object is stable across renders while `I18nManager.isRTL` is unchanged (`useMemo`).
9+
*/
10+
export function useRtlMirrorSwitchStyle(): RtlMirrorSwitchStyle {
11+
const isRTL = I18nManager.isRTL;
12+
return useMemo(() => {
13+
if (Platform.OS !== 'ios' || !isRTL) {
14+
return {};
15+
}
16+
return {
17+
transform: [{ scaleX: -1 }],
18+
};
19+
}, [isRTL]);
1120
}

0 commit comments

Comments
 (0)