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:
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:
- https://vasanthk.gitbooks.io/react-bits/
- https://krasimir.gitbooks.io/react-in-patterns/content/
- https://survivejs.com/react/introduction/
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:
- https://reacttraining.com/react-router/web/guides/philosophy
- https://reacttraining.com/react-router/web/guides/basic-components
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 />:
- https://reacttraining.com/react-router/web/api/Route
- https://reacttraining.com/react-router/core/api/Link
- https://reacttraining.com/react-router/web/api/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:
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 themobx-reactpackage)@computed@actionreactionandautorun
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:
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
- Redux: https://redux.js.org/
- Handling side effects like API calls: https://medium.com/magnetis-backstage/redux-side-effects-and-me-89c104a4b149
- egghead.io tutorial by the creator of Redux: https://egghead.io/courses/getting-started-with-redux
Babel
- Simple setup: http://ccoenraets.github.io/es6-tutorial-data/babel-webpack/
- More documentation: https://babeljs.io/docs/en/index.html
Helpful libraries
Here you will find a list of interesting libraries that can help you with working with react:
- recompose: Higher order component toolbelt (like lodash for HOCs): https://github.com/acdlite/recompose
- react-testing-library: For testing your components: https://github.com/kentcdodds/react-testing-library
- enzyme: Similar to react-testing-library with a different concept (and more powerful): https://github.com/airbnb/enzyme
- mobx-react-form: Form abstraction layer: https://github.com/foxhound87/mobx-react-form
- react-intl: Format numbers and dates with the browser intl API: https://github.com/yahoo/react-intl
- jest: Facebook's test runner and testing framework: https://github.com/facebook/jest