UNPKG

5.78 kBMarkdownView Raw
1# Universal React App
2
3This template will set up a React project that renders in both the client and server with Node.js.
4
5## Usage
6
71. In Bash, navigate into your project directory
82. Type `npx @optimistdigital/create-frontend --template=universal-react` to install the toolkit
93. Type `npm run dev` to start developing
10
11The configuration and commands are the same as with the default template, but there are some additional options:
12
13**Additional CLI scripts:**
14
15- `npm run start` - Starts the production server
16
17**Additional configuration options:**
18
19- `serverEntryPoint` (_server/entry.js_) - Entry point for your server.
20- `serverBuildPath` (_build/server_) - Where the compiled server will be built. Relative to project root.
21
22## Rendering
23
24Create-Frontend exposes functions that take care of rendering the React app in the server and client:
25
26```js
27/**
28 * Client render - renders your react app to the DOM
29 *
30 * @param ReactComponent - The root component of your React app
31 * @param domNode - DOM node that the React app should be rendered to.
32 * @param props (optional) - This will get passed to the App component during render, and as the 2nd argument to getPageData.
33 * You will have to ensure that passing different props on server/client won't result in a different HTML,
34 * Otherwise you will get content mismatch errors during hydration.
35 */
36import { render } from '@optimistdigital/create-frontend/universal-react/client';
37render(ReactComponent, domNode, props);
38```
39
40```js
41/**
42 * Server render - renders your react app to string
43 *
44 * @param ReactComponent - The root component of your React app
45 * @param url - The url for the request. For express, you should pass `req.originalUrl`
46 * @param props (optional) - This will get passed to the App component during render, and as the 2nd argument to getPageData.
47 * You will have to ensure that passing different props on server/client won't result in a different HTML,
48 * Otherwise you will get content mismatch errors during hydration.
49 *
50 * @return {{ content: String, context: Object }}
51 */
52import { render } from '@optimistdigital/create-frontend/universal-react/server';
53const {
54 content, // App rendered to string
55 context, // Server-side context, for passing data to from the React app to the server
56} = render(ReactComponent, url, backendData);
57```
58
59## Configuration
60
61The `server/config.js` file contains your app configuration. You can define values here that will be accessed throughout your app.
62The configuration is available in React components (both server and client) through the **AppDataContext**:
63
64```js
65import { AppDataContext } from '@optimistdigital/create-frontend/universal-react';
66
67function Header() {
68 const { config } = React.useContext(AppDataContext);
69 return <div>{config.APP_NAME}</div>;
70}
71```
72
73Note that this example uses the hooks API, but other [context API's](https://reactjs.org/docs/context.html#api) work as well.
74
75## Page based data fetching
76
77The top level App component can have an async function called `getPageData`, which is called once in the server, and in the client whenever the page changes.
78
79This function gets the page's location as an argument, and returns an updater function. The updater function gets the previous state and should return the new state.
80
81The page data will be available in the AppDataContext.
82
83```js
84import { AppDataContext } from '@optimistdigital/create-frontend/universal-react';
85import React from 'react';
86
87export default function App() {
88 const { pageData } = React.useContext(AppDataContext);
89 return <div>{pageData.url}</div>;
90}
91
92App.getPageData = async location => {
93 // Fetch some data asynchronously here
94 return prevState => ({
95 ...prevState,
96 url: location.pathname,
97 });
98};
99```
100
101With the default boilerplate, this can also be used on route components. In this case the function also receives the URL params as a second argument:
102
103```js
104HomePage.getPageData = async (location, params) => {
105 // Fetch some data for the homepage here
106 return prevState => ({
107 ...prevState,
108 homePageData: {},
109 });
110};
111```
112
113## React-Router
114
115We have a wrapper around [React-Router](https://github.com/ReactTraining/react-router) that handles the
116client/server differences, and passes information about status codes and redirects to the server.
117To use it, wrap your application around the Router.
118
119```js
120import { AppDataContext } from '@optimistdigital/create-frontend/universal-react';
121import Router from '@optimistdigital/create-frontend/universal-react/Router';
122
123export default function App() {
124 return <Router>Your app content goes here</Router>;
125}
126```
127
128## Passing data from React app to server (such as http status code)
129
130If you're using a router, you probably want to let the server know when a 404 page was rendered to set the correct status code. This can be done by mutating the `serverContext` property found in the AppDataContext:
131
132```js
133import { AppDataContext } from '@optimistdigital/create-frontend/universal-react';
134
135export default function NotFoundPage() {
136 const appData = React.useContext(AppDataContext);
137 if (appData.serverContext) appData.serverContext.status = 404;
138
139 return <div>Page not found!</div>;
140}
141```
142
143This content is then available in the `context` property after the server render:
144
145```js
146import { render } from '@optimistdigital/create-frontend/universal-react/server';
147
148// Render the app
149const { content, context } = render(ReactComponent, request, config);
150
151// Read status from server context, with 200 as the default
152return res.status(context.status || 200).send(content);
153```
154
155PS! The `serverContext` property is only available during the server render, so make sure you check for that.