Code Monkey home page Code Monkey logo

reason-react-navigation-learn's Introduction

React Navigation Docs

https://reactnavigation.org/docs/en/hello-react-navigation.html

InitialRouteName on StackNavigator

There is no initialRouteName config on StackNavigator in reason. The initial screen is the first item in you array, here Home. Flip them around to see Details rendered first.

navigate doesnt go anywhere because you are already on this page. push in js, lets you indicate that you want to go to that screen anyway. I don't know if this is possible with Reason.

module DetailsScreen = {
  [@react.component]
  let make = (~navigation: Navigation.t) => {
    <Screen name="Details Screen"><Button
        title="Go to Details Screen Again"
        // js version: `onPress={() => this.props.navigation.push('Details')}`
        onPress={_ => navigation->Navigation.navigate("Details")}
      />
    </Screen>;
  };
};

Going back to previous screen in js:

<Button
  title="Go back"
  onPress={() => this.props.navigation.goBack()}
/>

Going back to previous screen in reason:

<Button
  title="Go back"
  onPress={_ => navigation->Navigation.goBack}
/>

popToTop take you to the top route in the current stack so this will go back to Home route.

<Button
  title="PopToTop"
  onPress={_ => navigation->Navigation.popToTop}
/>

This gif demonstrates how all screens remain mounted even when you navigate away. Notice that on the HomeStack we navigate to Details then we navigate to the Settings stack via the tabs at the bottom. When we navigate back to the HomeStack we are still on the Details screen in that stack since that is the screen we were on when we left it.

navigation-lifecycle

Passing Props / Json Serializing

Trying this to see if we can work towards json serializable values to use with deeplinking as mentioned here: https://reactnavigation.org/docs/en/params.html.

[@react.component]
  let make = (~navigation: Navigation.t) => {
    // trying this to see if we can work towards json serializable values to use with deeplinking as mentioned here: https://reactnavigation.org/docs/en/params.html
    let bucklescript = {
      "info": {
        itemId: 86,
        otherParam: "anything you want here",
      },
    };

    let param = bucklescript##info;
    Js.log2("TEST", Js.Json.test(param, Object)); // true
    <Screen name="Home Screen">
      <Button
        title="Go to Details"
        onPress={_ =>
          navigation->Navigation.navigateWithParams("Details", param)
          // {"itemId": 86, "otherParam": "anything you want here"},
        }
      />
    </Screen>;
 static navigationOptions = {
    title: 'Home',
  };
[@react.component]
  let make = (~navigation: Navigation.t) => {
    <Screen name="Home Screen">
      <Button
        title="Go to Details"
        onPress={_ =>
          navigation->Navigation.navigateWithParams("Details", {"itemId": 86, "otherParam": "anything you want here"})
        }>
    </Screen>;
  };
  make->NavigationOptions.setNavigationOptions(NavigationOptions.t(~title="Home", ()));
class DetailsScreen extends React.Component {
  static navigationOptions = ({ navigation }) => {
    return {
      title: navigation.getParam('otherParam', 'A Nested Details Screen'),
    };
  };

  /* render function, etc */
}

Use setDynamicNavigationOptions and pass in the params object. The title is on params##navigation, here we are you getParamsWithDefault to get the value otherParam and if not there, use the default.

[@react.component]
  let make = (~navigation: Navigation.t) => {
    <Screen name="Details Screen">
      /* rest of your component*/
    </Screen>;
  };

  make->NavigationOptions.setDynamicNavigationOptions(params => {
    let title = params##navigation->Navigation.getParamWithDefault("otherParam", "A Nested Details Screen");
    (NavigationOptions.t(~title, ()));
  });
        <Button
          title="Update the title"
          onPress={() =>
            this.props.navigation.setParams({ otherParam: 'Updated!' })}
        />

The reason-react-navigation bindings haven't implemented setParams yet I had to spin some up to ge this demo done. If you look at the js version above, it calls setParams which takes a json object with a key and a value. In keeping with how the bindings are written, we can reproduce the passed in value using bucklescripts object creation method. That is what I did below in the reason example. I messed with it for a bit, unsuccessfully, before I jumped into the reason-discord channel and asked the question. John Jackson the author of the ReasonML chess game coronate chimes in wiht the link to the bucklescript docs. This worked immediately. The problem now is getting this one function to work with the reason-react-navigation bindings we are using for all these demos. I ended up creating a module NavUtils.re with two lines of code. An include reference to the ReactNavigation.Navigation module which copies every thing from that module to the current one. So in the end, we have all of ReactNavigation.Navigation plus our new setParams function. You can apparently, now use either NavUtils or ReactNavigation.Navigation interchangeably.

include ReactNavigation.Navigation;
[@bs.send] external setParams: (t, Js.t({..})) => unit = "setParams";
[@react.component]
  let make = (~navigation: Navigation.t) => {
    // create a `bs.obj` that we will pass to `setParams`
    // typing is Js.t(< otherParam : string >) which matches the type we wrote in our `NavUtils` module, which was `Js.t({..})`. When you see the doulbe dots, tell yourself, that is the type for the `Bucklescript interop object`
    let params = {"otherParam": "Updated!"};

    <Screen name="Details Screen">
      <Button
        title="Update the title"
        onPress={_ =>
          navigation->NavUtils.setParams(params)
        }
      />
      /* rest of your component*/
    </Screen>;
  };

  make->NavigationOptions.setDynamicNavigationOptions(params => {
    let title = params##navigation->Navigation.getParamWithDefault("otherParam", "A Nested Details Screen");
    (NavigationOptions.t(~title, ()));
  });

From the docs:

There are three key properties to use when customizing the style of your > header: headerStyle, headerTintColor, and headerTitleStyle.

headerStyle: a style object that will be applied to the View that wraps the header. If you set backgroundColor on it, that will be the color of your header.

headerTintColor: the back button and title both use this property as their color. In the example below, we set the tint color to white (#fff) so the back button and the header title would be white.

headerTitleStyle: if we want to customize the fontFamily, fontWeight and other Text style properties for the title, we can use this to do it.

class HomeScreen extends React.Component {
  static navigationOptions = {
    title: 'Home',
    headerStyle: {
      backgroundColor: '#f4511e',
    },
    headerTintColor: '#fff',
    headerTitleStyle: {
      fontWeight: 'bold',
    },
  };

  /* render function, etc */
}

Using ReasonML:

[@react.component]
  let make = (~navigation: Navigation.t) => {
    <View
      style=Style.(
        style(~flex=1., ~alignItems=`center, ~justifyContent=`center, ())
      )>
      <Text> {"Home Screen" |> React.string} </Text>
      /* rest of your component*/
    </View>;
  };

    make->NavigationOptions.setNavigationOptions(
    NavigationOptions.t(
      ~headerTitleStyle=Style.(style(~fontWeight=`bold, ())),
      ~headerTintColor="#fff",
      ~headerStyle=Style.(style(~backgroundColor="#f4511e", ())),
      ~title="Home",
      (),
    ),
  );
  // alternatively, define the prop and pass it to `NavigationOptions`
  // let headerStyle = Style.(style(~backgroundColor="#f4511e", ()));
  // let headerTitleStyle = Style.(style(~fontWeight=`bold, ()));
  // let headerTintColor = "#fff";
  // make->NavigationOptions.setNavigationOptions(
  //   NavigationOptions.t(
  //     ~headerTitleStyle,
  //     ~headerTintColor,
  //     ~headerStyle,
  //     ~title="Home",
  //     (),
  //   ),
  // );

From the docs:

There are three key properties to use when customizing the style of your > header: headerStyle, headerTintColor, and headerTitleStyle.

headerStyle: a style object that will be applied to the View that wraps the header. If you set backgroundColor on it, that will be the color of your header.

headerTintColor: the back button and title both use this property as their color. In the example below, we set the tint color to white (#fff) so the back button and the header title would be white.

headerTitleStyle: if we want to customize the fontFamily, fontWeight and other Text style properties for the title, we can use this to do it.

class HomeScreen extends React.Component {
  static navigationOptions = {
    title: 'Home',
    headerStyle: {
      backgroundColor: '#f4511e',
    },
    headerTintColor: '#fff',
    headerTitleStyle: {
      fontWeight: 'bold',
    },
  };

  /* render function, etc */
}

Using ReasonML I had to do a few things to get this to work. The reason-react-navigation bindings don't have initialRouteName and defaultNavigationOptions so I created StackUtils.re which copies over all of ReactNavigation.StackNavigator and overides the definition of config so that it has just the properties from react-navigation that I need to reproduce the example. It looks like this:

// include this file: https://github.com/reasonml-community/reason-react-native/blob/53dc7f9ebf6ae72fb88c2cfe957dc45b993265fa/reason-react-navigation/src/StackNavigator.re then override `config` definition
include ReactNavigation.StackNavigator;

[@bs.obj]
external config:
  (
    ~initialRouteName: string=?,
    ~defaultNavigationOptions: Js.t('a)=?,
    unit
  ) =>
  config =
  "";

Then I removed the NavigatorOptions calls from the HomeScreen and DetailsScreen modules and defined them in the routes object in AppContainer. I then use StackUtils to define our config object that we pass to StackNavigator.makeWithConfig();

module AppContainer =
  AppContainer.Make({
    type screenProps = {. "someProp": int};

    let routes = {
      "Home": {
        screen: HomeScreen.make,
        navigationOptions: () => {
          title: "Home",
        },
      },
      "Details": {
        screen: DetailsScreen.make,
        navigationOptions: (params: {. navigation: Navigation.t}) => {
          let navigation = params##navigation;
          let title =
            navigation->Navigation.getParamWithDefault(
              "otherParam",
              "A Nested Details Screen",
            );
            // here we have to do `title:title` or change the name of `let title` identifier because the compiler will pun it away to `title` instead of `{title}` which compiles it away.
            {
              title:title,
            };
        },
      },
    };
    // create a `Js.t('a)` object containing the values we want to use as `defaultNavigationOptions`.
    let configFromHomeScreen = {
      "headerStyle": Style.(style(~backgroundColor="#f4511e", ())),
      "headerTintColor": "#fff",
      "headerTitleStyle": Style.(style(~fontWeight=`bold, ())),
    };
    // define our `config` object passing in `configFromHomeScreen` to `~defaultNavigationOptions`
    let configOptions = StackUtils.config(
        ~initialRouteName="Home",
        /* The header config from HomeScreen is now here */
        ~defaultNavigationOptions=configFromHomeScreen,
        (),
      );
    let navigator = StackNavigator.(makeWithConfig(routes, configOptions));
  });

This is what it looks like:

sharing-options

const Home = createStackNavigator(
  {
    Feed: ExampleScreen,
    Profile: ExampleScreen,
  },
  {
    defaultNavigationOptions: {
      title: 'Home',
      headerTintColor: '#fff',
      headerStyle: {
        backgroundColor: '#000',
      },
    },
    navigationOptions: {
      tabBarLabel: 'Home!',
    },
  }
);

Define the routes seperately for readability

  let routes = {
    "Feed": {
      screen: ExampleScreen.make,
    },
    "Profile": {
      screen: ExampleScreen.make,
    },
  };

Define the defaultNavigationOptions seperately for readability

  let defaultNavigationOptions = {
    "title": "Home",
    "headerTintColor": "#fff",
    "headerStyle": Style.(style(~backgroundColor="#000", ())),
  };

Use StackUtils version of StackNavigator.config which has the defaultNavigationOptions option available on it.

  let configOptions =
    StackUtils.config(
      ~defaultNavigationOptions,
      (),
    );

Generate Config

Notes: tabBarLabel is represented in the binding as title as far as I can tell. There is a TabBarLabel module but I can not get it to pass in this format yet. Instead, I used the setNavigationOptions method from the bindings found here: https://github.com/reasonml-community/reason-react-native/blob/21a72c601c12adaf09ebd0cd55d57ecb59a650f8/reason-react-navigation/src/NavigationOptions.re#L165 which is having the desired effect apparently.

Use StackUtils version of StackNavigator.config which has the defaultNavigationOptions option available on it.

  let configOptions =
    StackUtils.config(
      ~defaultNavigationOptions,
      (),
    );

Create the navigator

Pass the routes and config to our StackNavigator

  let navigator = StackNavigator.(makeWithConfig(routes, configOptions));

Use setNavigationOptions from bindings

Use setNavigationOptions to create the title per the demo. See notes above on tabBarLabel. The title property here overrides the tabBarLabel title, not the title we set in defaultNavigationOptions.

  navigator->NavigationOptions.setNavigationOptions(
    NavigationOptions.t(~title="Home!", ()),
  );

All together:

module Home = {
  // define the routes seperately for readability
  let routes = {
    "Feed": {
      screen: ExampleScreen.make,
    },
    "Profile": {
      screen: ExampleScreen.make,
    },
  };

    // define the defaultNavigationOptions seperately for readability

  let defaultNavigationOptions = {
    "title": "Home",
    "headerTintColor": "#fff",
    "headerStyle": Style.(style(~backgroundColor="#000", ())),
  };
  // `tabBarLabel` is represented in the binding as `title` as far as I can tell. There is a `TabBarLabel` module  but I can not get it to pass in this format yet. Instead, I used the `setNavigationOptions` method from the bindings found here: https://github.com/reasonml-community/reason-react-native/blob/21a72c601c12adaf09ebd0cd55d57ecb59a650f8/reason-react-navigation/src/NavigationOptions.re#L165 which is having the desired effect apparently.

  // use StackUtils version of `StackNavigator.config` which has the `defaultNavigationOptions` option available on it.
  let configOptions =
    StackUtils.config(
      ~defaultNavigationOptions,
      (),
    );

  // pass the `routes` and `config` to our `StackNavigator`
  let navigator = StackNavigator.(makeWithConfig(routes, configOptions));
  // use `setNavigationOptions` to create the title per the demo. See notes above on `tabBarLabel`. The `title` property here overrides the `tabBarLabel` title, not the `title` we set in `defaultNavigationOptions`.
  navigator->NavigationOptions.setNavigationOptions(
    NavigationOptions.t(~title="Home!", ()),
  );
};

This is what it looks like:

overriding-options

The navigationOptions specified on your screen component are merged together with the default navigation options of its parent stack navigator, with the options on the screen component taking precedence. Let's use this knowledge to invert the background and tint colors on the details screen.

class DetailsScreen extends React.Component {
  static navigationOptions = ({ navigation, navigationOptions }) => {
    const { params } = navigation.state;

    return {
      title: params ? params.otherParam : 'A Nested Details Screen',
      /* These values are used instead of the shared configuration! */
      headerStyle: {
        backgroundColor: navigationOptions.headerTintColor,
      },
      headerTintColor: navigationOptions.headerStyle.backgroundColor,
    };
  };

  /* render function, etc */
}

We do most of the work in the DetailsScreen module in the setDynamicNavigationOptions function.

This section calls for us to take some of that style values and reverse them. We have access to the values on the NavigationOptions param that we can access in setDynamicNavigationOptions as seen below.

The problem I ran up against was the values come in typed as ReactNavigation.NavigationOptions.t but to use them in creating new ReactNative.Style values we have to convert them to something we can use. Here that is Js.t. So at the top of the file, you see what is referred to in the bucklescript docs as identity external and in the reason-docs:

external unsafeCast : ReactNavigation.NavigationOptions.t => Js.t('a) = "%identity";

Once we have a useable type, we moved the values around to get the desired effect. Read the inline notes below to see how it happened.

make->NavigationOptions.setDynamicNavigationOptions(params => {
    let navigation = params##navigation;
    let navigationOptions = params##navigationOptions;
    Js.log2("navigationOptions: ", navigationOptions);
    // log output of incoming values:
    // navigationOptions Object {
    //   "headerStyle": Object {
    //     "backgroundColor": "#f4511e",
    //   },
    //   "headerTintColor": "#fff",
    //   "headerTitleStyle": Object {
    //     "fontWeight": "bold",
    //   },
    // }
    // identity type switch at top of file: `external toJst: ReactNavigation.NavigationOptions.t => 'a = "%identity";` converts from one type to `'a`:any type which we can then use later to switch colors. This cant be right but it works.
    // have to do identity hack because:
    // ```
    // ReactNavigation.NavigationOptions.t
    // type t
    // <root>/src/OverrideSharedOptionsDemo.re

    // Error: This expression has type
    //          ReactNavigation.NavigationOptions.t =
    //            ReactNavigation.NavigationOptions.t
    //        but an expression was expected of type Js.t('a
    // ```
    // comment out this line to see the error generated
    let navigationOptions = unsafeCast(navigationOptions);
    // incoming headerTintColor to be passed to `newHeaderStyle`
    let navigationOptionsheaderTintColor = navigationOptions##headerTintColor;
    // incoming headerStyle.backgroundColor to be passed to `newHeaderTintColor`
    let navigationOptionsheaderStylebackgroundColor =
      navigationOptions##headerStyle##backgroundColor;
    // this should be the same as the old headerStyle background color
    let newHeaderTintColor = navigationOptionsheaderStylebackgroundColor;
    // this should be the same as the old headerTintColor
    let newHeaderStyle: ReactNative.Style.t =
      Style.(style(~backgroundColor=navigationOptionsheaderTintColor, ()));

    let title =
      navigation->Navigation.getParamWithDefault(
        "otherParam",
        "A Nested Details Screen",
      );
    // pass new values to NavigationOptions
    NavigationOptions.t(
      ~title,
      ~headerStyle=newHeaderStyle,
      ~headerTintColor=newHeaderTintColor,
      (),
    );
  });

This is what it looks like:

overriding-shared-options

class LogoTitle extends React.Component {
  render() {
    return (
      <Image
        source={require('./spiro.png')}
        style={{ width: 30, height: 30 }}
      />
    );
  }
}

class HomeScreen extends React.Component {
  static navigationOptions = {
    // headerTitle instead of title
    headerTitle: <LogoTitle />,
  };

  /* render function, etc */
}

ReasonML Version:

module LogoTitle = {
  [@react.component]
  let make = () => {
    <Image
      source=Image.Source.fromRequired(Packager.require("./assets/spiro.png"))
      style=Style.(style(~width=30.->dp, ~height=30.->dp, ()))
    />;
  };
};

Then in HomeScreen module:

  make->NavigationOptions.(setNavigationOptions(t(
    // ~title="Home",
    // headerTitle instead of title
      ~headerTitle=NavigationOptions.HeaderTitle.element(<LogoTitle />),
     ())));

This is what it looks like:

custom-header-title

class HomeScreen extends React.Component {
  static navigationOptions = {
    headerTitle: <LogoTitle />,
    headerRight: (
      <Button
        onPress={() => alert('This is a button!')}
        title="Info"
        color="#fff"
      />
    ),
  };
}

ReasonML Version:

In HomeScreen module:

  make->NavigationOptions.(setNavigationOptions(t(
    // ~title="Home",
    // headerTitle instead of title
      ~headerTitle=NavigationOptions.HeaderTitle.element(<LogoTitle />),
      ~headerRight=<Button title="Info"
        color="#fff" onPress={_e =>
          Alert.alert(~title="This is a button!", ());
        }
      />,
     ())));

This is what it looks like:

adding-header-button

reason-react-navigation-learn's People

Contributors

idkjs avatar

Stargazers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.