javascriptreactjsreact-router-domreact-native-flatlistreact-flatlist

Navigate to another component on clicking FlatList in React


I am using FlatList to display Image and Text side by side in a Component screen. I want to click on any row and open a new Component class[FoodItems] and passing just a simple string.

It says, "Cannot read properties of undefined (reading 'navigate')". I have installed all required packages.

npm install react-navigation

npm install @react-navigation/native

I don't know what this.props here is? I am not sending props from previous screen. I just copy paste it from some post.

Restaurants.js:

import { View, FlatList, Text, TouchableOpacity } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import withRouter from './withRouter';

class Restaurants extends Component {
  render() {
    if (!this.state.restaurantsSet) {
      this.setState({ restaurantsSet: true });
      this.settingRestaurants(this.props.location.state.station);
    }

    return (
      <View>
        <div className='header'></div>
        <FlatList
          ItemSeparatorComponent={this.FlatListItemSeparator}
          ListHeaderComponent={this.FlatListHeader} 
          style={{ flex: 1 }}
          data={this.state.restaurants}
          renderItem={({ item }) => 
            <TouchableOpacity 
              onPress={() => 
                this.props.navigation.navigate(
                  'FoodItems',
                  { message: 'my_message' }
                )
              }
            >
              <Row {...item} />
            </TouchableOpacity>
          }
        />
      </View>
    )
  }
}

export default withRouter(Restaurants);

withRouter.js:


import { useLocation, useParams, useNavigation } from 'react-router-dom'; 

const withRouter = WrappedComponent => props => {
  const location = useLocation();
  const params = useParams();
  const navigation = useNavigation();
  
  return (
    <WrappedComponent
      {...{props, params}}
      {...{ location, params,}}
      {...{navigation, params}}
    />
  );
};

export default withRouter;

Solution

  • The withRouter HOC is implemented incorrectly. The useNavigation hook is a RRDv6.4 Data router only hook. Emphasis mine.

    This hook tells you everything you need to know about a page navigation to build pending navigation indicators and optimistic UI on data mutations. Things like:

    • Global loading indicators
    • Disabling forms while a mutation is happening
    • Adding busy indicators to submit buttons
    • Optimistically showing a new record while it's being created on the server
    • Optimistically showing the new state of a record while it's being updated

    Important

    This feature only works if using a data router, see Picking a Router

    import { useNavigation } from "react-router-dom";
    
    function SomeComponent() {
      const navigation = useNavigation();
      navigation.state;
      navigation.location;
      navigation.formData;
      navigation.formAction;
      navigation.formMethod;
    }
    

    The useNavigation hook isn't used to issue any imperative navigation actions. For this you should import and use the useNavigate hook as this is the hook that returns the navigate function.

    import { useLocation, useParams, useNavigate } from 'react-router-dom'; 
    
    const withRouter = WrappedComponent => props => {
      const location = useLocation();
      const params = useParams();
      const navigate = useNavigate();
      
      return (
        <WrappedComponent
          {...props}
          {...{ location, params, navigate }}
        />
      );
    };
    
    export default withRouter;
    

    The in the React Class component access this.props.navigate. Don't forget that data you want to pass in route state is passed in the option object's state property.

    import { View, FlatList, Text, TouchableOpacity } from 'react-native';
    import withRouter from './withRouter';
    
    class Restaurants extends Component {
      ...
    
      componentDidMount() {
        if (!this.state.restaurantsSet) {
          this.setState({ restaurantsSet: true });
          this.settingRestaurants(this.props.location.state?.station);
        }
      }
    
      ...
    
      render() {
        return (
          <View>
            <div className='header'></div>
            <FlatList
              ItemSeparatorComponent={this.FlatListItemSeparator}
              ListHeaderComponent={this.FlatListHeader} 
              style={{ flex: 1 }}
              data={this.state.restaurants}
              renderItem={({ item }) => 
                <TouchableOpacity 
                  onPress={() => 
                    this.props.navigate(
                      'FoodItems',
                      { state: { message: 'my_message' }}
                    );
                  }
                >
                  <Row {...item} />
                </TouchableOpacity>
              }
            />
          </View>
        )
      }
    }
    
    export default withRouter(Restaurants);