Skip to content

Commit 8380d0f

Browse files
author
Turbo
committed
Preload data for Posts and Books, fix language
1 parent 9d5879f commit 8380d0f

19 files changed

+153
-97
lines changed

package.json

-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
"@babel/preset-react": "^7.0.0",
2727
"@babel/register": "^7.0.0",
2828
"@babel/runtime": "^7.2.0",
29-
"accepts": "^1.3.5",
3029
"babel-loader": "^8.0.4",
3130
"babel-plugin-inline-dotenv": "^1.2.0",
3231
"babel-plugin-module-resolver": "^3.1.1",

src/App/AppWrapper.js

+1-5
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,8 @@ const AppWrapper = ({ lang }) => (
1515
</ThemeProvider>
1616
);
1717

18-
AppWrapper.defaultProps = {
19-
lang: 'en',
20-
};
21-
2218
AppWrapper.propTypes = {
23-
lang: PropTypes.string,
19+
lang: PropTypes.string.isRequired,
2420
};
2521

2622
export default AppWrapper;

src/App/AppRoot.js renamed to src/App/Client.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React from 'react';
33
import { BrowserRouter as Router } from 'react-router-dom';
44
import AppWrapper from 'src/App/AppWrapper';
55
import ScrollToTop from 'src/Components/ScrollToTop';
6+
import { getLocaleOnClient } from 'src/i18n/helpers';
67

78
/* eslint-disable no-restricted-globals */
89
export default class extends React.Component {
@@ -12,13 +13,13 @@ export default class extends React.Component {
1213
}
1314

1415
render() {
15-
const isEn = location.pathname.substr(1, 2) === 'en' && 'en';
16-
const isVi = location.pathname.substr(1, 2) === 'vi' && 'vi';
17-
const currentLang = isEn || isVi || 'en';
16+
// Determine current language by name
17+
const currentLang = getLocaleOnClient(location);
18+
1819
return (
1920
<Router>
2021
<ScrollToTop>
21-
<AppWrapper lang={navigator && currentLang} />
22+
<AppWrapper lang={currentLang} />
2223
</ScrollToTop>
2324
</Router>
2425
);

src/App/index.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export default ({ lang }) => (
4747
/>
4848

4949
{/* Define Redirect logic if any */}
50-
<RedirectWithStatus httpStatus={301} exact from="/:lang" to={`/${lang}`} />
50+
<RedirectWithStatus httpStatus={301} from="/:lang" to={`/${lang}`} />
5151
<RedirectWithStatus httpStatus={301} from="/:lang/users" to="/" />
5252
<RedirectWithStatus httpStatus={302} from="/:lang/courses" to="/:lang/404" />
5353

@@ -56,6 +56,5 @@ export default ({ lang }) => (
5656
</Switch>
5757

5858
<Footer />
59-
{/* Footer here */}
6059
</React.Fragment>
6160
);

src/Pages/Books/index.js

+30-28
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,46 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
3-
import loadData from 'src/helpers/loadData';
3+
import { fetchAllBooks } from 'src/helpers/loadData';
44
import Head from 'src/Components/Head';
5-
import Footer from 'src/Components/Footer';
5+
import FlexBox from 'src/Components/FlexBox';
6+
import { H3 } from 'src/Components/Typo';
67

7-
class Books extends React.Component {
8-
constructor(props) {
9-
super(props);
10-
11-
// Check if staticContext exists
12-
// because it will be undefined if rendered through a BrowserRouter
13-
this.state = (props.staticContext && props.staticContext.data) ? {
14-
data: props.staticContext.data,
15-
} : { data: [] };
16-
}
178

18-
componentDidMount() {
19-
setTimeout(() => {
20-
if (window.ROUTE_LOADED_DATA) {
9+
class Books extends React.Component {
10+
// Check if staticContext exists
11+
// because it will be undefined if rendered through a BrowserRouter
12+
state = {
13+
books: (this.props.staticContext && this.props.staticContext.data)
14+
? this.props.staticContext.data : [],
15+
};
16+
17+
async componentDidMount() {
18+
if (window.ROUTE_LOADED_DATA) {
19+
// console.log('Data preloaded');
20+
this.setState({
21+
books: window.ROUTE_LOADED_DATA,
22+
});
23+
delete window.ROUTE_LOADED_DATA;
24+
} else {
25+
// console.log('Data not preloaded. Fetching...');
26+
await fetchAllBooks().then((data) => {
2127
this.setState({
22-
data: window.ROUTE_LOADED_DATA,
23-
});
24-
delete window.ROUTE_LOADED_DATA;
25-
} else {
26-
loadData('posts').then((data) => {
27-
this.setState({
28-
data,
29-
});
28+
books: data,
3029
});
31-
}
32-
}, 0);
30+
});
31+
}
3332
}
3433

3534
render() {
36-
const { data } = this.state;
35+
const { books } = this.state;
3736
return (
3837
<React.Fragment>
3938
<Head title="Books" />
40-
<ul>{data.map(post => <li key={post.id}>{post.title}</li>)}</ul>
41-
<Footer />
39+
<FlexBox width={1/2} mx="auto" flexDirection="column" alignItems="flex-start">
40+
{
41+
books.map(book => <H3 key={book.id}>{book.title}</H3>)
42+
}
43+
</FlexBox>
4244
</React.Fragment>
4345
);
4446
}

src/Pages/Posts/index.js

+31-12
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { Link } from 'react-router-dom';
33
import PropTypes from 'prop-types';
44

55
import truncate from 'src/helpers/truncate';
6+
import { fetchAllPosts } from 'src/helpers/loadData';
7+
68
import FlexBox from 'src/Components/FlexBox';
79
import CardBox from 'src/Components/CardBox';
810
import { H3, Span } from 'src/Components/Typo';
@@ -12,21 +14,30 @@ import Badge from 'src/Components/Badge';
1214
// Import all files inside `data` via Webpack require.context
1315
// Read more:
1416
// https://goo.gl/315fi3 - Importing Multiple Markdown files into a React Component with Webpack
15-
const postContext = require.context('../../../content', false, /\.md$/);
16-
const postFiles = postContext
17-
.keys()
18-
.map(filename => postContext(filename));
19-
2017
class Posts extends React.Component {
2118
state = {
22-
posts: [],
23-
lang: 'en',
19+
posts: (this.props.staticContext && this.props.staticContext.data)
20+
? this.props.staticContext.data : [],
2421
}
2522

26-
componentDidMount() {
27-
const posts = postFiles;
23+
async componentDidMount() {
2824
const { lang } = this.props.match.params;
29-
this.setState(state => ({ ...state, posts, lang }));
25+
26+
if (window.ROUTE_LOADED_DATA) {
27+
// console.log('Data preloaded');
28+
this.setState({
29+
posts: window.ROUTE_LOADED_DATA,
30+
});
31+
delete window.ROUTE_LOADED_DATA;
32+
} else {
33+
// console.log('Data not preloaded. Fetching...');
34+
await fetchAllPosts().then((data) => {
35+
this.setState({
36+
lang,
37+
posts: data,
38+
});
39+
});
40+
}
3041
}
3142

3243
render() {
@@ -46,8 +57,8 @@ class Posts extends React.Component {
4657
>
4758
{
4859
posts.map((post, i) => (
49-
<CardBox width={[1, 1/3]} px={[1, 2, 3]} py={[0, 1, 2]} mx={[1, 2, 3]}>
50-
<Link key={i} to={`/${lang}/posts/${post.slug}`}>
60+
<CardBox key={i} width={[1, 1/3]} px={[1, 2, 3]} py={[0, 1, 2]} mx={[1, 2, 3]}>
61+
<Link to={`/${lang}/posts/${post.slug}`}>
5162
<H3 dangerouslySetInnerHTML={{ __html: post.title }} />
5263
</Link>
5364
<Span dangerouslySetInnerHTML={{ __html: truncate(post.__content) }} />
@@ -71,6 +82,14 @@ Posts.propTypes = {
7182
lang: PropTypes.string.isRequired,
7283
}),
7384
}).isRequired,
85+
86+
staticContext: PropTypes.shape({
87+
data: PropTypes.array,
88+
}),
89+
};
90+
91+
Posts.defaultProps = {
92+
staticContext: {},
7493
};
7594

7695
export default Posts;

src/client-locale/constants.js

-2
This file was deleted.

src/client-locale/extractLocalesFromReq.js

-12
This file was deleted.

src/client-locale/guessLocale.js

-9
This file was deleted.

src/helpers/getCookie.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
const getCookie = (name) => {
2+
const match = document.cookie.match(new RegExp(`(^| )${name}=([^;]+)`));
3+
return (match) ? match[2] : null;
4+
};
5+
6+
export default getCookie;

src/helpers/loadData.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import 'isomorphic-fetch';
22

3-
export default resourceType => fetch(`https://jsonplaceholder.typicode.com/${resourceType}`)
3+
export const fetchAllBooks = () => fetch('https://jsonplaceholder.typicode.com/posts')
44
.then(res => res.json())
55
.then(data => data.filter((_, idx) => idx < 10));
6+
7+
export const fetchAllPosts = async () => {
8+
const postContext = require.context('../../content', false, /\.md$/);
9+
const postFiles = await postContext
10+
.keys()
11+
.map(filename => postContext(filename));
12+
return postFiles;
13+
};

src/i18n/helpers/constants.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const LOCALE_COOKIE_NAME = 'locale';
2+
export const COOKIE_MAX_AGE = 2592000; // 30 days
3+
4+
export const STORAGE_LOCALE_NAME = 'locale';
5+
export const LOCALE_AVAILABLE = ['vi', 'en'];
6+
export const LOCALE_DEFAULT = 'en';

src/i18n/helpers/getLocale.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import getCookie from 'src/helpers/getCookie';
2+
import { LOCALE_DEFAULT, LOCALE_COOKIE_NAME, LOCALE_AVAILABLE } from './constants';
3+
4+
// Get Locale from request server
5+
const getLocaleOnServer = (req) => {
6+
// Check if locale has been chosen before via Cookie
7+
let currentLocale = req.cookies[LOCALE_COOKIE_NAME];
8+
9+
// Check if locale belongs to available locales
10+
currentLocale = (LOCALE_AVAILABLE.includes(currentLocale)) ? currentLocale : LOCALE_DEFAULT;
11+
12+
return currentLocale;
13+
};
14+
15+
// Get Locale on client side
16+
const getLocaleOnClient = (location) => {
17+
let currentLocale = getCookie('locale');
18+
if (!currentLocale) location.pathname.substr(1, 2);
19+
currentLocale = (LOCALE_AVAILABLE.includes(currentLocale)) ? currentLocale : LOCALE_DEFAULT;
20+
return currentLocale;
21+
};
22+
23+
export {
24+
getLocaleOnServer,
25+
getLocaleOnClient,
26+
};

src/i18n/helpers/index.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { getLocaleOnClient, getLocaleOnServer } from './getLocale';
2+
import setLocale from './setLocale';
3+
4+
export {
5+
getLocaleOnClient,
6+
getLocaleOnServer,
7+
setLocale,
8+
};
File renamed without changes.

src/index.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import { hydrate } from 'react-dom';
33
import { AppContainer } from 'react-hot-loader';
4-
import AppRoot from './App/AppRoot';
4+
import Client from './App/Client';
55

66
const render = (Component) => {
77
hydrate(
@@ -11,12 +11,12 @@ const render = (Component) => {
1111
document.getElementById('app'),
1212
);
1313
};
14-
render(AppRoot);
14+
render(Client);
1515

1616
// Enable Hot Module Replacement
1717
if (module.hot) {
18-
module.hot.accept('./App/AppRoot.js', () => {
19-
const NewAppRoot = require('./App/AppRoot.js').default; // eslint-disable-line global-require
20-
render(NewAppRoot);
18+
module.hot.accept('./App/Client.js', () => {
19+
const NewClient = require('./App/Client.js').default; // eslint-disable-line global-require
20+
render(NewClient);
2121
});
2222
}

src/routes.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import NotFound404 from 'src/Pages/404';
44
import Posts from 'src/Pages/Posts';
55
import Books from 'src/Pages/Books';
66

7-
import loadData from 'src/helpers/loadData';
7+
import { fetchAllBooks, fetchAllPosts } from 'src/helpers/loadData';
88

9-
const routes = [
10-
{ path: '/', exact: true, component: Home },
11-
{ path: '/login', component: Login },
12-
{ path: '/books', component: Books },
13-
{ path: '/posts', component: Posts, loadData: () => loadData('posts') },
9+
const routes = lang => [
10+
{ path: `/${lang}/`, exact: true, component: Home },
11+
{ path: `/${lang}/login`, component: Login },
12+
{ path: `/${lang}/books`, component: Books, loadData: () => fetchAllBooks() },
13+
{ path: `/${lang}/posts`, component: Posts, loadData: () => fetchAllPosts() },
1414
{ component: NotFound404 },
1515
];
1616

src/server/render.js

+13-10
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@ import serialize from 'serialize-javascript';
1010
import AppWrapper from 'src/App/AppWrapper';
1111
import Markup from 'src/Components/HTML';
1212

13-
import extractLocalesFromReq from 'src/client-locale/extractLocalesFromReq';
14-
import guessLocale from 'src/client-locale/guessLocale';
15-
import { LOCALE_COOKIE_NAME, COOKIE_MAX_AGE } from 'src/client-locale/constants';
13+
import { getLocaleOnServer } from 'src/i18n/helpers';
14+
import {
15+
LOCALE_COOKIE_NAME,
16+
COOKIE_MAX_AGE,
17+
LOCALE_AVAILABLE,
18+
LOCALE_DEFAULT,
19+
} from 'src/i18n/helpers/constants';
1620
import {
1721
log, error, warning,
1822
} from 'src/Components/Logger';
@@ -24,20 +28,19 @@ import Sitemap from './sitemap';
2428

2529
export default ({ clientStats }) => (req, res) => {
2630
// Prepare Language
27-
const userLocales = extractLocalesFromReq(req);
28-
let lang = guessLocale(['vi', 'en'], userLocales, 'en');
29-
30-
if (req.originalUrl.substr(1, 2) === 'vi') lang = 'vi';
31-
if (req.originalUrl.substr(1, 2) === 'en') lang = 'en';
31+
// TODO: Properly redirect language to preveous use
32+
let lang = getLocaleOnServer(req);
33+
lang = req.originalUrl.substr(1, 2);
34+
lang = (LOCALE_AVAILABLE.includes(lang)) ? lang : LOCALE_DEFAULT;
35+
// console.log(lang);
3236

3337
// Match current route and check if loadData is required
34-
const currentRoute = routes.find(route => matchPath(req.url, route)) || {};
38+
const currentRoute = routes(lang).find(route => matchPath(req.url, route)) || {};
3539
const promise = (currentRoute.loadData) ? currentRoute.loadData() : Promise.resolve(null);
3640

3741
// Prepare out stylesheets and apply to DOM
3842
const stylesheet = new ServerStyleSheet();
3943

40-
// TODO: Fetch isomorphic-data
4144
promise.then((data) => {
4245
// Prepare data
4346
const context = { data };

0 commit comments

Comments
 (0)