React Apollo with react-hooks useEffect warning in console - reactjs

The following console warning has been dogging me for days...
Warning: The final argument passed to useEffect changed size between renders. The order and size of this array must remain constant. react-dom.development.js:530
Previous: [true, 1, , ]
Incoming: [false, 7, , [object Object], function () { return queryData.afterExecute({ lazy: lazy }); }, 0]
in SampleQuery (created by App)
in ApolloProvider (created by App)
in App
Warning: The final argument passed to useEffect changed size between renders. The order and size of this array must remain constant. react-dom.development.js:530
Previous: []
Incoming: [function () {
return function () { return queryData.cleanup(); };
}]
in SampleQuery (created by App)
in ApolloProvider (created by App)
in App
The code for the app is as follows...
app.tsx
import React from 'react';
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import {ApolloProvider} from '#apollo/react-hooks';
import SampleQuery from "./sample-query";
interface AppProps {
compiler: string;
framework: string;
}
const App = (props: AppProps) => {
const cache = new InMemoryCache();
const link = new HttpLink({
uri: 'http://localhost:4000/',
});
const client = new ApolloClient({
// Provide required constructor fields
cache: cache,
link: link,
// Provide some optional constructor fields
name: 'react-web-client',
version: '1.3',
queryDeduplication: false,
defaultOptions: {
watchQuery: {
fetchPolicy: 'cache-and-network',
},
}
});
return (
<ApolloProvider client={client}>
<h1>Hello from {props.compiler} and {props.framework}!</h1>
<SampleQuery />
</ApolloProvider>
);
};
export default App;
sample-query.tsx...
import React from 'react'
import { useQuery } from '#apollo/react-hooks';
import gql from 'graphql-tag'
interface Link {
id: string;
description: string;
}
const SAMPLE_QUERY = gql`{
feed {
links {
id
description
}
}
}`;
const SampleQuery = () => {
const { loading, error, data } = useQuery(SAMPLE_QUERY);
if (loading) return <p>Loading ...</p>;
const links = (data && data.feed.links) || [];
return (<ul>
{links.map((link:Link, idx:number) => {
return (
<li key={idx}>
<span>{link.id}</span>
<span> : </span>
<span>{link.description}</span>
</li>
);
})}
</ul>)
};
export default SampleQuery;
I'm unsure why I get the warning as I'm pretty close to the examples from the React Apollo docs. Hopefully someone here has experienced this before and could point me in the right direction.

Related

Shopify App using React + Node throwing error

i'm creating a app in Shopify using React + Node but it is throwing error as :
Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
I don't know why this is happening. Any suggestions would be much appreciated. Thanks in advance.
HomePage.jsx
import {Page} from "#shopify/polaris";
import React, {Component} from 'react';
import { ResourcePicker } from "#shopify/app-bridge/actions";
class HomePage extends Component {
constructor(props) {
super(props);
this.state = {
open : false,
}
}
render () {
return (
<Page
fullWidth
title='Product Selector'
primaryAction={{
content: 'Select Products',
onAction: () => this.setState({open: true})
}}>
<ResourcePicker
resourceType = 'Product'
open={true}
/>
</Page>
)
}
}
export default HomePage;
App.jsx
import {
ApolloClient,
ApolloProvider,
HttpLink,
InMemoryCache,
} from "#apollo/client";
import {
Provider as AppBridgeProvider,
useAppBridge,
} from "#shopify/app-bridge-react";
import { authenticatedFetch } from "#shopify/app-bridge-utils";
import { Redirect } from "#shopify/app-bridge/actions";
import { AppProvider as PolarisProvider } from "#shopify/polaris";
import translations from "#shopify/polaris/locales/en.json";
import "#shopify/polaris/build/esm/styles.css";
import HomePage from "./components/HomePage";
export default function App() {
return (
<PolarisProvider i18n={translations}>
<AppBridgeProvider
config={{
apiKey: process.env.SHOPIFY_API_KEY,
host: new URL(location).searchParams.get("host"),
forceRedirect: true,
}}
>
<MyProvider>
<HomePage />
</MyProvider>
</AppBridgeProvider>
</PolarisProvider>
);
}
function MyProvider({ children }) {
const app = useAppBridge();
const client = new ApolloClient({
cache: new InMemoryCache(),
link: new HttpLink({
credentials: "include",
fetch: userLoggedInFetch(app),
}),
});
return <ApolloProvider client={client}>{children}</ApolloProvider>;
}
export function userLoggedInFetch(app) {
const fetchFunction = authenticatedFetch(app);
return async (uri, options) => {
const response = await fetchFunction(uri, options);
if (
response.headers.get("X-Shopify-API-Request-Failure-Reauthorize") === "1"
) {
const authUrlHeader = response.headers.get(
"X-Shopify-API-Request-Failure-Reauthorize-Url"
);
const redirect = Redirect.create(app);
redirect.dispatch(Redirect.Action.APP, authUrlHeader || `/auth`);
return null;
}
return response;
};
}
I think it's this line
host: new URL(location).searchParams.get("host"),
which might return null if host not found
I recommend adding the host to env variables and use it like
host: process.env.host

paypal is not defined in paypal.Buttons.driver("react", { React, ReactDOM });

I want to develop a PayPal button, and following the API documentation, I have the following code:
import React, {useState}from 'react';
import {useDispatch} from 'react-redux';
import {useHistory} from 'react-router-dom';
import ReactDOM from 'react-dom';
import * as actionsReservations from '../../reservation/actions';
import {Errors} from '..';
const PayPalButton = paypal.Buttons.driver("react", { React, ReactDOM });
const PayPalReserve = ({deposit, menu, companyId, reservationDate, periodType, diners}) => {
const dispatch = useDispatch();
const history = useHistory();
const [backendErrors, setBackendErrors] = useState(null);
const createOrder = (data,actions) => {
return actions.order.create({
purchase_units:[
{
amount:{
value: deposit
},
},
],
});
};
const onApprove = (data, actions) => {
return actions.order.capture().then(response => {
dispatch(actionsReservations.reservation(
menu.id,
companyId,
reservationDate,
periodType,
diners,
response.id,
() => history.push('/reservation/reservation-completed'),
errors => setBackendErrors(errors)
));
console.log(response);
});
}
};
export default PayPalReserve;
But is throwing me the following error:
Line 9:22: 'paypal' is not defined no-undef
But if I import paypal from paypal-checkout with this line:
import paypal from 'paypal-checkout';
React throw me the following error:
"TypeError: paypal_checkout__WEBPACK_IMPORTED_MODULE_4___default.a.Buttons is undefined"
My index.html i have this in head tag:
<script defer src="https://www.paypal.com/sdk/js?client-id=MY_CLIENT_CODE"></script>
I dont know why is throwing me these errors when i don't import paypal-checkout or when I import it. If you knew how to solve it, I would appreciate it
Thanks.
Things will probably become easier for you if you simply use the official react-paypal-js package.
Here is the storybook .. copying its example:
import { PayPalScriptProvider, PayPalButtons } from "#paypal/react-paypal-js";
<PayPalScriptProvider options={{ "client-id": "test" }}>
<PayPalButtons
style={{ layout: "horizontal" }}
createOrder={(data, actions) => {
return actions.order.create({
purchase_units: [
{
amount: {
value: "2.00",
},
},
],
});
}}
/>;
</PayPalScriptProvider>
That’s an eslint err, just add window at the beginning as PayPal is a global variable injected into the window obj
console.log(window.paypal)

Prevent client side re-render when using SSR and Apollo client

Problem in a nutshell is I server side render an html doc then the React app hydrates and re-renders what is already there. After that point the app works client side just great.
I am using React, Apollo Client (Boost 0.3.1) , Node, Express, and a graphql server we have in house.
See this in action here: https://www.slowdownshow.org/
Mostly I have tried what is suggested in the docs:
https://www.apollographql.com/docs/react/features/server-side-rendering
Here is what is not clear. Am I to assume that if I implement Store Rehydration the Apollo Client xhr request to fetch the data will not need to happen? If so the problem is I've tried what the docs suggest for store rehydration, but the doc is a little ambiguous
<script>
window.__APOLLO_STATE__ = JSON.stringify(client.extract());
</script>
What is client in this case? I believe it is the ApolloClient. But it is a method not an object, if I use that here I get error messages like
Warning: Failed context type: Invalid contextclientof typefunctionsupplied toComponent, expectedobject.
If the Store Rehydration technique is not the way to prevent unnecessary client side re-renders - it's not clear to me what is.
Here is the relevant server code:
import React from 'react';
import ReactDOM from 'react-dom/server';
import { ApolloProvider, renderToStringWithData } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import FragmentMatcher from '../shared/graphql/FragmentMatcher';
import { HelmetProvider } from 'react-helmet-async';
import { ServerLocation } from 'apm-titan';
import App from '../shared/App';
import fs from 'fs';
import os from 'os';
import {
globalHostFunc,
replaceTemplateStrings,
isFresh,
apm_etag,
siteConfigFunc
} from './utils';
export default function ReactAppSsr(app) {
app.use((req, res) => {
const helmetContext = {};
const filepath =
process.env.APP_PATH === 'relative' ? 'build' : 'current/build';
const forwarded = globalHostFunc(req).split(':')[0];
const siteConfig = siteConfigFunc(forwarded);
const hostname = os.hostname();
const context = {};
const cache = new InMemoryCache({ fragmentMatcher: FragmentMatcher });
let graphqlEnv = hostname.match(/dev/) ? '-dev' : '';
graphqlEnv = process.env.NODE_ENV === 'development' ? '-dev' : graphqlEnv;
const graphqlClient = (graphqlEnv) => {
return new ApolloClient({
ssrMode: false,
cache,
link: createHttpLink({
uri: `https://xxx${graphqlEnv}.xxx.org/api/v1/graphql`,
fetch: fetch
})
});
};
let template = fs.readFileSync(`${filepath}/index.html`).toString();
const component = (
<ApolloProvider client={graphqlClient}>
<HelmetProvider context={helmetContext}>
<ServerLocation url={req.url} context={context}>
<App forward={forwarded} />
</ServerLocation>
</HelmetProvider>
</ApolloProvider>
);
renderToStringWithData(component).then(() => {
const { helmet } = helmetContext;
let str = ReactDOM.renderToString(component);
const is404 = str.match(/Not Found\. 404/);
if (is404?.length > 0) {
str = 'Not Found 404.';
template = replaceTemplateStrings(template, '', '', '', '');
res.status(404);
res.send(template);
return;
}
template = replaceTemplateStrings(
template,
helmet.title.toString(),
helmet.meta.toString(),
helmet.link.toString(),
str
);
template = template.replace(/__GTMID__/g, `${siteConfig.gtm}`);
const apollo_state = ` <script>
window.__APOLLO_STATE__ = JSON.stringify(${graphqlClient.extract()});
</script>
</body>`;
template = template.replace(/<\/body>/, apollo_state);
res.set('Cache-Control', 'public, max-age=120');
res.set('ETag', apm_etag(str));
if (isFresh(req, res)) {
res.status(304);
res.send();
return;
}
res.send(template);
res.status(200);
});
});
}
client side:
import App from '../shared/App';
import React from 'react';
import { hydrate } from 'react-dom';
import { ApolloProvider } from 'react-apollo';
import { HelmetProvider } from 'react-helmet-async';
import { client } from '../shared/graphql/graphqlClient';
import '#babel/polyfill';
const graphqlEnv = window.location.href.match(/local|dev/) ? '-dev' : '';
const graphqlClient = client(graphqlEnv);
const Wrapped = () => {
const helmetContext = {};
return (
<HelmetProvider context={helmetContext}>
<ApolloProvider client={graphqlClient}>
<App />
</ApolloProvider>
</HelmetProvider>
);
};
hydrate(<Wrapped />, document.getElementById('root'));
if (module.hot) {
module.hot.accept();
}
graphqlCLinet.js:
import fetch from 'cross-fetch';
import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import FragmentMatcher from './FragmentMatcher';
const cache = new InMemoryCache({ fragmentMatcher: FragmentMatcher });
export const client = (graphqlEnv) => {
return new ApolloClient({
ssrMode: true,
cache,
link: createHttpLink({
uri: `https://xxx${graphqlEnv}.xxx.org/api/v1/graphql`,
fetch: fetch
})
});
};
FragmentMatcher.js:
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
const FragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData: {
__schema: {
types: [
{
kind: 'INTERFACE',
name: 'resourceType',
possibleTypes: [
{ name: 'Episode' },
{ name: 'Link' },
{ name: 'Page' },
{ name: 'Profile' },
{ name: 'Story' }
]
}
]
}
}
});
export default FragmentMatcher;
See client side re-renders in action
https://www.slowdownshow.org/
In the production version of the code above,
I skip state rehydration window.__APOLLO_STATE__ = JSON.stringify(${graphqlClient.extract()}); as I do not have it working
So the answer was simple once I realized I was making a mistake. I needed to put
window.__APOLLO_STATE__ = JSON.stringify(client.extract());
</script>
BEFORE everything else so it could be read and used.
This const apollo_state = ` <script>
window.__APOLLO_STATE__ = JSON.stringify(${graphqlClient.extract()});
</script>
</body>`;
template = template.replace(/<\/body>/, apollo_state);
needed to go up by the <head> not down by the body. Such a no duh now but tripped me up for a while

Mocking apollo link state

I am trying to mock a query #client and I am not getting.
I mocked the query from graphql server correctly and it's working.
import React from 'react';
import renderer from 'react-test-renderer';
import wait from 'waait';
import ExchangeRates from './ExchangeRates';
import { MockedProvider } from 'react-apollo/test-utils';
import { sucessMockrates, errorMockrates } from '../../mocks/exchangeRatesMock';
describe('ExchangeRates', () => {
it('should render rate', async () => {
const component = renderer.create(
<MockedProvider mocks={[sucessMockrates]} addTypename={false}>
<ExchangeRates />
</MockedProvider>
);
await wait(0);
const p = component.root.findByType('p');
expect(p.children).toContain('AED: 3.67');
});
it('should render loading state initially', () => {
const component = renderer.create(
<MockedProvider mocks={[]}>
<ExchangeRates />
</MockedProvider>
);
const tree = component.toJSON();
expect(tree.children).toContain('Loading...');
});
it('should show error UI', async () => {
const component = renderer.create(
<MockedProvider mocks={[errorMockrates]} addTypename={false}>
<ExchangeRates />
</MockedProvider>
);
await wait(0);
const tree = component.toJSON();
expect(tree.children).toContain('Error!');
});
});
I am using the graphql server link from apollo tutorial
But when I tried to test the apollo query with local state I got an error.
My query:
import gql from 'graphql-tag';
export default gql`
query {
allocations #client {
list
}
}
`;
and my apollo client setup:
const cache = new InMemoryCache();
const defaultState = {
allocations: {
__typename: 'Allocations',
list: [],
},
};
const listQuery = gql`
query getAllocations {
allocations #client {
list
}
}
`;
const stateLink = withClientState({
cache,
defaults: defaultState,
resolvers: {
addAllocation: (
_,
{ userName },
{ cache }
) => {
const previousState = cache.readQuery({ query: listQuery });
const { list } = previousState.allocations;
const data = {
...previousState,
allocations: {
...previousState.allocations,
list: [
...list,
{
userName
},
],
},
};
cache.writeQuery({ query: listQuery, data });
return data.allocations;
},
},
},
});
const client = new ApolloClient({
link: ApolloLink.from([
stateLink,
new HttpLink({
uri: 'https://w5xlvm3vzz.lp.gql.zone/graphql',
}),
]),
cache,
});
My test with apollo local state:
import React from 'react';
import renderer from 'react-test-renderer';
import AllocationListPage from './AllocationListPage';
import { MockedProvider } from 'react-apollo/test-utils';
import { sucessMockAllocations } from '../../../mocks/allocationListMock';
describe('AllocationListPage', () => {
it('should render list of allocations', () => {
renderer.create(
<MockedProvider mocks={[sucessMockAllocations]} addTypename={false}>
<AllocationListPage />
</MockedProvider>
);
});
});
The error I got: TypeError:
Cannot destructure property list of 'undefined' or 'null'.
I need to mock the initial state of apollo local state, and I don't know how.
Thanks in advance.
I got setup my apollo link state with this component:
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { ApolloProvider } from 'react-apollo';
import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools';
import { ApolloClient } from 'apollo-client';
import { stateLink, cache } from '../graphql/stateLink';
import { ApolloLink } from 'apollo-link';
import { SchemaLink } from 'apollo-link-schema';
const setupClient = mocks => {
const typeDefs = `
type Query {
test: String!
}
`;
const schema = makeExecutableSchema({ typeDefs });
addMockFunctionsToSchema({
schema,
mocks,
preserveResolvers: false,
});
return new ApolloClient({
cache,
link: ApolloLink.from([stateLink, new SchemaLink({ schema })]),
});
};
class ApolloLinkStateSetup extends PureComponent {
render() {
return (
<ApolloProvider client={setupClient(this.props.mocks)}>
{this.props.children}
</ApolloProvider>
);
}
}
ApolloLinkStateSetup.defaultProps = {
mocks: {},
};
ApolloLinkStateSetup.propTypes = {
children: PropTypes.object.isRequired,
mocks: PropTypes.object,
};
export default ApolloLinkStateSetup;
You can mock the graphql queries with makeExecutableSchema and addMockFunctionsToSchema from graphql-tools. This mock can be useful to create the front-end side without the back-end side.

How to mock react-router context

I've got fairly simple react component (Link wrapper which adds 'active' class if route is active):
import React, { PropTypes } from 'react';
import { Link } from 'react-router';
const NavLink = (props, context) => {
const isActive = context.router.isActive(props.to, true);
const activeClass = isActive ? 'active' : '';
return (
<li className={activeClass}>
<Link {...props}>{props.children}</Link>
</li>
);
}
NavLink.contextTypes = {
router: PropTypes.object,
};
NavLink.propTypes = {
children: PropTypes.node,
to: PropTypes.string,
};
export default NavLink;
How am I supposed to test it? My only attempt was:
import NavLink from '../index';
import expect from 'expect';
import { mount } from 'enzyme';
import React from 'react';
describe('<NavLink />', () => {
it('should add active class', () => {
const renderedComponent = mount(<NavLink to="/home" />, { router: { pathname: '/home' } });
expect(renderedComponent.hasClass('active')).toEqual(true);
});
});
It doesn't work and returns TypeError: Cannot read property 'isActive' of undefined. It definitely needs some router mocking, but I have no idea how to write it.
Thanks #Elon Szopos for your answer but I manage to write something much more simple (following https://github.com/airbnb/enzyme/pull/62):
import NavLink from '../index';
import expect from 'expect';
import { shallow } from 'enzyme';
import React from 'react';
describe('<NavLink />', () => {
it('should add active class', () => {
const context = { router: { isActive: (a, b) => true } };
const renderedComponent = shallow(<NavLink to="/home" />, { context });
expect(renderedComponent.hasClass('active')).toEqual(true);
});
});
I have to change mount to shallow in order not to evaluate Link which gives me an error connected with the react-router TypeError: router.createHref is not a function.
I would rather have "real" react-router than just an object but I have no idea how to create it.
For react router v4 you can use a <MemoryRouter>. Example with AVA and Enzyme:
import React from 'react';
import PropTypes from 'prop-types';
import test from 'ava';
import { mount } from 'enzyme';
import sinon from 'sinon';
import { MemoryRouter as Router } from 'react-router-dom';
const mountWithRouter = node => mount(<Router>{node}</Router>);
test('submits form directly', t => {
const onSubmit = sinon.spy();
const wrapper = mountWithRouter(<LogInForm onSubmit={onSubmit} />);
const form = wrapper.find('form');
form.simulate('submit');
t.true(onSubmit.calledOnce);
});
Testing components which rely on the context can be a little tricky. What I did was to write a wrapper that I used in my tests.
You can find the wrapper below:
import React, { PropTypes } from 'react'
export default class WithContext extends React.Component {
static propTypes = {
children: PropTypes.any,
context: PropTypes.object
}
validateChildren () {
if (this.props.children === undefined) {
throw new Error('No child components were passed into WithContext')
}
if (this.props.children.length > 1) {
throw new Error('You can only pass one child component into WithContext')
}
}
render () {
class WithContext extends React.Component {
getChildContext () {
return this.props.context
}
render () {
return this.props.children
}
}
const context = this.props.context
WithContext.childContextTypes = {}
for (let propertyName in context) {
WithContext.childContextTypes[propertyName] = PropTypes.any
}
this.validateChildren()
return (
<WithContext context={this.props.context}>
{this.props.children}
</WithContext>
)
}
}
Here you can see a sample usage:
<WithContext context={{ location: {pathname: '/Michael/Jackson/lives' }}}>
<MoonwalkComponent />
</WithContext>
<WithContext context={{ router: { isActive: true }}}>
<YourTestComponent />
</WithContext>
And it should work as you would expect.
You can use https://github.com/pshrmn/react-router-test-context for that exact purpose
"Create a pseudo context object that duplicates React Router's context.router structure. This is useful for shallow unit testing with Enzyme."
After installing it, you will be able to do something like
describe('my test', () => {
it('renders', () => {
const context = createRouterContext()
const wrapper = shallow(<MyComponent />, { context })
})
})

Resources