diff --git a/Changelog.md b/Changelog.md index c1522e6c93..598a74ddb5 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,11 @@ 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.4.5 + +- Feature: Allow options value to be an object instead of a method. [#144](https://github.com/apollostack/react-apollo/issues/144) +- Bug: Fixed issue with missing methods on initial props [#142](https://github.com/apollostack/react-apollo/issues/142) +- Bug: Fixed oddity with multi nested enhancers on SSR [#141](https://github.com/apollostack/react-apollo/issues/141) ### v0.4.4 diff --git a/package.json b/package.json index 2f977fede7..b8ee856bda 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-apollo", - "version": "0.4.4", + "version": "0.4.5", "description": "React data container for Apollo Client", "main": "index.js", "scripts": { diff --git a/src/graphql.tsx b/src/graphql.tsx index c72d13e404..7c43e21a78 100644 --- a/src/graphql.tsx +++ b/src/graphql.tsx @@ -127,7 +127,7 @@ export function withApollo(WrappedComponent) { }; export interface OperationOption { - options?: (props: any) => QueryOptions | MutationOptions; + options?: Object | ((props: any) => QueryOptions | MutationOptions); props?: (props: any) => any; name?: string; withRef?: boolean; @@ -140,7 +140,9 @@ export default function graphql( // extract options const { options = defaultMapPropsToOptions } = operationOptions; - const mapPropsToOptions = options; + let mapPropsToOptions = options as (props: any) => QueryOptions | MutationOptions; + if (typeof mapPropsToOptions !== 'function') mapPropsToOptions = () => options; + const mapResultToProps = operationOptions.props; // safety check on the operation @@ -217,8 +219,6 @@ export default function graphql( store: PropTypes.object.isRequired, client: PropTypes.object.isRequired, }; - // for use with getData during SSR - static fetchData = operation.type === DocumentType.Query ? fetchData : false; // start of query composition static fragments: FragmentDefinition[] = operation.fragments; @@ -328,7 +328,22 @@ export default function graphql( query: document, variables, }); - queryData = assign({ errors: null, loading: false, variables }, result); + + const refetch = (vars) => { + return this.client.query({ + query: document, + variables: vars, + }); + }; + + const fetchMore = (opts) => { + opts.query = document; + return this.client.query(opts); + }; + + queryData = assign({ + errors: null, loading: false, variables, refetch, fetchMore, + }, result); } catch (e) {/* tslint:disable-line */} this.data = queryData; @@ -553,6 +568,8 @@ export default function graphql( } } + if (operation.type === DocumentType.Query) (GraphQL as any).fetchData = fetchData; + // Make sure we preserve any custom statics on the original component. return hoistNonReactStatics(GraphQL, WrappedComponent); diff --git a/test/react-web/client/graphql/fragments.tsx b/test/react-web/client/graphql/fragments.tsx index 4a3c1f5e7e..3ee2decce0 100644 --- a/test/react-web/client/graphql/fragments.tsx +++ b/test/react-web/client/graphql/fragments.tsx @@ -150,7 +150,7 @@ describe('fragments', () => { const client = new ApolloClient({ networkInterface }); @graphql(query, { - options: () => ({ fragments: [shipFragment]}) + options: () => ({ fragments: [shipFragment]}), }) class Container extends React.Component { componentWillReceiveProps(props) { diff --git a/test/react-web/client/graphql/queries.tsx b/test/react-web/client/graphql/queries.tsx index 57bbd3115d..cad74bf487 100644 --- a/test/react-web/client/graphql/queries.tsx +++ b/test/react-web/client/graphql/queries.tsx @@ -493,6 +493,10 @@ describe('queries', () => { let hasRefetched; @graphql(query) class Container extends React.Component { + componentWillMount(){ + expect(this.props.data.refetch).to.be.exist; + expect(this.props.data.refetch).to.be.instanceof(Function); + } componentWillReceiveProps({ data }) { // tslint:disable-line if (hasRefetched) return; hasRefetched = true; @@ -531,6 +535,10 @@ describe('queries', () => { let count = 0; @graphql(query, { options: () => ({ variables }) }) class Container extends React.Component { + componentWillMount(){ + expect(this.props.data.fetchMore).to.be.exist; + expect(this.props.data.fetchMore).to.be.instanceof(Function); + } componentWillReceiveProps(props) { if (count === 0) { expect(props.data.fetchMore).to.be.exist; diff --git a/test/react-web/client/graphql/shared-operations.tsx b/test/react-web/client/graphql/shared-operations.tsx index 41f9167e11..d75274047f 100644 --- a/test/react-web/client/graphql/shared-operations.tsx +++ b/test/react-web/client/graphql/shared-operations.tsx @@ -148,7 +148,7 @@ describe('shared opertations', () => { const testData = { foo: 'bar' }; class Container extends React.Component { - someMethod(){ + someMethod() { return testData; } @@ -167,10 +167,35 @@ describe('shared opertations', () => { const decorated = TestUtils.findRenderedComponentWithType(tree, Decorated); - expect(() => (decorated as any).someMethod()).to.throw() - expect((decorated as any).getWrappedInstance().someMethod()).to.deep.equal(testData); - expect((decorated as any).refs.wrappedInstance.someMethod()).to.deep.equal(testData); + expect(() => (decorated as any).someMethod()).to.throw(); + expect((decorated as any).getWrappedInstance().someMethod()).to.deep.equal(testData); + expect((decorated as any).refs.wrappedInstance.someMethod()).to.deep.equal(testData); }); + it('allows options to take an object', (done) => { + const query = gql`query people { allPeople(first: 1) { people { name } } }`; + const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } }; + const networkInterface = mockNetworkInterface({ request: { query }, result: { data } }); + const client = new ApolloClient({ networkInterface }); + + let queryExecuted; + @graphql(query, { options: { skip: true } }) + class Container extends React.Component { + componentWillReceiveProps(props) { + queryExecuted = true; + } + render() { + return null; + } + }; + + mount(); + + setTimeout(() => { + if (!queryExecuted) { done(); return; } + done(new Error('query ran even though skip present')); + }, 25); + }); + }); diff --git a/test/react-web/server/index.tsx b/test/react-web/server/index.tsx index fb5768ca55..81e7f8ad65 100644 --- a/test/react-web/server/index.tsx +++ b/test/react-web/server/index.tsx @@ -140,6 +140,60 @@ describe('SSR', () => { ; }); + it('should correctly handle SSR mutations', (done) => { + + const query = gql`{ currentUser { firstName } }`; + const data1 = { currentUser: { firstName: 'James' } }; + + const mutation = gql`mutation { logRoutes { id } }`; + const mutationData= { logRoutes: { id: 'foo' } }; + + const networkInterface = mockNetworkInterface( + { request: { query }, result: { data: data1 }, delay: 5 }, + { request: { query: mutation }, result: { data: mutationData }, delay: 5 } + ); + const apolloClient = new ApolloClient({ networkInterface }); + + const withQuery = graphql(query, { + options: (ownProps) => ({ ssr: true }), + props: ({ data }) => { + expect(data.refetch).to.exist; + return { + refetchQuery: data.refetch, + data, + }; + }, + }); + + const withMutation = graphql(mutation, { + props: ({ ownProps, mutate }) => { + expect(ownProps.refetchQuery).to.exist; + return { + action(variables) { + return mutate({ variables }).then(() => ownProps.refetchQuery()); + }, + }; + }, + }); + + const Element = (({ data }) => ( +
{data.loading ? 'loading' : data.currentUser.firstName}
+ )); + + const WrappedElement = withQuery(withMutation(Element)); + + const app = (); + + getDataFromTree(app) + .then(() => { + const markup = ReactDOM.renderToString(app); + expect(markup).to.match(/James/); + done(); + }) + .catch(console.error) + ; + }); + }); describe('`renderToStringWithData`', () => {