web-dev-qa-db-fra.com

React-native - Passage de données d'un écran à un autre

J'essaie d'apprendre à utiliser react-native. Je crée donc une petite application qui récupère une liste d'utilisateurs d'un serveur, l'affiche dans une variable listview et où le fait d'appuyer sur une ligne ouvre un écran affichant les données de l'utilisateur. 

J'ai configuré un navigateur pour passer d'un écran à un autre. En appuyant sur la ligne, je peux ouvrir un nouvel écran, mais je ne vois pas comment passer des données à ce nouvel écran.

J'ai configuré un navigateur dans index.Android.js

import React from 'react';
import {
  AppRegistry,
  Navigator,
} from 'react-native';

import ContactList from './src/containers/ContactList.js';

function MyIndex() {
  return (
    <Navigator
      initialRoute={{ name: 'index', component: ContactList }}
      renderScene={(route, navigator) => {
        if (route.component) {
          return React.createElement(route.component, { navigator });
        }

        return undefined;
      }}
    />
  );
}

AppRegistry.registerComponent('reactest', () => MyIndex);

Tout d’abord, j’affiche un écran avec un bouton et une liste vide (ContactList.js). Après avoir appuyé sur le bouton, je récupère des données JSON utilisées pour mettre à jour la listview

import React, { Component, PropTypes } from 'react';
import {
  Text,
  View,
  TouchableOpacity,
  TouchableHighlight,
  ListView,
  Image,
} from 'react-native';

import styles from '../../styles';
import ContactDetails from './ContactDetails';

const url = 'http://api.randomuser.me/?results=15&seed=azer';

export default class ContactList extends Component {
  static propTypes = {
    navigator: PropTypes.object.isRequired,
  }
  constructor(props) {
    super(props);

    const datasource = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
    this.state = {
      jsonData: datasource.cloneWithRows([]),
      ds: datasource,
    };
  }
  _handlePress() {
    return fetch(url)
      // convert to json
      .then((response) => response.json())
      // do some string manipulation on json
      .then(({ results }) => {
        const newResults = results.map((user) => {
          const newUser = {
            ...user,
            name: {
              title: `${user.name.title.charAt(0).toUpperCase()}${user.name.title.slice(1)}`,
              first: `${user.name.first.charAt(0).toUpperCase()}${user.name.first.slice(1)}`,
              last: `${user.name.last.charAt(0).toUpperCase()}${user.name.last.slice(1)}`,
            },
          };

          return newUser;
        });

        return newResults;
      })
      // set state
      .then((results) => {
        this.setState({
          jsonData: this.state.ds.cloneWithRows(results),
        });
      });
  }
  renderRow(rowData: string) {
    return (
      <TouchableHighlight
        onPress={() => {
          this.props.navigator.Push({
            component: ContactDetails,
          });
        }}
      >
        <View style={styles.listview_row}>
          <Image
            source={{ uri: rowData.picture.thumbnail }}
            style={{ height: 48, width: 48 }}
          />
          <Text>
            {rowData.name.title} {rowData.name.first} {rowData.name.last}
          </Text>
        </View>
      </TouchableHighlight>
    );
  }
  render() {
    const view = (
      <View style={styles.container}>
        <TouchableOpacity
          onPress={() => this._handlePress()}
          style={styles.button}
        >
          <Text>Fetch results?</Text>
        </TouchableOpacity>
        <ListView
          enableEmptySections
          dataSource={this.state.jsonData}
          renderRow={(rowData) => this.renderRow(rowData)}
          onPress={() => this._handleRowClick()}
        />
      </View>
    );

    return view;
  }
}

Lorsqu'on appuie sur une ligne, un nouvel écran ContactDetails.js apparaît, qui est censé afficher les données de l'utilisateur: 

import React, {
} from 'react';

import {
  Text,
  View,
} from 'react-native';

import styles from '../../styles';

export default function ContactDetails() {
  return (
    <View style={styles.container}>
      <Text>{this.props.title}</Text>
      <Text>{this.props.first}</Text>
      <Text>{this.props.last}</Text>
    </View>
  );
}

À ce stade, j'ai eu cette erreur: 

indéfini n'est pas un objet (évaluer 'this.props.title')

J'ai essayé beaucoup de choses telles que: 

this.props.navigator.Push({
    component: ContactDetails,
    title: rowData.name.title,
    first: rowData.name.first,
    last: rowData.name.last,
});

ou 

this.props.navigator.Push({
    component: ContactDetails,
    props: {
        title: rowData.name.title,
        first: rowData.name.first,
        last: rowData.name.last,
    }
});

ou

this.props.navigator.Push({
    component: ContactDetails,
    passProps: {
        title: rowData.name.title,
        first: rowData.name.first,
        last: rowData.name.last,
    }
});

Mais en vain.

J'ai aussi lu que je devrais utiliser redux. Est-ce que je fais quelque chose de mal ?

EDIT: Le problème est ici: 

<TouchableHighlight
    onPress={() => {
      this.props.navigator.Push({
        component: ContactDetails,
      });
    }}
>

Je suppose que je devrais passer certains paramètres ici, mais tout ce que j'ai essayé ci-dessus a échoué.

8
vincenth

Donc, le problème semble se situer ici:

<Navigator
  initialRoute={{ name: 'index', component: ContactList }}
  renderScene={(route, navigator) => {
    if (route.component) {
      return React.createElement(route.component, { navigator });
    }

    return undefined;
  }}
/>

Dans la fonction renderScene, vous recevez la route (et utilisez route.component), mais vous ne transmettez pas votre route.props ou route.passProps ou ce que vous voulez appeler! Et d'après ce que je vois dans la source de Navigator à ce moment-là, vous devriez avoir l'objet route complet tel que vous l'avez créé. Donc, vous devriez pouvoir passer vos accessoires.

Par exemple: 

<Navigator
  initialRoute={{ name: 'index', component: ContactList }}
  renderScene={(route, navigator) => {
    if (route.component) {
      return React.createElement(route.component, { navigator, ...route.props });
    }

    return undefined;
  }}
/>

// Push
<TouchableHighlight
  onPress={() => {
    this.props.navigator.Push({
      component: ContactDetails,
      props: { /* ... */ }
    });
  }}
>

Vous pouvez également configurer redux, mais ce n’est pas une obligation. Cependant, si votre application grossit, vous devriez vraiment envisager d'utiliser un magasin externe!


Mettre à jour:

Il y a aussi un autre problème.

Vous utilisez des composants fonctionnels. Les composants fonctionnels n'ont pas de this. Ils reçoivent les accessoires en paramètre.

Donc ça devrait être comme ça:

export default function ContactDetails(props) {
  return (
    <View style={styles.container}>
      <Text>{props.title}</Text>
      <Text>{props.first}</Text>
      <Text>{props.last}</Text>
    </View>
  );
}
5
Hugo Dozois

Je pense toujours que la navigation par défaut est trop lourde. Je suggère fortement d'utiliser cette bibliothèque pour le routage: https://github.com/aksonov/react-native-router-flux

1
Shivam Sinha

Remplacer le code:

<TouchableOpacity
      onPress={() => this._handlePress()}
      style={styles.button}
>

Avec : 

<TouchableOpacity
      onPress={() => this._handlePress()}
      style={styles.button}
     navigator={navigator} {...route.props}
>

C'est bon:

this.props.navigator.Push({
component: ContactDetails,
   props: {
    title: rowData.name.title,
    first: rowData.name.first,
    last: rowData.name.last,
  }
});
1