React-native - Passing data from one screen to another
Asked Answered
W

3

9

I am trying to learn how to use react-native, so I am putting up a small app that fetch a list of users from a server, displays it in a listview and where pressing a row opens a screen displaying user's data.

I set up a navigator to go from one screen to another. When pressing on the row I am able to open a new screen but I can't figure how to pass data to this new screen.

I set up an navigator in 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);

First I display a screen with a button and an empty listview (ContactList.js), after pressing the button, I fetch some JSON data that are used to update the 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;
  }
}

When a row is pressed, it opens a new screen ContactDetails.js, which is supposed to display user's data :

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>
  );
}

At this point I got this error :

undefined is not an object (evaluating 'this.props.title')

I have tried many things such as :

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

or

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

or

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

But to no avail.

I also read that I should use redux. Am I doing something wrong ?

EDIT: The problem is here :

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

I assume I should pass some parameters there, but whatever I tried above failed.

Woodsman answered 29/7, 2016 at 15:50 Comment(2)
Possible duplicate of Pass Data between Pages in React nativeCrotchety
@Crotchety It's the other way around :). This question is almost two years older than the one you refer to.Woodsman
U
5

So the problem seems to lie here:

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

    return undefined;
  }}
/>

In the renderScene function, you do receive the route (and use route.component) but you don't pass your route.props or route.passProps or what ever you want to call it! And from what I see in the source of Navigator at that moment you should have the full route object as you created it. So you should be able to pass your props along.

For example:

<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: { /* ... */ }
    });
  }}
>

You could also setup redux, but that's not a requirement. Though if your app gets bigger you should really consider using an external store!


Update:

There is also another problem.

You use a functional components. Functional components don't have a this. They receive the props in parameter.

So it should be like this:

export default function ContactDetails(props) {
  return (
    <View style={styles.container}>
      <Text>{props.title}</Text>
      <Text>{props.first}</Text>
      <Text>{props.last}</Text>
    </View>
  );
}
Ushaushant answered 29/7, 2016 at 16:50 Comment(4)
Thanks, but that part I got working, the problem lies in another part, I added some precision in my first post.Woodsman
@Woodsman You actually need both. Your renderScene function currently doesn't pass the parameters along. See the example, I spread route.props to the component. So if you call it "props" in navigator.push you need to pass props in renderScene. If you call it passProps you should pass passProps.Ushaushant
@Woodsman see my update. I found another issue with your ContactDetails Component.Ushaushant
Thanks a lot, I just made it working using your corrections. Much appreciated, I have spent the better part of the afternoon on that :)Woodsman
N
1

I still feel the default navigation is to cumbersome. I highly suggest using this library for routing: https://github.com/aksonov/react-native-router-flux

Nonfulfillment answered 29/7, 2016 at 16:37 Comment(1)
Thanks for the suggestion, I will definitely try it, I hope to find a solution to my proble firstWoodsman
W
1

Replace code :

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

With :

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

This is fine:

this.props.navigator.push({
component: ContactDetails,
   props: {
    title: rowData.name.title,
    first: rowData.name.first,
    last: rowData.name.last,
  }
});
Wolves answered 9/6, 2017 at 11:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.