From 7490b53121c89c26b8d44df69a490f4cf7412ea1 Mon Sep 17 00:00:00 2001 From: James Baxley Date: Tue, 12 Jul 2016 10:21:39 -0400 Subject: [PATCH] fix missing redux integration for ssr (#103) * fix missing redux integration for ssr --- Changelog.md | 4 ++ package.json | 2 +- src/connect.tsx | 1 + src/server.ts | 8 ++- test/server/index.tsx | 135 +++++++++++++++++++++++++++++++++--------- 5 files changed, 121 insertions(+), 29 deletions(-) diff --git a/Changelog.md b/Changelog.md index 880bb31ac6..f367b24250 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,10 @@ Expect active development and potentially significant breaking changes in the `0.x` track. We'll try to be diligent about releasing a `1.0` version in a timely fashion (ideally within 1 or 2 months), so that we can take advantage of SemVer to signify breaking changes from that point on. +### v0.3.17 + +- Bug: Fixed but where SSR wouldn't get calculated props from redux actions [#103](https://github.com/apollostack/react-apollo/pull/103) + ### v0.3.16 - Feature: integrated SSR [#83](https://github.com/apollostack/react-apollo/pull/83) diff --git a/package.json b/package.json index 71356fe940..e6f62c5c9b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-apollo", - "version": "0.3.16", + "version": "0.3.17", "description": "React data container for Apollo Client", "main": "index.js", "scripts": { diff --git a/src/connect.tsx b/src/connect.tsx index 622ed3f337..a452fcdb89 100644 --- a/src/connect.tsx +++ b/src/connect.tsx @@ -121,6 +121,7 @@ export default function connect(opts?: ConnectOptions) { }; // for use with getData during SSR static mapQueriesToProps = mapQueries ? mapQueriesToProps : false; + static opts = opts; // react / redux and react dev tools (HMR) needs public state: any; // redux state diff --git a/src/server.ts b/src/server.ts index 0f0ffa4b94..53e20a4d8d 100644 --- a/src/server.ts +++ b/src/server.ts @@ -89,10 +89,15 @@ function getQueriesFromTree({ component, context = {}, queries = []}: QueryTreeA if (typeof type === 'function') { let ComponentClass = type; let ownProps = getPropsFromChild(component); - const { state } = context; // see if this is a connect type if (typeof type.mapQueriesToProps === 'function') { + const state = store.getState(); + const { mapStateToProps, mapDispatchToProps, mergeProps } = type.opts; + const mappedState = mapStateToProps && mapStateToProps(state, ownProps); + const mappedDisptach = mapDispatchToProps && mapDispatchToProps(store.dispatch, ownProps); + const mergedProps = mergeProps && mergeProps(mappedState, mappedDisptach, ownProps); + ownProps = assign(ownProps, mappedState, mappedDisptach, mergedProps); const data = type.mapQueriesToProps({ ownProps, state }); for (let key in data) { if (!data.hasOwnProperty(key)) continue; @@ -150,6 +155,7 @@ export function getDataFromTree(app, ctx: any = {}): Promise { if (!store && client && !client.store) client.initStore(); if (!store && client && client.store) store = client.store; + // no client found, nothing to do if (!client || !store) return Promise.resolve(null); diff --git a/test/server/index.tsx b/test/server/index.tsx index bcc98c3a37..c009976f76 100644 --- a/test/server/index.tsx +++ b/test/server/index.tsx @@ -5,6 +5,7 @@ import ApolloClient, { createNetworkInterface } from 'apollo-client'; import { connect, ApolloProvider } from '../../src'; import { getDataFromTree, renderToStringWithData } from '../../src/server'; import 'isomorphic-fetch'; +import { createStore, combineReducers, applyMiddleware } from 'redux'; import gql from 'graphql-tag'; @@ -108,40 +109,21 @@ describe('SSR', () => { }); it('should run return the initial state for hydration', (done) => { - const Element = ({ data }) => { - return
{data.loading ? 'loading' : data.currentUser.firstName}
; - }; - - const query = gql` - query App { - currentUser { - firstName - } - } - `; + const Element = ({ data }) => ( +
{data.loading ? 'loading' : data.currentUser.firstName}
+ ); - const data = { - currentUser: { - firstName: 'James', - }, - }; + const query = gql`query App { currentUser { firstName } }`; + const data = { currentUser: { firstName: 'James' } }; const networkInterface = mockNetworkInterface( - { - request: { query }, - result: { data }, - delay: 50, - } + { request: { query }, result: { data }, delay: 50 } ); - const apolloClient = new ApolloClient({ - networkInterface, - }); + const apolloClient = new ApolloClient({ networkInterface }); const WrappedElement = connect({ - mapQueriesToProps: () => ({ - data: { query }, - }), + mapQueriesToProps: () => ({ data: { query } }), })(Element); const app = ( @@ -157,6 +139,105 @@ describe('SSR', () => { done(); }); }); + it('should allow using the calculated props in the mapQueriesToProps function', (done) => { + function counter(state = 0, action) { + return action.type === 'INCREMENT' ? state + 1 : state; + } + const Element = ({ data }) => ( +
{data.loading ? 'loading' : data.currentUser.firstName}
+ ); + + const query = gql`query App($ctnr: Int) { currentUser(ctrn: $ctnr) { firstName } }`; + const data = { currentUser: { firstName: 'James' } }; + + const networkInterface = mockNetworkInterface( + { request: { query, variables: { ctnr: 1 } }, result: { data }, delay: 50 } + ); + + const apolloClient = new ApolloClient({ networkInterface }); + + function mapStateToProps(state) { + return { ctnr: state.counter + 1 } + } + + const WrappedElement = connect({ + mapQueriesToProps: ({ ownProps }) => ({ + data: { query, variables: { ctnr: ownProps.ctnr } }, + }), + mapStateToProps, + })(Element); + + // Typscript workaround + const apolloReducer = apolloClient.reducer() as () => any; + const store = createStore( + combineReducers({ counter, apollo: apolloReducer }), + applyMiddleware(apolloClient.middleware()) + ); + + const app = ( + + + + ); + + getDataFromTree(app) + .then(({ initialState }) => { + expect(initialState.apollo.data).to.exist; + expect(initialState.apollo.data['ROOT_QUERY.currentUser({"ctrn":1})']).to.exist; + done(); + }) + .catch(done); + }); + + it('should allow using the state in the mapQueriesToProps function', (done) => { + function counter(state = 0, action) { + return action.type === 'INCREMENT' ? state + 1 : state; + } + const Element = ({ data }) => ( +
{data.loading ? 'loading' : data.currentUser.firstName}
+ ); + + const query = gql`query App($ctnr: Int) { currentUser(ctrn: $ctnr) { firstName } }`; + const data = { currentUser: { firstName: 'James' } }; + + const networkInterface = mockNetworkInterface( + { request: { query, variables: { ctnr: 0 } }, result: { data }, delay: 50 } + ); + + const apolloClient = new ApolloClient({ networkInterface }); + + function mapStateToProps(state) { + return { ctnr: state.counter + 1 } + } + + const WrappedElement = connect({ + mapQueriesToProps: ({ state }) => ({ + data: { query, variables: { ctnr: state.counter } }, + }), + mapStateToProps, + })(Element); + + // Typscript workaround + const apolloReducer = apolloClient.reducer() as () => any; + const store = createStore( + combineReducers({ counter, apollo: apolloReducer }), + applyMiddleware(apolloClient.middleware()) + ); + + const app = ( + + + + ); + + getDataFromTree(app) + .then(({ initialState }) => { + expect(initialState.apollo.data).to.exist; + expect(initialState.apollo.data['ROOT_QUERY.currentUser({"ctrn":0})']).to.exist; + done(); + }) + .catch(done); + }); it('shouldn\'t run queries if ssr is turned to off', (done) => { const Element = ({ data }) => { return
{data.loading ? 'loading' : data.currentUser.firstName}
;