react-navigation / rfcs

RFCs for changes to React Navigation
Other
88 stars 16 forks source link

Question about card layout design #53

Closed StasD closed 4 years ago

StasD commented 6 years ago

Hi,

The documentation is very scarce about some of the very fundamental aspects of inner workings of the library, e.g. screen components lifecycle: how screens are created, reused and destroyed during navigation.

But, looking at the source code, in particular Transitioner.js and ScenesReducer.js files, it looks like scenes/cards have 1:1 correspondence with routes, that is a new card is created each time a new route is added to the navigator by calling 'navigate' or 'push' methods.

I think this is not the optimal way of doing this. Instead, cards should have 1:1 correspondence with screen components used by the navigator, OR the combination of screen component and route key (in that case, multiple use of the same key should be allowed for the same screen, which would indicate the intent to re-use the card which corresponds to that key). Also when navigating between two routes which correspond to the same card, no animation should be applied during the transition. It should just call the (already existing) screen component with new parameters for the route you are navigating to.

One possible use case would be a hybrid native/web application, where one of the screens has a web view which would allow internal navigation inside the web view. You could track such navigation from inside the screen, add new routes with parameters (web url etc.) to the navigator, and when you press the "Back" button, the navigator would pass the parameters of the routes back to the screen, which would figure it out what it has to do. It could, for example, call .goBack() method of the web view.

With react-navigation, I think it is possible to implement the first part, that is to add routes without navigating to them (because it has been done already) using the technique described here, but I don't think it would be possible to do the rest...

StasD commented 6 years ago

I figured it out how to implement the functionality which I need using the current version of "react navigation". So, nevermind. :)

Here is what I did:

NavigationService.js:

import { NavigationActions, StackActions } from 'react-navigation';

let _navigator = null;
let _defaultGetStateForAction = null;

let _getStateForAction = (action, state) => {
  if (state && action.type === NavigationActions.BACK) {
    let _params = _getCurrentRoute(state).params;
    if (_params && _params.onBackAction && _params.onBackAction()) {
      return null; // action has been taken care of already
    }
  }

  return _defaultGetStateForAction(action, state);
};

function _getCurrentRoute(state) {
  while (state.routes) {
    state = state.routes[state.index];
  }

  return state;
}

function back() {
  _navigator.dispatch(
    NavigationActions.back({
      key: _getCurrentRoute(_navigator.state.nav).key,
    })
  );
}

function navigate(routeName, params = {}) {
  _navigator.dispatch(
    StackActions.push({
      routeName,
      params,
    })
  );
}

function navigateHome() {
  _navigator.dispatch(
    StackActions.popToTop()
  );
}

function setTopLevelNavigator(navigatorRef) {
  if (navigatorRef && _navigator === null) {
    _navigator = navigatorRef;
    let router = _navigator._navigation.router;
    _defaultGetStateForAction = router.getStateForAction;
    router.getStateForAction = _getStateForAction;
  }
}

export default {
  back,
  navigate,
  navigateHome,
  setTopLevelNavigator,
};

DetailsScreen.js:

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

import nav from '../utils/NavigationService';

export default class DetailsScreen extends Component {
  static navigationOptions = {
    title: 'Details',
  };

  state = {
    itemId: null
  };

  stateHistory = [];

  _onBackAction = () => {
    if (this.stateHistory.length === 0)
      return false;

    let _newState = this.stateHistory.pop();
    this.setState(_newState);

    return true;
  }

  _updateState = (_newState) => {
    this.stateHistory.push({...this.state});
    this.setState(_newState);
  }

  componentDidMount() {
    const { navigation } = this.props;

    navigation.setParams({
      onBackAction: this._onBackAction
    });

    const itemId = navigation.getParam('itemId', 0);

    this.setState({
      itemId
    });
  }

  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Details Screen</Text>
        <Text>itemId: {this.state.itemId}</Text>
        <Button
          title="Go to Home"
          onPress={() => nav.navigateHome()}
        />
        <Button
          title="Go to Details (again...)"
          onPress={() => nav.navigate('Details', {
              itemId: Math.floor(Math.random() * 100),
            })}
        />
        <Button
          title="Update itemId"
          onPress={() => this._updateState({
              itemId: Math.floor(Math.random() * 100),
            })}
        />
        <Button
          title="Go to Another"
          onPress={() => nav.navigate('Another')}
        />
        <Button
          title="Go back"
          onPress={() => nav.back()}
        />
      </View>
    );
  }
}
satya164 commented 4 years ago

Closing since this repo is for proposals, not questions.