Noble Polygon
Noble Polygon

Reputation: 806

Changing values in a dropdown menu

I'm working with a custom dropdown menu in React Native and am having a problem changing the text when trying to reuse the dropdown in other components.

DropdownMenu.js

export const DropdownMenu = ({dropdownMenuItems}) => {
  const [isActive, setIsActive] = React.useState(false);
  const onPress = () => {
    setIsActive(!isActive);
  };

  return (
    <TouchableOpacity
      activeOpacity={1}
      onPress={() => setIsActive(false)}
      style={{flex: 1}}>
      <View>
        <TouchableOpacity style={styles.img} onPress={onPress}>
          <Image style={styles.imgimg} source={require('./icon.png')} />
        </TouchableOpacity>
        <Animated.View
          style={{
            ...styles.menu,
            opacity: fadeAnim,
            transform: [{translateY: translateAnim}],
          }}
          pointerEvents={isActive ? 'auto' : 'none'}>
          <FlatList
            data={dropdownMenuItems}
            renderItem={({item, index}) => (
              <OpenURLButton
                url={item.href}
                label={item.name}
                style={
                  index === 0
                    ? {borderTopLeftRadius: 8, borderTopRightRadius: 8}
                    : index === dropdownMenuItems.length - 1
                    ? {borderBottomLeftRadius: 8, borderBottomRightRadius: 8}
                    : {}
                }
              />
            )}
          />
        </Animated.View>
      </View>
    </TouchableOpacity>
  );
};

In the CompactMenu component, I import my <DropdownMenu /> and set my initial values for my menu:

CompactMenu.js


import React from 'react';
import {SafeAreaView} from 'react-native';

import {DropdownMenu} from './DropdownMenu';

const CompactMenu = () => {
  const backgroundStyle = {
    backgroundColor: '#fff',
    flex: 1,
    display: 'flex',
  };

  const dropdownMenuItems = [
    {name: 'Messages', href: '/messages'},
    {name: 'Trips', href: '/trips'},
    {name: 'Saved', href: '/saved'},
  ];

  return (
    <SafeAreaView style={backgroundStyle}>
      <DropdownMenu dropdownMenuItems={dropdownMenuItems} />
    </SafeAreaView>
  );
};

export default CompactMenu;

After importing <CompactMenu /> into another component, I try to change the name & the href in object:

example import: <CompactMenu dropdownMenuItems={[{name: "changed name", href: "/somePath"}]} />

However, the same strings from CompactMenu.js are displayed in the dropdown.

Being new to RN, I'm not sure of two things here.

1.) Why do the text value for "name" not change when used in a different component?

2.) Shouldn't navigation to another screen use { navigation } instead of href:? I've tried adding onPress={() => navigation.navigate('SomeScreen') in the href but I get an error.

I'm not sure what the correct solution to this is. Any help would be appreciated.

Upvotes: 0

Views: 53

Answers (1)

David Scholz
David Scholz

Reputation: 9856

You are not using the props dropdownMenuItems that you are passing to CompactMenu. Instead, you reuse the same menu items for the DropdownMenu component everytime you create a CompactMenu.

You need to use the props that you are passing. I have kept the static items as a default value. If you would like to have these items as well and to add additional items via props, then have a look at the second solution.

Notice that I have integrated some small changes to the rest of the code as well.

const defaultItems = [
    {name: 'Messages', href: '/messages'},
    {name: 'Trips', href: '/trips'},
    {name: 'Saved', href: '/saved'},
];

const CompactMenu = ({dropdownMenuItems = defaultItems}) => {
  return (
    <SafeAreaView style={styles.backgroundStyle}>
      <DropdownMenu dropdownMenuItems={dropdownMenuItems} />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
    backgroundStyle: {
        backgroundColor: '#fff',
        flex: 1,
    }
});

Now using the CompactMenu component can receive dropdownMenuItems and will pas them to the DropdownMenu component.

<CompactMenu dropdownMenuItems={[{name: "changed name", href: "/somePath"}]} />

If you want to keep default items and add additional items via props, we could achieve this by merging the provided props with our default items.

const defaultItems = [
    {name: 'Messages', href: '/messages'},
    {name: 'Trips', href: '/trips'},
    {name: 'Saved', href: '/saved'},
];

const CompactMenu = ({dropdownMenuItems}) => {
  return (
    <SafeAreaView style={styles.backgroundStyle}>
      <DropdownMenu dropdownMenuItems={[...defaultItems, ...dropdownMenuItems]} />
    </SafeAreaView>
  );
};

Your second questions addresses the react-navigation framework for react-native. This is a very broad topic and I am assuming from your question that you don't know how this works yet, since you have not setup the necessary structure for using it. I encourage you to go through the documentation first.

In summary, you will need to define a navigator, e.g. a stack-navigator and add a name reference for each of your screens to the dropdown menu. To keep things short, here is a minimal example on how this could work.

const MenuScreen1 = (props) {
   return (...)
}

const MenuScreen2 = (props) {
   return (...)
}

const CompactMenu = ({dropdownMenuItems, navigation}) => {
  return (
    <SafeAreaView style={styles.backgroundStyle}>
      <DropdownMenu dropdownMenuItems={dropdownMenuItems} navigation={navigation} />
    </SafeAreaView>
  );
};

const dropdownMenuItems = [
 {name: 'Menu Item 1', screen: 'Item1'},
 {name: 'Menu Item 2', screen: 'Item2'},
]

const Home = ({navigation}) {
   return <CompactMenu navigation={navigation} dropdownMenuItems={dropdownMenuItems} />
}

const Stack = createNativeStackNavigator();

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Home">
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Item1" component={MenuScreen1} />
        <Stack.Screen name="Item2" component={MenuScreen2} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default App;

Notice that I have created two screens whose names, which I have defined in the stack navigator, are provided to the CompactMenu. Notice as well that the navigation framework will pas the navigation object only to components that are defined as a screen, thus I have decided to pass the navigation object to the CompactMenu and the DropdownMenu component. You can use the useNavigation hook if you prefer this method.

Now, in your DropdownMenu you can navigate on click to the defined screens.

export const DropdownMenu = ({dropdownMenuItems, navigation}) => {

  const [isActive, setIsActive] = React.useState(false);
  const onPress = () => {
    setIsActive(!isActive);
  };

  return (
    <TouchableOpacity
      activeOpacity={1}
      onPress={() => setIsActive(false)}
      style={{flex: 1}}>
      <View>
        <TouchableOpacity style={styles.img} onPress={onPress}>
          <Image style={styles.imgimg} source={require('./icon.png')} />
        </TouchableOpacity>
        <Animated.View
          style={{
            ...styles.menu,
            opacity: fadeAnim,
            transform: [{translateY: translateAnim}],
          }}
          pointerEvents={isActive ? 'auto' : 'none'}>
          <FlatList
            data={dropdownMenuItems}
            renderItem={({item, index}) => (
              <Button
                onPress={() => navigation.navigate(item.screen)}
                title={item.name}
                style={
                  index === 0
                    ? {borderTopLeftRadius: 8, borderTopRightRadius: 8}
                    : index === dropdownMenuItems.length - 1
                    ? {borderBottomLeftRadius: 8, borderBottomRightRadius: 8}
                    : {}
                }
              />
            )}
          />
        </Animated.View>
      </View>
    </TouchableOpacity>
  );
};

Upvotes: 1

Related Questions