React

The react and portal plugin meta guide

We strongly suggest that you should use React to write a new restaurant portal plugin. To make the process of learning react as painless as possible we collected some tutorial and guides to learn React from scratch. If you have learned React before or find some additional interesting material to learn React, please contact us so we can improve this introduction.

The react ecosystem gives you a lot of freedom! This can be overwhelming. Later in this guide we will show you an easy tech stack for a react app, that makes it easy to get into, when you come from an OOD world. There are more choices, but we think you should focus on this stack, because it is easy to use and understand. We want to provide you a framework of existing libraries that work well together and are easy to get started with. We use that stack in our own plugins as well, so it is well tested.

If you want to make your own decisions anyway, at the bottom of this guide there is a small list of resources of alternatives and learning resources for them.

Learning ES6

React takes advantage of ES6, a set of new features and syntax for JavaScript that modern browsers can/will use. Although there are still some legacy browsers in use, with tools like babel and typescript you can use these new shiny things already. These tools will transform your ES6 style code to code that ES5 compatible browsers can understand.

If you haven't been working with ES6 before, you should definitely look into this article:

https://medium.com/the-react-native-log/a-brief-overview-of-es6-for-react-native-developers-15e7c68315da

Although it has react-native in the title, everything is applicable for react as well. You should definitely look into modules and arrow functions here, because these will be used in react a lot.

What is also important for react are classes, that were also introduced with ES6:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes

Setting up a simple dev environment

We have already a simple plugin boilerplate created for you: The "Hello World" plugin. This has everything you need to start coding almost immediately.

You can find the boilerplate repo here: https://github.com/deliveryhero/rps-portal-plugin-hello-world

A short guide to start the runtime you can find in this parent section

If you still want know how to set this up yourself, here is a video that describes how to set up TypeScript together with webpack: https://www.youtube.com/watch?v=ZyoVZ6cLvpI

React, the basics

But first we will go with the basics: plain react without the other parts of the tech stack. The API of react is really simple and you can learn all the concepts in one day and can already start hacking.

The react documentation has a nice introduction into react and JSX(the preferred way to write your components).

https://reactjs.org/docs/hello-world.html

The "Main Concepts" section describes the most important concepts really well. We strongly recommend to read everything through and try to write the examples yourself. The last section "Thinking in React" is optional, but gives good insights to get a better feeling what is important when writing a react app.

From the advanced guide "Higher-Order Components" is the most important one, because you can find this concepts everywhere in the react ecosystem, also in the portal. For your first steps in the react world this is not so important though.

React itself is already really powerful. If you don't have complex state you might even consider to not use a state management library like mobx or redux and just use the component internal setState(). It's enough to keep you going for quite some use cases already.

Further reads:

Routing

For routing there is one library that you should use for your portal plugin: react-router (and react-router-dom for it's web implementation).

It is a component based router that makes it really easy to define routes dynamically (even dynamically during runtime).

The "philosophy" page describes how react-router works really quick:

To sum it up a little: Every route is a <Route /> component, that contains the route to react to and a component prop that contains the component that will be displayed, when the route matches. With the <Link /> component you can navigate through the (basically the <a/> tag for app internal navigation).

The components you will use the most are <Route/>, <Link /> and <Redirect />:

You will also most definitely need to get and set URL parameters:

https://reacttraining.com/react-router/web/example/url-params

Routing in the portal

In the portal you can use routing almost as if it would be a standalone app. There only needs to be one thing added to the wrapping <BrowserRouter />. The browser router gives the routes inside of it context about the routing environment. It can take arguments to configure it. That's what we need to do:

import { getBaseRoute } from '@deliveryhero/vendor-portal-sdk';

<BrowserRouter basename={getBaseRoute()}>
  <Route path="/test/route"></Route>
</BrowserRouter>

By using basename we will prepend the plugin base route received by getBaseRoute() to every URL inside, so you don't need to do this yourself in every sub page. This will then be applied to <Route />, <Link/> and <Redirect />. You only need navigate inside your plugin context.

So let's say your plugin base route is /hello_world. Then the route in the example above would actually react to /hello_world/test/route.

If you would use a link like this:

<Link to="/test/route">Go to test route</Link>

You would actually navigate to /hello_world/test/route. So you don't need to care about what your actual plugin route is. Also when you want to change it, there is no need to update your plugin code.

State management

If your app grows bigger there is a need to have more complex state management. We recommend to use mobx, as this library utilizes more classical OOD concepts and needs less boilerplate. There is also the bigger competitor redux, but that needs some knowledge of functional programming and immutability. If you want to go that path, the redux documentation is really good.

But our focus will be mobx! It uses reactive concepts, so it is full of observables that gives interested parties the option to react on changes of these values. This is even easier for react, as there is the mobx-react package, that will re-render the react view, when there is a change in observables that are used in the render() method.

Again the mobx documentation is a good place to start:

https://mobx.js.org/

There is an egghead.io course from it's creator that gives some introduction as well:

https://egghead.io/courses/manage-complex-state-in-react-apps-with-mobx

The most important parts of mobx are:

  • @observable
  • @observer (part of the mobx-react package)
  • @computed
  • @action
  • reaction and autorun

So you should focus on this when looking into the documentation.

Dependency injection

Dependency injection makes it really easy to test your code independently from functionality from other building blocks your code uses.

The mobx library has no build in concept for dependency injection. But it works well together with inversify, a dependency injection library build for TypeScript.

The documentation for this you will find directly in it's GitHub repo:

https://github.com/inversify/InversifyJS

To make your mobx stores available as a dependency, you only need to decorate it as @injectable() and add it to a dependency container.

You should make the dependencies available as singletons, then the same state is available for all components and stores: The easiest way to do this is by setting the scope to singleton for all dependencies in a container:

export const container = new Container({ defaultScope: 'Singleton' });

In the documentation you should also have a look at the @postConstruct() decorator, where you can do initialization logic (to get this out of the constructor, for better unit testing).

Make it work with react

inversify works well within it's boundaries, but when it comes to react, there is a problem. React already occupies the class constructor to pass down props. So you can't use the inversify way of passing dependencies.

That's why we build a small library that helps with passing dependencies to react components:

https://github.com/deliveryhero/rps-react-inversify-provider

Basically this is a higher order component that will pass selected dependencies as props to the component it wraps. You can find the documentation for it also in the repo above (still TODO). Its interface is really simple, so take a look.

Material UI and styling

All of our designs are based on material-ui. It is a library that implements the Material design system from google in react. We build a collection of ready to use components, that we describe in the next section.

On their website they have demos and API documentation for all their components:

https://material-ui.com/

In the portal we use the theme functionality, so you don't need to care about the colors. They will automatically fit perfectly in our design, when using the material-ui components.

When using material-ui, please keep in mind, that you need to import the components from the main entry point of the library, instead directly from the file that only describes the file. This might sound counter intuitive, because you would normally do this, to leverage tree shaking. But we make the complete library available for you as a external dependency (configured in the webpack config). This way we only need to load the library once and it can be used in all plugins without them to include the library in their bundles. When you import from the files directly you will include this file in your bundle instead of using the already loaded library.

Here is an example how to import the file with the (bad example at the top):

// YOU SHOULD NOT DO IT THIS WAY!!!
import Card from '@material-ui/core/Card';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';

// But do it this way
import { Card, CardActions, CardContent } from '@material-ui/core';

There is one exception though: Importing prop types (as they are not included in the main export and also not included in the bundle).

import { Card, CardActions, CardContent } from '@material-ui/core';
import { CardProps } from '@material-ui/core/Card';

Customizing the components and styling

You can customize the material-ui components you use. Here is a short description of how you do it:

https://material-ui.com/customization/overrides/

When customizing, please use the theme values for colors, spacing, shadows etc. Only when using these values you guarantee a smooth experience and your components will automatically adapt, when there are changes in the overall design.

Also for all of your texts and typography you should use the <Typography /> component, that material-ui provides you with:

https://material-ui.com/style/typography/

Styling the component way

In all of our plugins and also in the portal frame we use the styled-components library. It is a library that makes you write code closer to CSS, but leverage the power and your environment of JavaScript.

You can read into it here:

https://www.styled-components.com/

material-ui works somehow well with this library, but needs some small adjustments that create a lot of boilerplate.

If you want to use styled-components together with material-ui we recommend to use this library, that we created:

https://github.com/deliveryhero/rps-styled-material-ui

You don't need to care about the theme providers, because the portal frame already does that for you. With this you also have access to the material-ui theme within styled-components.

Using the portal plugin SDK with react

Now we get to the interesting part! In this section we will show you some recipes how to communicate with the portal and how to handle the SDK functions.

Using dependency injection with the SDK functions

This is a common use case. We strongly recommend you to do this, because it makes testing code that uses the SDK functions way easier.

Here is how you do it:

First you bind the functions as constant values to the inversify container.

import { 
  getCurrentRestaurant,
  setNewNavigation,
  pushGtmEvent,
  getUserData,
  getBaseRoute,
  t
} from '@deliveryhero/vendor-portal-sdk';

export const container = new Container({ defaultScope: 'Singleton' });

container.bind<Function>('getCurrentRestaurant').toConstantValue(getCurrentRestaurant);
container.bind<Function>('pushGtmEvent').toConstantValue(pushGtmEvent);
container.bind<Function>('getUserData').toConstantValue(getUserData);
container.bind<Function>('setNewNavigation').toConstantValue(setNewNavigation);
container.bind<Function>('getBaseRoute').toConstantValue(getBaseRoute);
container.bind<Function>('t').toConstantValue(t);

After that you can use these things either in a mobx store:

@injectable()
export default class MyRestaurantStore {
  @observable currentRestaurant;
  constructor(@inject('getCurrentRestaurant') private getCurrentRestaurant) {}

  @postConstruct() init() {
    this.currentRestaurant = this.getCurrentRestaurant();
    // ...
  }
}

Or you can use it in react:

@observer
export class MyRestaurantComponent extends React.Component {
  render() {
    const currentRestaurant = this.props.getCurrentRestaurant();
    const translatedWelcomeMessage = this.props.t(
      'hello_world.welcome',
      { restaurantName: currentRestaurant.name }
    );
    return <p>{translatedWelcomeMessage}</p>;
  }
}

// withDependencies is imported from the @deliveryhero/react-inversify-provider package
export default withDependencies({
  getCurrentRestaurant: 'getCurrentRestaurant',
  t: 't',
})(MyRestaurantComponent);

Setting the Appbar title and change navigation elements

The ideal place to change the title in the black Appbar is the componentDidMount() hook of your container components.

This example will set the navigation for the top level component (with bottom navigation and no back arrow in the top):

export class MainContainer extends React.Component {
  componentDidMount() {
    this.props.setNewNavigation({
      name: "Hello World main page",
      title: this.props.t("hello_world.title"), // Already translated field
    });
  }
}

// withDependencies is imported from the @deliveryhero/react-inversify-provider package
export default withDependencies({
  setNewNavigation: 'setNewNavigation',
  t: 't',
})(MyRestaurantComponent);

This example uses the SDK dependency injection example from above.

And if you have some component that is deeper within your app, you should set it like this:

export class SomeContentContainer extends React.Component {
  componentDidMount() {
    this.props.setNewNavigation({
      name: "Deeper page",
      title: this.props.t("hello_world.deeper_page_title"), // Already translated field
      hasBackButton: true,
      backUrl: "/",
      showAppBarPlugins: false,
      showBottomNav: false,
    });
  }
}

This will:

  • replace the menu icon with a back arrow (that navigates to the backUrl which will be prepended with the baseRoute),
  • the app bar plugins on the top right are not displayed anymore, so you have more space in the top,
  • and the bottom navigation is hidden

Using Translations inside components

To make it easier to use translations within components it is helpful to create a <Translate /> component.

const Translate = ({t, code, params}) => <React.Fragment>{t(code, params)}</React.Fragment>;
export default withDependencies({
  t: 't',
})(Translate);

Then you can use the component like this:

<Translate code="hello_world.some.translated.text" params={{ name: "John" }} />

Using your API with our token

We have a small little API library that handles API fetch requests and automatically adds authorization headers to all requests. Also it handles triggering token refresh, when the auth token expired and queueing every request that comes in during the refresh time and sending this requests again when the token was refreshed.

You can find the library here:

https://github.com/deliveryhero/rps-react-api

Because it needs some boilerplate on your side to set up adding the auth header and refresh token handling we made a function in the SDK, that gives you back an already created instance, that already does all that for you. You only need to make the fetch calls.

import { createGlobalApi } from '@deliveryhero/vendor-portal-sdk';
import { Api } from '@deliveryhero/portal-api-client';
import { Container } from 'inversify';

const apiInstance = createGlobalApi();

export const container = new Container({ defaultScope: 'Singleton' });

container.bind<Api>(Api).toConstantValue(apiInstance);

Then you can use the Api instance directly with the inversify dependency injection. You should use this instance in the store to trigger the API.

You can find more information about this in the SDK documentation at the bottom.

The other paths

To keep things short here you will only find a collection of useful links:

Redux

Babel

Helpful libraries

Here you will find a list of interesting libraries that can help you with working with react:

results matching ""

    No results matching ""