-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
190 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# Server-side rending | ||
|
||
Cashay supports server-side rending out of the box. | ||
|
||
First, you'll need to create a redux store: | ||
``` | ||
const store = createStore(reducer, {}); | ||
``` | ||
|
||
Second, you need to create the Cashay singleton: | ||
``` | ||
import {cashay, ServerSideTransport} from 'cashay'; | ||
const cashaySchema = require('cashay!./utils/getCashaySchema.js'); | ||
cashay.create({ | ||
store, | ||
schema: cashaySchema, | ||
transport: new ServerSideTransport(...) | ||
}); | ||
``` | ||
_Note: if you use a bundler like webpack, make sure that this file is included in the bundle. | ||
You'll want the `cashay` that you `import` here | ||
to be the same `cashay` that you use in your components. (singletons are no fun like that)_ | ||
|
||
Third, you'll want to stringify your state to send it down the wire: | ||
``` | ||
const initialState = `window.__INITIAL_STATE__ = ${JSON.stringify(store.getState())}`; | ||
// assume you use a react jsx template for SSR | ||
<html> | ||
<body> | ||
... | ||
<script dangerouslySetInnerHTML={{__html: initialState}} /> | ||
</body> | ||
</html> | ||
``` | ||
|
||
Finally, when it arrives on the client, you'll want to rehydrate the state: | ||
|
||
``` | ||
const initialState = window.__INITIAL_STATE__; | ||
store = createStore(reducer, initialState); | ||
``` | ||
|
||
And there you have it! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# Recipes | ||
|
||
- [Multi-part queries](./multi-part.queries.md) | ||
- [Pagination](./pagination.md) | ||
- [Persisted state](./persisted-state.md) | ||
- [Schema (without webpack)](./cashay-schema.md) | ||
- [Server-side rendering](./SSR.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
# Persisted state | ||
|
||
Cashay is just a reducer, so it doesn't need any extra logic to persist data locally. | ||
Any tool you use to persist your redux store will work for persisting your domain state, too! | ||
|
||
Personally, I like using [redux-persist](https://github.com/rt2zz/redux-persist). | ||
Redux-persist works by storing each reducer in in a key-value store like | ||
`localStorage`, `localForage`, or React-native storage. | ||
After each action is dispatched, it will update your locally-stored copy, too. | ||
When your app starts up, you can rehydrate the persisted data back into your redux state. | ||
|
||
A production setup might look like this: | ||
|
||
``` | ||
persistStore(store, {transforms: [cashayPersistTransform]}, () => { | ||
cashay.create({ | ||
store, | ||
schema: cashaySchema, | ||
transport: new HTTPTransport(...); | ||
}); | ||
render( | ||
<AppContainer> | ||
<Root store={store}/> | ||
</AppContainer>, | ||
document.getElementById('root') | ||
); | ||
}); | ||
``` | ||
|
||
A more advanced feature is removing stale data during rehydration, such as old query data. | ||
That's what the `cashayPersistTransform` does above. | ||
For example, if you use Cashay for authorization, | ||
you might want to remove an expired JWT from a `getUserWithAuthToken` query: | ||
|
||
``` | ||
import {createTransform} from 'redux-persist'; | ||
import jwtDecode from 'jwt-decode'; | ||
const cashayDeserializer = outboundState => { | ||
const auth = outboundState.data.result.getUserWithAuthToken; | ||
if (auth) { | ||
const authObj = auth['']; | ||
const {authToken} = authObj; | ||
if (authToken) { | ||
const authTokenObj = jwtDecode(authToken); | ||
if (authTokenObj.exp < Date.now() / 1000) { | ||
authObj.authToken = null; | ||
} | ||
} | ||
} | ||
return outboundState; | ||
}; | ||
export default createTransform( | ||
state => state, | ||
cashayDeserializer, | ||
{whitelist: ['cashay']} | ||
); | ||
``` | ||
Note: Cashay stores data using the following pattern: | ||
``` | ||
cashay.data.result[queryName][?arguments][?pagination] | ||
``` | ||
You can verify this path by cracking open your friendly `redux-devtools`. | ||
|
||
In the future, when time-to-live (TTL) metadata is supported, | ||
Cashay will include a transfrom that will automatically delete all expired documents. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
export cashayReducer from './normalize/duck'; | ||
export cashay from './Cashay'; | ||
export HTTPTransport from './HTTPTransport'; | ||
export HTTPTransport from './transports/HTTPTransport'; | ||
export ServerSideTransport from './transports/ServerSideTransport'; | ||
export transformSchema from './schema/transformSchema'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import defaultHandleErrors from './defaultHandleErrors'; | ||
|
||
export default class ServerSideTransport { | ||
constructor(graphQLHandler, handleErrors = defaultHandleErrors) { | ||
this.graphQLHandler = graphQLHandler; | ||
this.handleErrors = handleErrors; | ||
} | ||
|
||
async handleQuery(request) { | ||
const {data, errors} = await this.graphQLHandler(request); | ||
const error = this.handleErrors(request, errors); | ||
return error ? {data, error} : {data}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
const tryParse = str => { | ||
let obj; | ||
try { | ||
obj = JSON.parse(str); | ||
} catch (e) { | ||
return false; | ||
} | ||
return obj; | ||
}; | ||
|
||
export default function defaultHandleErrors(request, errors, duckField = '_error') { | ||
if (!errors) return; | ||
// expect a human to put an end-user-safe message in the message field | ||
const firstErrorMessage = errors[0].message; | ||
if (!firstErrorMessage) return {errors}; | ||
const parsedError = tryParse(firstErrorMessage); | ||
if (parsedError && parsedError.hasOwnProperty(duckField)) { | ||
return parsedError; | ||
} | ||
return {errors}; | ||
}; |