Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initialize a new HTTP client for each web request #1237

Merged
merged 6 commits into from
Apr 25, 2017
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License

Copyright (c) 2014-present Konstantin Tarkus, KriaSoft LLC.
Copyright (c) 2014-present Konstantin Tarkus, Kriasoft LLC.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
4 changes: 1 addition & 3 deletions docs/data-fetching.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ There is isomorphic `core/fetch` module that can be used the same way in both
client-side and server-side code as follows:

```jsx
import fetch from '../core/fetch';

export const path = '/products';
export const action = async () => {
export const action = async ({ fetch }) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example here is on router. But I believe users are importing fetch on more nested components so HoC implementation and React HoC component withFetch() which correctly bridge context will be needed for users.

const response = await fetch('/graphql?query={products{id,name}}');
const data = await response.json();
return <Layout><Products {...data} /></Layout>;
Expand Down
4 changes: 2 additions & 2 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ Before you start, take a moment to see how the project structure looks like:
├── /public/ # Static files which are copied into the /build/public folder
├── /src/ # The source code of the application
│ ├── /components/ # React components
│ ├── /core/ # Core framework and utility functions
│ ├── /data/ # GraphQL server schema and data models
│ ├── /routes/ # Page/screen components along with the routing information
│ ├── /client.js # Client-side startup script
│ ├── /config.js # Global application settings
│ └── /server.js # Server-side startup script
│ ├── /server.js # Server-side startup script
│ └── ... # Other core framework modules
├── /test/ # Unit and end-to-end tests
├── /tools/ # Build automation scripts and utilities
│ ├── /lib/ # Library for utility snippets
Expand Down
24 changes: 12 additions & 12 deletions docs/recipes/how-to-implement-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Let's see how a custom routing solution under 100 lines of code may look like.

First, you will need to implement the **list of application routes** in which each route can be
represented as an object with properties of `path` (a parametrized URL path string), `action`
(a function), and optionally `children` (a list of sub-routes, each of which is a route object).
(a function), and optionally `children` (a list of sub-routes, each of which is a route object).
The `action` function returns anything - a string, a React component, etc. For example:

#### `src/routes/index.js`
Expand Down Expand Up @@ -42,7 +42,7 @@ return `{ id: '123' }` while calling `matchURI('/tasks/:id', '/foo')` must retur
Fortunately, there is a great library called [`path-to-regexp`](https://github.com/pillarjs/path-to-regexp)
that makes this task very easy. Here is how a URL matcher function may look like:

#### `src/core/router.js`
#### `src/router.js`

```js
import toRegExp from 'path-to-regexp';
Expand All @@ -67,7 +67,7 @@ action method returns anything other than `null` or `undefined` return that to t
Otherwise, it should continue iterating over the remaining routes. If none of the routes match to the
provided URL string, it should throw an exception (Not found). Here is how this function may look like:

#### `src/core/router.js`
#### `src/router.js`

```js
import toRegExp from 'path-to-regexp';
Expand All @@ -93,12 +93,12 @@ export default { resolve };
That's it! Here is a usage example:

```js
import router from './core/router';
import router from './router';
import routes from './routes';

router.resolve(routes, { pathname: '/tasks' }).then(result => {
console.log(result);
// => { title: 'To-do', component: <TodoList .../> }
// => { title: 'To-do', component: <TodoList .../> }
});
```

Expand All @@ -108,10 +108,10 @@ npm module to handles this task for you. It is the same library used in React Ro
wrapper over [HTML5 History API](https://developer.mozilla.org/docs/Web/API/History_API) that
handles all the tricky browser compatibility issues related to client-side navigation.

First, create `src/core/history.js` file that will initialize a new instance of the `history` module
First, create `src/history.js` file that will initialize a new instance of the `history` module
and export is as a singleton:

#### `src/core/history.js`
#### `src/history.js`

```js
import createHistory from 'history/lib/createBrowserHistory';
Expand All @@ -125,8 +125,8 @@ Then plug it in, in your client-side bootstrap code as follows:

```js
import ReactDOM from 'react-dom';
import history from './core/history';
import router from './core/router';
import history from './history';
import router from './router';
import routes from './routes';

const container = document.getElementById('root');
Expand Down Expand Up @@ -157,7 +157,7 @@ In order to trigger client-side navigation without causing full-page refresh, yo

```js
import React from 'react';
import history from '../core/history';
import history from '../history';

class App extends React.Component {
transition = event => {
Expand All @@ -181,9 +181,9 @@ class App extends React.Component {

Though, it is a common practice to extract that transitioning functionality into a stand-alone
(`Link`) component that can be used as follows:

```html
<Link to="/tasks/123">View Task #123</Link>
<Link to="/tasks/123">View Task #123</Link>
```

### Routing in React Starter Kit
Expand Down
44 changes: 22 additions & 22 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,36 +21,36 @@
"core-js": "^2.4.1",
"express": "^4.15.2",
"express-graphql": "^0.6.4",
"express-jwt": "^5.1.0",
"express-jwt": "^5.3.0",
"fastclick": "^1.0.6",
"graphql": "^0.9.2",
"graphql": "^0.9.3",
"history": "^4.6.1",
"isomorphic-style-loader": "^1.1.0",
"isomorphic-fetch": "^2.2.1",
"isomorphic-style-loader": "^2.0.0",
"jsonwebtoken": "^7.3.0",
"node-fetch": "^1.6.3",
"normalize.css": "^6.0.0",
"passport": "^0.3.2",
"passport-facebook": "^2.1.1",
"pretty-error": "^2.1.0",
"prop-types": "^15.5.6",
"query-string": "^4.3.2",
"react": "^15.5.3",
"react-dom": "^15.5.3",
"prop-types": "^15.5.8",
"query-string": "^4.3.4",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"sequelize": "^3.30.4",
"serialize-javascript": "^1.3.0",
"source-map-support": "^0.4.14",
"sqlite3": "^3.1.8",
"universal-router": "^3.0.0",
"whatwg-fetch": "^2.0.3"
"universal-router": "^3.1.0"
},
"devDependencies": {
"assets-webpack-plugin": "^3.5.1",
"autoprefixer": "^6.7.7",
"babel-cli": "^6.24.1",
"babel-core": "^6.24.1",
"babel-eslint": "^7.2.1",
"babel-loader": "^6.4.1",
"babel-eslint": "^7.2.3",
"babel-loader": "^7.0.0",
"babel-plugin-rewire": "^1.1.0",
"babel-preset-env": "^1.3.3",
"babel-preset-env": "^1.4.0",
"babel-preset-react": "^6.24.1",
"babel-preset-react-optimize": "^1.0.1",
"babel-preset-stage-2": "^6.24.1",
Expand All @@ -62,7 +62,7 @@
"chokidar": "^1.6.1",
"css-loader": "^0.28.0",
"editorconfig-tools": "^0.1.1",
"enzyme": "^2.8.0",
"enzyme": "^2.8.2",
"eslint": "^3.19.0",
"eslint-config-airbnb": "^14.1.0",
"eslint-loader": "^1.7.1",
Expand All @@ -77,21 +77,21 @@
"lint-staged": "^3.4.0",
"markdown-it": "^8.3.1",
"mkdirp": "^0.5.1",
"mocha": "^3.2.0",
"mocha": "^3.3.0",
"pixrem": "^3.0.2",
"pleeease-filters": "^3.0.1",
"postcss": "^5.2.16",
"postcss": "^5.2.17",
"postcss-calc": "^5.3.1",
"postcss-color-function": "^3.0.0",
"postcss-custom-media": "^5.0.1",
"postcss-custom-properties": "^5.0.2",
"postcss-custom-selectors": "^3.0.0",
"postcss-flexbugs-fixes": "^2.1.0",
"postcss-flexbugs-fixes": "^2.1.1",
"postcss-global-import": "^1.0.0",
"postcss-import": "^9.1.0",
"postcss-loader": "^1.3.3",
"postcss-media-minmax": "^2.1.2",
"postcss-nested": "^1.0.0",
"postcss-nested": "^1.0.1",
"postcss-nesting": "^2.3.1",
"postcss-pseudoelements": "^4.0.0",
"postcss-selector-matches": "^2.0.5",
Expand All @@ -108,11 +108,11 @@
"stylelint": "^7.10.1",
"stylelint-config-standard": "^16.0.0",
"url-loader": "^0.5.8",
"webpack": "^2.3.3",
"webpack-bundle-analyzer": "^2.3.1",
"webpack-dev-middleware": "^1.10.1",
"webpack": "^2.4.1",
"webpack-bundle-analyzer": "^2.4.0",
"webpack-dev-middleware": "^1.10.2",
"webpack-hot-middleware": "^2.18.0",
"write-file-webpack-plugin": "^4.0.0"
"write-file-webpack-plugin": "^4.0.2"
},
"babel": {
"presets": [
Expand Down
File renamed without changes.
18 changes: 12 additions & 6 deletions src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import ReactDOM from 'react-dom';
import FastClick from 'fastclick';
import queryString from 'query-string';
import { createPath } from 'history/PathUtils';
import history from './core/history';
import App from './components/App';
import { updateMeta } from './core/DOMUtils';
import { ErrorReporter, deepForceUpdate } from './core/devUtils';
import createFetch from './createFetch';
import history from './history';
import { updateMeta } from './DOMUtils';
import { ErrorReporter, deepForceUpdate } from './devUtils';

/* eslint-disable global-require */

Expand All @@ -29,6 +30,10 @@ const context = {
const removeCss = styles.map(x => x._insertCss());
return () => { removeCss.forEach(f => f()); };
},
// Universal HTTP client
fetch: createFetch({
baseUrl: window.App.apiUrl,
}),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add credentials: 'same-origin' here?

};

// Switch off the native scroll restoration behavior and handle it manually
Expand Down Expand Up @@ -87,7 +92,7 @@ FastClick.attach(document.body);
const container = document.getElementById('app');
let appInstance;
let currentLocation = history.location;
let router = require('./core/router').default;
let router = require('./router').default;

// Re-render the app when window.location changes
async function onLocationChange(location, action) {
Expand All @@ -109,6 +114,7 @@ async function onLocationChange(location, action) {
const route = await router.resolve({
path: location.pathname,
query: queryString.parse(location.search),
fetch: context.fetch,
});

// Prevent multiple page renders during the routing process
Expand Down Expand Up @@ -161,8 +167,8 @@ if (__DEV__) {

// Enable Hot Module Replacement (HMR)
if (module.hot) {
module.hot.accept('./core/router', () => {
router = require('./core/router').default;
module.hot.accept('./router', () => {
router = require('./router').default;

if (appInstance) {
try {
Expand Down
2 changes: 2 additions & 0 deletions src/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const ContextType = {
// Enables critical path CSS rendering
// https://github.com/kriasoft/isomorphic-style-loader
insertCss: PropTypes.func.isRequired,
// Universal HTTP client
fetch: PropTypes.func.isRequired,
};

/**
Expand Down
23 changes: 11 additions & 12 deletions src/components/Html.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@

import React from 'react';
import PropTypes from 'prop-types';
import { analytics } from '../config';
import serialize from 'serialize-javascript';
import config from '../config';

/* eslint-disable react/no-danger */

class Html extends React.Component {
static propTypes = {
Expand All @@ -20,6 +23,7 @@ class Html extends React.Component {
cssText: PropTypes.string.isRequired,
}).isRequired),
scripts: PropTypes.arrayOf(PropTypes.string.isRequired),
app: PropTypes.object, // eslint-disable-line
children: PropTypes.string.isRequired,
};

Expand All @@ -29,7 +33,7 @@ class Html extends React.Component {
};

render() {
const { title, description, styles, scripts, children } = this.props;
const { title, description, styles, scripts, app, children } = this.props;
return (
<html className="no-js" lang="en">
<head>
Expand All @@ -43,27 +47,22 @@ class Html extends React.Component {
<style
key={style.id}
id={style.id}
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: style.cssText }}
/>,
)}
</head>
<body>
<div
id="app"
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: children }}
/>
<div id="app" dangerouslySetInnerHTML={{ __html: children }} />
<script dangerouslySetInnerHTML={{ __html: `window.App=${serialize(app)}` }} />
{scripts.map(script => <script key={script} src={script} />)}
{analytics.google.trackingId &&
{config.analytics.googleTrackingId &&
<script
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html:
'window.ga=function(){ga.q.push(arguments)};ga.q=[];ga.l=+new Date;' +
`ga('create','${analytics.google.trackingId}','auto');ga('send','pageview')` }}
`ga('create','${config.analytics.googleTrackingId}','auto');ga('send','pageview')` }}
/>
}
{analytics.google.trackingId &&
{config.analytics.googleTrackingId &&
<script src="https://www.google-analytics.com/analytics.js" async defer />
}
</body>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Link/Link.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import React from 'react';
import PropTypes from 'prop-types';
import history from '../../core/history';
import history from '../../history';

function isLeftClickEvent(event) {
return event.button === 0;
Expand Down
Loading