1 | # Universal React App
|
2 |
|
3 | This template will set up a React project that renders in both the client and server with Node.js.
|
4 |
|
5 | ## Usage
|
6 |
|
7 | 1. In Bash, navigate into your project directory
|
8 | 2. Type `npx @optimistdigital/create-frontend --template=universal-react` to install the toolkit
|
9 | 3. Type `npm run dev` to start developing
|
10 |
|
11 | The 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 |
|
24 | Create-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 | */
|
36 | import { render } from '@optimistdigital/create-frontend/universal-react/client';
|
37 | render(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 | */
|
52 | import { render } from '@optimistdigital/create-frontend/universal-react/server';
|
53 | const {
|
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 |
|
61 | The `server/config.js` file contains your app configuration. You can define values here that will be accessed throughout your app.
|
62 | The configuration is available in React components (both server and client) through the **AppDataContext**:
|
63 |
|
64 | ```js
|
65 | import { AppDataContext } from '@optimistdigital/create-frontend/universal-react';
|
66 |
|
67 | function Header() {
|
68 | const { config } = React.useContext(AppDataContext);
|
69 | return <div>{config.APP_NAME}</div>;
|
70 | }
|
71 | ```
|
72 |
|
73 | Note 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 |
|
77 | The 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 |
|
79 | This 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 |
|
81 | The page data will be available in the AppDataContext.
|
82 |
|
83 | ```js
|
84 | import { AppDataContext } from '@optimistdigital/create-frontend/universal-react';
|
85 | import React from 'react';
|
86 |
|
87 | export default function App() {
|
88 | const { pageData } = React.useContext(AppDataContext);
|
89 | return <div>{pageData.url}</div>;
|
90 | }
|
91 |
|
92 | App.getPageData = async location => {
|
93 | // Fetch some data asynchronously here
|
94 | return prevState => ({
|
95 | ...prevState,
|
96 | url: location.pathname,
|
97 | });
|
98 | };
|
99 | ```
|
100 |
|
101 | With 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
|
104 | HomePage.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 |
|
115 | We have a wrapper around [React-Router](https://github.com/ReactTraining/react-router) that handles the
|
116 | client/server differences, and passes information about status codes and redirects to the server.
|
117 | To use it, wrap your application around the Router.
|
118 |
|
119 | ```js
|
120 | import { AppDataContext } from '@optimistdigital/create-frontend/universal-react';
|
121 | import Router from '@optimistdigital/create-frontend/universal-react/Router';
|
122 |
|
123 | export 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 |
|
130 | If 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
|
133 | import { AppDataContext } from '@optimistdigital/create-frontend/universal-react';
|
134 |
|
135 | export 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 |
|
143 | This content is then available in the `context` property after the server render:
|
144 |
|
145 | ```js
|
146 | import { render } from '@optimistdigital/create-frontend/universal-react/server';
|
147 |
|
148 | // Render the app
|
149 | const { content, context } = render(ReactComponent, request, config);
|
150 |
|
151 | // Read status from server context, with 200 as the default
|
152 | return res.status(context.status || 200).send(content);
|
153 | ```
|
154 |
|
155 | PS! The `serverContext` property is only available during the server render, so make sure you check for that.
|