Skip to content

Switch not visually toggling in drawer navigator but state is changing #105

Description

@jianrontan

So in my Drawer.Navigator is a page called settings.js that manages state for my switch, SettingsComponent.jsx that uses Switch from 'react-native-switch'. The problem is that when I press the switch, it doesn't visually toggle, but according to console.log, the state is actually changing.

Before I implemented the state in settings.js, it used to be able to toggled.

I tried every trick in the book (I believe), using 'useDrawerStatus', 'useCallback', 'useEffect' to re-render, but nothing seems to want to make my switch visually move!

For more context, settings.js is a Drawer.Screen in my Drawer.Navigator and I believe this is to be the issue as I believe the switch is able to visually toggle if it were in my Stack.Navigator instead as previously, I have encountered the same problem but with the native 'Switch' component from 'react-native' and just by moving the component to my Stack.Screen, it ends up being able to visually toggle.

settings.js

import React, { useState, useCallback, useEffect } from 'react';
import { View, ScrollView, SafeAreaView } from 'react-native';
import { useNavigation, useFocusEffect } from '@react-navigation/native';

import SettingsComponent from '../myComponents/drawer/settings/SettingsComponent';
import { COLORS, icons, images, FONT, SIZES } from '../constants';

function Settings({navigation}) {
  const [isBreakContinueEnabled, setBreakContinueEnabled] = useState(false);
  const [isBreakSaveEnabled, setBreakSaveEnabled] = useState(false);
  const [isNotificationEnabled, setNotificationEnabled] = useState(false);

  const setBreakContinueEnabledMemoized = useCallback((value) => {
    setBreakContinueEnabled(value);
  }, []);
  
  const setBreakSaveEnabledMemoized = useCallback((value) => {
    setBreakSaveEnabled(value);
  }, []);
  
  const setNotificationEnabledMemoized = useCallback((value) => {
    setNotificationEnabled(value);
  }, []);

  useEffect(() => {
  }, [isBreakContinueEnabled, isBreakSaveEnabled, isNotificationEnabled]);

  console.log({ isBreakContinueEnabled, isBreakSaveEnabled, isNotificationEnabled });

  const breakContinue=[{title: 'Continue after break', subTitle:'Stopwatch starts once break ends'}];
  const breakSave=[{title: 'Save break time', subTitle:'Save break time for later on continue'}];
  const notificationToggle=[{title: 'Notifications', subTitle: 'Notification when break ends'}];

  return (
    <SafeAreaView style={{ flex: 1, backgroundColor: COLORS.grayBeige }}>
      <ScrollView showsVerticalScrollIndicator={false}>
        <View>
          <SettingsComponent 
            settingsOptions={breakContinue} 
            isEnabled={isBreakContinueEnabled} 
            setIsEnabled={setBreakContinueEnabledMemoized} 
          />
          <SettingsComponent 
            settingsOptions={breakSave} 
            isEnabled={isBreakSaveEnabled} 
            setIsEnabled={setBreakSaveEnabledMemoized} 
          />
          <SettingsComponent 
            settingsOptions={notificationToggle} 
            isEnabled={isNotificationEnabled} 
            setIsEnabled={setNotificationEnabledMemoized} 
          />
        </View>
      </ScrollView>
    </SafeAreaView>
  );
}

export default Settings;

SettingsComponent.jsx

import { useState } from 'react';
import { View, Text, SafeAreaView, ScrollView } from 'react-native';
import { TouchableOpacity } from 'react-native-gesture-handler';
import { Switch } from 'react-native-switch';
import { useDrawerStatus } from '@react-navigation/drawer';

import { COLORS, FONT, SIZES } from '../../../constants';

const SettingsComponent = ({settingsOptions, isEnabled, setIsEnabled}) => {
    const isDrawerOpen = useDrawerStatus() === 'open';

    const toggleSwitch = () => {
        if (!isDrawerOpen){
          setIsEnabled(previousState => !previousState);
        }
      }
    

    return (
        <SafeAreaView>
            <View style={{ flex: 1, flexDirection: 'row', justifyContent: 'space-between' }}>
                {settingsOptions.map(({title, subTitle }, index) =>
                    <TouchableOpacity key={title}>
                        <View style={{
                            paddingHorizontal: 20,
                            paddingTop: 20,
                            paddingBottom: 20,
                        }}>
                            <Text style={{fontFamily: FONT.medium, fontSize: SIZES.medium, color: COLORS.themeColor}}>{title}</Text>
                            {subTitle && <Text style={{fontFamily: FONT.regular, fontSize: SIZES.small, color: COLORS.lightThemeColor}}>{subTitle}</Text>}
                        </View>
                    </TouchableOpacity>
                )}
                <View 
                    style={{
                        paddingHorizontal: 20,
                        paddingTop: 20,
                        paddingBottom: 20,
                        justifyContent: 'center',
                    }}
                >
                    <Switch
                        value={isEnabled}
                        onValueChange={toggleSwitch}
                        disabled={false}
                        activeText={'On'}
                        inActiveText={'Off'}
                        circleSize={25}
                        barHeight={20}
                        circleBorderWidth={1}
                        circleBorderActiveColor={COLORS.themeColor}
                        circleBorderInactiveColor={'#c9beb1'}
                        backgroundActive={COLORS.themeColor}
                        backgroundInactive={'#c9beb1'}
                        circleActiveColor={COLORS.lightBeige}
                        circleInActiveColor={COLORS.lightBeige}
                        changeValueImmediately={true} // if rendering inside circle, change state immediately or wait for animation to complete
                        innerCircleStyle={{ alignItems: "center", justifyContent: "center" }} // style for inner animated circle for what you (may) be rendering inside the circle
                        outerCircleStyle={{}} // style for outer animated circle
                        renderActiveText={false}
                        renderInActiveText={false}
                        switchLeftPx={2} // denominator for logic when sliding to TRUE position. Higher number = more space from RIGHT of the circle to END of the slider
                        switchRightPx={2} // denominator for logic when sliding to FALSE position. Higher number = more space from LEFT of the circle to BEGINNING of the slider
                        switchWidthMultiplier={2} // multiplied by the `circleSize` prop to calculate total width of the Switch
                        switchBorderRadius={20} // Sets the border Radius of the switch slider. If unset, it remains the circleSize.
                    />
                </View>
            </View>
        </SafeAreaView>
    );
};

export default SettingsComponent;

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions