skip to Main Content

I’ve a Sections screen that gets quiz data from database and passes it off to Quiz screen based on the Section.
The structure of the data from database is:

{
  sections: [
   {
    questions: [
     {
      question: "",
      options: ["", "", ""],
      answer: "",

...

}

Since I’m using React Navigation with TypeScript I need to define the RootStackParamList for Section and Quiz screen, something like:

type RootStackParamList = {
  Home: undefined;
  Modules: undefined;
  Sections: {data: {}};
  Quiz: {data: {}};
};

Navigating across screens like:

{data.sections.slice(1).map((section, index) => (
 <TouchableOpacity
  key={index}
  onPress={() => navigation.navigate('Quiz', {data: section.questions})}
 </TouchableOpacity>
))}

I get the following error message on the line when navigating to the new screen with params onPress={() => navigation.navigate('Quiz', {data: section.questions})}:

Argument of type '{ data: any; }' is not assignable to parameter of type '{ sections: [{ questions: []; }]; }'.
  Object literal may only specify known properties, and 'data' does not exist in type '{ sections: [{ questions: []; }]; }'.ts(2345)

How can I define RootStackParamList in order to fix the error?

ModuleList.tsx

import * as React from 'react';
import {
  ScrollView,
  Text,
  TouchableOpacity,
  StyleSheet,
  useColorScheme,
  SafeAreaView,
} from 'react-native';
import {Colors} from 'react-native/Libraries/NewAppScreen';
import {useState, useEffect} from 'react';
import {List} from 'react-native-paper';
import {db} from '../../firebase';
import {ref, onValue} from 'firebase/database';
// import { AdmobContext } from "../components/AdmobController";
import {StackNavigationProp} from '@react-navigation/stack';

type RootStackParamList = {
  Home: undefined;
  Modules: undefined;
  Sections: {data: {}};
  Quiz: undefined;
};
type ModuleListScreenNavigationProp = StackNavigationProp<
  RootStackParamList,
  'Modules'
>;
type Props = {
  navigation: ModuleListScreenNavigationProp;
};

const ModuleList = ({navigation}: Props) => {
  const [quiz, setQuiz] = useState({});
  const isDarkMode = useColorScheme() === 'dark';
  const style = {
    backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
    color: isDarkMode ? Colors.lighter : Colors.darker,
    optionBackgroundColor: isDarkMode ? '#333333' : '#D3D3D3',
  };
  // let { renderBanner } = useContext(AdmobContext);

  useEffect(() => {
    return onValue(ref(db, '/'), (querySnapShot: {val: () => {}}) => {
      const data = querySnapShot.val() || {};
      let quizItems = {...data};
      setQuiz(quizItems);
    });
  }, []);

  return (
    <ScrollView
      style={[styles.container, {backgroundColor: style.backgroundColor}]}>
      <SafeAreaView>
        <TouchableOpacity
          onPress={() => navigation.navigate('Sections', {data: quiz})}
          style={[
            styles.listItem,
            {backgroundColor: style.optionBackgroundColor},
          ]}>
          <Text style={[styles.text, {color: style.color}]}>
            Security Guard Training
          </Text>
          <List.Icon color={style.color} icon="security" />
        </TouchableOpacity>
        <TouchableOpacity
          onPress={() => navigation.navigate('Sections', {data: quiz})}
          style={[
            styles.listItem,
            {backgroundColor: style.optionBackgroundColor},
          ]}>
          <Text style={[styles.text, {color: style.color}]}>Useful Links</Text>
          <List.Icon color={style.color} icon="link" />
        </TouchableOpacity>
      </SafeAreaView>
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingVertical: 30,
    paddingHorizontal: 20,
    position: 'relative',
  },
  listItem: {
    borderRadius: 10,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    marginBottom: 1,
    paddingHorizontal: 10,
    paddingVertical: 15,
  },
  text: {
    fontSize: 18,
    marginHorizontal: 15,
  },
});

export default ModuleList;

SectionList.tsx

import * as React from 'react';
import {
  ScrollView,
  StyleSheet,
  Text,
  TouchableOpacity,
  useColorScheme,
} from 'react-native';
import {List} from 'react-native-paper';
// import { AdmobContext } from "../components/AdmobController";
import {StackNavigationProp} from '@react-navigation/stack';
import {RouteProp} from '@react-navigation/native';
import {Colors} from 'react-native/Libraries/NewAppScreen';

type QuestionDataType = {
  question: string;
  options: string[];
  answer: string;
};
type SectionDataType = {
  sections: {
    questions: QuestionDataType[];
  }[];
};
type RootStackParamList = {
  Home: undefined;
  Modules: undefined;
  Sections: {data: QuestionDataType[]};
  Quiz: {data: QuestionDataType[]};
};
type SectionListScreenNavigationProp = StackNavigationProp<
  RootStackParamList,
  'Sections'
>;
type SectionListScreenRouteProp = RouteProp<RootStackParamList, 'Sections'>;
type Props = {
  navigation: SectionListScreenNavigationProp;
  route: SectionListScreenRouteProp;
};

const SectionList = ({navigation, route}: Props) => {
  const data = route.params.data;
  const isDarkMode = useColorScheme() === 'dark';
  const style = {
    backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
    color: isDarkMode ? Colors.lighter : Colors.darker,
    optionBackgroundColor: isDarkMode ? '#333333' : '#D3D3D3',
  };
  // let { renderBanner } = useContext(AdmobContext);

  return (
    <ScrollView
      style={[styles.container, {backgroundColor: style.backgroundColor}]}>
      <TouchableOpacity
        key={1}
        onPress={() =>
          navigation.navigate('Quiz', {data: data.sections[0].questions})
        }
        style={[
          styles.listItem,
          {backgroundColor: style.optionBackgroundColor},
        ]}>
        <Text style={[styles.text, {color: style.color}]}>Section 1</Text>
        <List.Icon color={style.color} icon="frequently-asked-questions" />
      </TouchableOpacity>

      {data.sections.slice(1).map((section, index) => (
        <TouchableOpacity
          key={index}
          onPress={() => navigation.navigate('Quiz', {data: section.questions})}
          style={[
            styles.listItem,
            {backgroundColor: style.optionBackgroundColor},
          ]}>
          <Text style={[styles.text, {color: style.color}]}>
            {'Section '}
            {index + 2}
          </Text>
          <List.Icon color={style.color} icon="frequently-asked-questions" />
        </TouchableOpacity>
      ))}
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingVertical: 30,
    paddingHorizontal: 20,
    position: 'relative',
  },
  listItem: {
    borderRadius: 10,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    marginBottom: 1,
    paddingHorizontal: 10,
    paddingVertical: 15,
  },
  text: {
    fontSize: 18,
    marginHorizontal: 15,
  },
});

export default SectionList;

2

Answers


  1. You can first define types for your section and question like this:

    type QuestionDataType = {
      question: string,
      options: string[],
      answer: string,
    }
    type SectionDataType = {
     sections: {
       questions: QuestionDataType[];
     }[];
    }
    

    Then inside the RootStackParamList type:

    type RootStackParamList = {
      Home: undefined;
      Modules: undefined;
      Sections: {data: QuestionDataType[]};
      Quiz: {data: {}};
    };
    

    And please remember when you get data from the server (your database), for the variable/state that store the response define SectionDataType till everything be ok when use that for passing the data in the navigate.
    At the end your navigation section will execute without error:

    {data.sections.slice(1).map((section, index) => (
     <TouchableOpacity
      key={index}
      onPress={() => navigation.navigate('Quiz', {data: section.questions})}
     </TouchableOpacity>
    ))}
    
    Login or Signup to reply.
  2. I have a handy-dandy way to declare my React-Navigation thigs.

    Inside your AppNavigator.tsx

    // Telling TypeScript what to expect as "route" identifier
    export interface AppRoutes {
      home: "home";
      settings: "settings";
    }
    
    // Telling TypeScript what every screen will expect as ParamTypes
    //  - home screen will ask for 'HomeProps', but it's optional
    //  - settings screen will ask for 'SettingsProps', required!
    export type AppNavigationScreenProps = {
      home?: HomeProps;
      settings: SettingsProps;
    }
    
    // Black magic
    export type AppStackParamList = {
      [key in keyof AppRoutes]: AppNavigationScreenProps[key];
    };
    
    // Stack navigator for this route that uses black magic
    const Stack = createNativeStackNavigator<AppStackParamList>();
    
    
    // Interface for my component (empty, but one does not know when he will need it)
    interface AppNavigatorProps {}
    
    // Navigator component, home of all navigation
    const AppNavigator: React.FC<AppNavigatorProps> = (_: AppNavigatorProps) => {
        // ... things
        return (
            <NavigationContainer>
                <Stack.Navigator>
                    <Stack.Screen
                        name="home" // This will be restricted to "home" and "settings"
                        component={Home}  // Only "Home" component can fit here, see Home.tsx
                        options={/* Things */}
                    />
                    <Stack.Screen
                        name="settings" // This will be restricted to "home" and "settings"
                        component={Settings} // Only "Settings" component can fit here, see Settings.tsx
                        options={/* Things */}
                    />
                </Stack.Navigator>
            </NavigationContainer>
        )
    }
    

    And then inside your Home.tsx and Settings.tsx component:

    // ---------------------------------------
    // Home.tsx
    // ---------------------------------------
    
    // This is the interface that AppNavigation.tsx was referencing!
    export interface HomeProps {
        myData: string[]
        myOptionalData?: number;
        // ...and so on
    }
    
    // Black Magic 2.0 that will make "Home" fit as "home" reference
    type Props = NativeStackScreenProps<AppNavigationScreenProps, "home">;
    
    // Home component!
    const Home: React.FC<Props> = ({ navigation, route }: Props) => {
        // Can be undefined, since HomeProps is optional!
        const params = route.params; 
        const myData = params?.myData;
    
        // ....things and black magic
    }
    
    // ---------------------------------------
    // Settings.tsx
    // ---------------------------------------
    
    // This is the interface that AppNavigation.tsx was referencing!
    export interface SettingsProps {
        name: string;
        myOtherData: MyDataType;
    }
    
    // Black Magic 2.0 that will make "Settings" fit as "settings" reference
    type Props = NativeStackScreenProps<AppNavigationScreenProps, "settings">;
    
    // Setting component!
    const Settings: React.FC<Props> = ({ navigation, route }: Props) => {
        // It's not optional can use it directly!
        const { name, myOtherData } = route.params; 
    
        // ....things and black magic
    }
    
    

    Hope it will help to make sure your data will be typed correctly for every screen you will make.

    Make sure your data is serializable (you should not pass functions as arguments), if not you will receive some warnings about screen-to-screen communication.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search