Passing props / arguments to HOCs - node.js

I am trying to pass props (or an argument) to this HOC. I would like to be able to set the uri here dynamically:
import withApollo from 'next-with-apollo';
import ApolloClient, { InMemoryCache } from 'apollo-boost';
import { ApolloProvider } from '#apollo/react-hooks';
export default withApollo(
({ initialState }) => {
return new ApolloClient({
uri: 'https://example.com/graphql',
cache: new InMemoryCache().restore(initialState || {})
});
},
{
render: ({ Page, props }) => {
return (
<ApolloProvider client={props.apollo}>
<Page {...props} />
</ApolloProvider>
);
}
}
);
I don't really use HOCs normally, so not sure the best way to go about this. If I pass the variable to as a Page prop. How can I access it in the ApolloClient object? Or if that's not possible how would I set an argument here and then access it in the HOC?
export default withApollo(HomePage);
Thanks for any help offered!

Related

How to use next-i18next with Redux Persist in NodeJS?

I have error when combine appWithTranslation and PersistGate:
Unhandled Runtime Error
NotFoundError: The object can not be found here.
My code:
import { appWithTranslation } from 'next-i18next'
import { Provider } from 'react-redux'
import { persistStore } from 'redux-persist'
import { PersistGate } from 'redux-persist/integration/react'
import { useStore } from '../store'
const MyApp = ({ Component, pageProps }) => {
const store = useStore(pageProps.initialReduxState)
const persistor = persistStore(store, {}, () => { persistor.persist() })
return (
<Provider store={store}>
<PersistGate loading={<div>loading</div>} persistor={persistor}>
<Component { ...pageProps } />
</PersistGate>
</Provider>
)
}
export default appWithTranslation(MyApp)
Firstly it loading successfully, but when I change locale I get error.
And no traceback! Just stops working -- components are not displayed, only loading (from PersistGate)

RTK Query in Redux-Toolkit is returning data of undefined, when I console.log the data it appears in console

I was trying to display an array of data fetched from my custom server with RTK Query using Next.js (React framework). And this is my first time using RTK Query. Whenever I console.log the data, it appears in the browser console. But whenever I try to map the data to render it in the browser, it keeps throwing an error saying Cannot read properties of undefined (reading 'map').
I figured Next.js always throws an error if an initial state is undefined or null even if the state change. This link talked about solving the problem using useMemo hook https://redux.js.org/tutorials/essentials/part-7-rtk-query-basics
But I didn't understand it well. Please kindly help me out with displaying the data.
Here is the BaseQuery function example I followed, it was derived from redux toolkit docmentation https://redux-toolkit.js.org/rtk-query/usage/customizing-queries#axios-basequery
import axios from "axios";
const axiosBaseQuery =
({ baseUrl } = { baseUrl: "" }) =>
async ({ url, method, data }) => {
try {
const result = await axios({ url: baseUrl + url, method, data });
return { data: result.data };
} catch (axiosError) {
let err = axiosError;
return {
error: { status: err.response?.status, data: err.response?.data },
};
}
};
export default axiosBaseQuery;
I make the GET request here
import { createApi } from "#reduxjs/toolkit/query/react";
import axiosBaseQuery from "./axiosBaseQuery";
export const getAllCarsApi = createApi({
reducerPath: "getAllCarsApi",
baseQuery: axiosBaseQuery({
baseUrl: "http://localhost:5000/",
}),
endpoints(build) {
return {
getAllCars: build.query({
query: () => ({ url: "all-cars", method: "get" }),
}),
};
},
});
export const { useGetAllCarsQuery } = getAllCarsApi;
This is my redux store
import { configureStore } from "#reduxjs/toolkit";
import { getAllCarsApi } from "./getAllCarsApi";
import { setupListeners } from "#reduxjs/toolkit/dist/query";
const store = configureStore({
reducer: { [getAllCarsApi.reducerPath]: getAllCarsApi.reducer },
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(getAllCarsApi.middleware),
});
setupListeners(store.dispatch);
export default store;
I provide the store to the _app.js file.
import "../styles/globals.css";
import axios from "axios";
import { MyContextProvider } from "#/store/MyContext";
import { Provider } from "react-redux";
import store from "#/store/ReduxStore/index";
axios.defaults.withCredentials = true;
function MyApp({ Component, pageProps }) {
return (
<MyContextProvider>
<Provider store={store}>
<Component {...pageProps} />
</Provider>
</MyContextProvider>
);
}
export default MyApp;
I get the data here in my frontend.
import { useGetAllCarsQuery } from "#/store/ReduxStore/getAllCarsApi";
const theTest = () => {
const { data, isLoading, error } = useGetAllCarsQuery();
return (
<div>
{data.map((theData, i) => (
<h1 key={i}>{theData}</h1>
))}
<h1>Hello</h1>
</div>
);
};
export default theTest;
This is a timing thing.
Your component will always render immediately and it will not defer rendering until data is there. That means it will also render before your data has been fetched. So while the data is still loading, data is undefined - and you try to map over that.
You could do things like just checking if data is there to deal with that:
const theTest = () => {
const { data, isLoading, error } = useGetAllCarsQuery();
return (
<div>
{data && data.map((theData, i) => (
<h1 key={i}>{theData}</h1>
))}
<h1>Hello</h1>
</div>
);
};

next-i18next Jest Testing with useTranslation

Testing libs...always fun. I am using next-i18next within my NextJS project. We are using the useTranslation hook with namespaces.
When I run my test there is a warning:
console.warn
react-i18next:: You will need to pass in an i18next instance by using initReactI18next
> 33 | const { t } = useTranslation(['common', 'account']);
| ^
I have tried the setup from the react-i18next test examples without success. I have tried this suggestion too.
as well as just trying to mock useTranslation without success.
Is there a more straightforward solution to avoid this warning? The test passes FWIW...
test('feature displays error', async () => {
const { findByTestId, findByRole } = render(
<I18nextProvider i18n={i18n}>
<InviteCollectEmails onSubmit={jest.fn()} />
</I18nextProvider>,
{
query: {
orgId: 666,
},
}
);
const submitBtn = await findByRole('button', {
name: 'account:organization.invite.copyLink',
});
fireEvent.click(submitBtn);
await findByTestId('loader');
const alert = await findByRole('alert');
within(alert).getByText('failed attempt');
});
Last, is there a way to have the translated plain text be the outcome, instead of the namespaced: account:account:organization.invite.copyLink?
Use the following snippet before the describe block OR in beforeEach() to mock the needful.
jest.mock("react-i18next", () => ({
useTranslation: () => ({ t: key => key }),
}));
Hope this helps. Peace.
use this for replace render function.
import { render, screen } from '#testing-library/react'
import DarkModeToggleBtn from '../../components/layout/DarkModeToggleBtn'
import { appWithTranslation } from 'next-i18next'
import { NextRouter } from 'next/router'
jest.mock('react-i18next', () => ({
I18nextProvider: jest.fn(),
__esmodule: true,
}))
const createProps = (locale = 'en', router: Partial<NextRouter> = {}) => ({
pageProps: {
_nextI18Next: {
initialLocale: locale,
userConfig: {
i18n: {
defaultLocale: 'en',
locales: ['en', 'fr'],
},
},
},
} as any,
router: {
locale: locale,
route: '/',
...router,
},
} as any)
const Component = appWithTranslation(() => <DarkModeToggleBtn />)
const defaultRenderProps = createProps()
const renderComponent = (props = defaultRenderProps) => render(
<Component {...props} />
)
describe('', () => {
it('', () => {
renderComponent()
expect(screen.getByRole("button")).toHaveTextContent("")
})
})
I used a little bit more sophisticated approach than mocking to ensure all the functions work the same both in testing and production environment.
First, I create a testing environment:
// testing/env.ts
import i18next, { i18n } from "i18next";
import JSDomEnvironment from "jest-environment-jsdom";
import { initReactI18next } from "react-i18next";
declare global {
var i18nInstance: i18n;
}
export default class extends JSDomEnvironment {
async setup() {
await super.setup();
/* The important part start */
const i18nInstance = i18next.createInstance();
await i18nInstance.use(initReactI18next).init({
lng: "cimode",
resources: {},
});
this.global.i18nInstance = i18nInstance;
/* The important part end */
}
}
I add this environment in jest.config.ts:
// jest.config.ts
export default {
// ...
testEnvironment: "testing/env.ts",
};
Sample component:
// component.tsx
import { useTranslation } from "next-i18next";
export const Component = () => {
const { t } = useTranslation();
return <div>{t('foo')}</div>
}
And later on I use it in tests:
// component.test.tsx
import { setI18n } from "react-i18next";
import { create, act, ReactTestRenderer } from "react-test-renderer";
import { Component } from "./component";
it("renders Component", () => {
/* The important part start */
setI18n(global.i18nInstance);
/* The important part end */
let root: ReactTestRenderer;
act(() => {
root = create(<Component />);
});
expect(root.toJSON()).toMatchSnapshot();
});
I figured out how to make the tests work with an instance of i18next using the renderHook function and the useTranslation hook from react-i18next based on the previous answers and some research.
This is the Home component I wanted to test:
import { useTranslation } from 'next-i18next';
const Home = () => {
const { t } = useTranslation("");
return (
<main>
<div>
<h1> {t("welcome", {ns: 'home'})}</h1>
</div>
</main>
)
};
export default Home;
First, we need to create a setup file for jest so we can start an i18n instance and import the translations to the configuration. test/setup.ts
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import homeES from '#/public/locales/es/home.json';
import homeEN from '#/public/locales/en/home.json';
i18n.use(initReactI18next).init({
lng: "es",
resources: {
en: {
home: homeEN,
},
es: {
home: homeES,
}
},
fallbackLng: "es",
debug: false,
});
export default i18n;
Then we add the setup file to our jest.config.js:
setupFilesAfterEnv: ["<rootDir>/test/setup.ts"]
Now we can try our tests using the I18nextProvider and the useTranslation hook:
import '#testing-library/jest-dom/extend-expect';
import { cleanup, render, renderHook } from '#testing-library/react';
import { act } from 'react-dom/test-utils';
import { I18nextProvider, useTranslation } from 'react-i18next';
import Home from '.';
describe("Index page", (): void => {
afterEach(cleanup);
it("should render properly in Spanish", (): void => {
const t = renderHook(() => useTranslation());
const component = render(
<I18nextProvider i18n={t.result.current.i18n}>
<Home / >
</I18nextProvider>
);
expect(component.getByText("Bienvenido a Pocky")).toBeInTheDocument();
});
it("should render properly in English", (): void => {
const t = renderHook(() => useTranslation());
act(() => {
t.result.current.i18n.changeLanguage("en");
});
const component = render(
<I18nextProvider i18n={t.result.current.i18n}>
<Home/>
</I18nextProvider>
);
expect(component.getByText("Welcome to Pocky")).toBeInTheDocument();
});
});
Here we used the I18nextProvider and send the i18n instance using the useTranslation hook. after that the translations were loaded without problems in the Home component.
We can also change the selected language running the changeLanguage() function and test the other translations.

GSAP timeline needed on every page in Gatsby

My Gatsby site use the same GSAP timeline on every page, so I want to stay DRY and my idea is to include my timeline in my Layout component in that order.
But I don't know how to pass refs that I need between children and layout using forwardRef.
In short, I don't know how to handle the sectionsRef part between pages and layout.
sectionsRef is dependant of the page content (children) but is needed in the timeline living in layout.
How can I share sectionsRef between these two (I tried many things but always leading to errors)?
Here's a codesandbox without the refs in the Layout:
https://codesandbox.io/s/jolly-almeida-njt2e?file=/src/pages/index.js
And the sandbox with the refs in the layout:
https://codesandbox.io/s/pensive-varahamihira-tc45m?file=/src/pages/index.js
Here's a simplified version of my files :
Layout.js
export default function Layout({ children }) {
const containerRef = useRef(null);
const sectionsRef = useRef([]);
sectionsRef.current = [];
useEffect(() => {
gsap.registerPlugin(ScrollTrigger);
const scrollTimeline = gsap.timeline();
scrollTimeline.to(sectionsRef.current, {
x: () =>
`${-(
containerRef.current.scrollWidth -
document.documentElement.clientWidth
)}px`,
ease: 'none',
scrollTrigger: {
trigger: containerRef.current,
invalidateOnRefresh: true,
scrub: 0.5,
pin: true,
start: () => `top top`,
end: () =>
`+=${
containerRef.current.scrollWidth -
document.documentElement.clientWidth
}`,
},
});
}, [containerRef, sectionsRef]);
return (
<div className="slides-container" ref={containerRef}>
{children}
</div>
);
}
index.js (page)
import { graphql } from 'gatsby';
import React, { forwardRef } from 'react';
import SectionImage from '../components/sections/SectionImage';
import SectionIntro from '../components/sections/SectionIntro';
import SectionColumns from '../components/sections/SectionColumns';
const HomePage = ({ data: { home } }, sectionsRef) => {
const { sections } = home;
const addToRefs = (el) => {
if (el && !sectionsRef.current.includes(el)) {
sectionsRef.current.push(el);
}
};
return (
<>
{sections.map((section) => {
if (section.__typename === 'SanitySectionIntro') {
return (
<SectionIntro key={section.id} section={section} ref={addToRefs} />
);
}
if (section.__typename === 'SanitySectionImage') {
return (
<SectionImage key={section.id} section={section} ref={addToRefs} />
);
}
if (section.__typename === 'SanitySectionColumns') {
return (
<SectionColumns
key={section.id}
section={section}
ref={addToRefs}
/>
);
}
return '';
})}
</>
);
};
export default forwardRef(HomePage);
export const query = graphql`
query HomeQuery {
// ...
}
`;
Any clue greatly appreciated :)

NextJS: TypeError: Cannot read property 'headers' of undefined

I am receiving this error in getInitialProps function attached to my page:
const getInitialProps = async ({ req, query }: NextPageContext) => {
let isValidReferenceQuery = true;
const { path } = absoluteUrl(req as IncomingMessage);
try {
await policyService.getPolicyByReference(
path,
query.referenceQuery as string
);
} catch (error) {
isValidReferenceQuery = false;
}
return {
referenceQuery: (query.referenceQuery as string) ?? null,
isValidReferenceQuery,
};
};
Page.getInitialProps = getInitialProps;
The function absoluteUrl determines if the API call was made server-side or client-side and adjusts the path accordingly:
export default function absoluteUrl(
req: IncomingMessage
): Record<string, string> {
const protocol = req.headers.referer?.split('//')[0] ?? 'http:';
const host = req
? req.headers['x-forwarded-host'] || req.headers.host
: window.location.host;
const path =
host === 'localhost:3000'
? `${protocol}//localhost:8080`
: `${protocol}//${host}${axios.defaults.baseURL}`;
return {
path,
};
}
I have updated my _document.tsx & _app.tsx to include getInitialProps as described to opt-out of automatic static generation in development mode so req should be defined. We also need all pages to be SSR for localisation:
import React from 'react';
import App, { AppContext } from 'next/app';
import { ReactQueryCacheProvider, QueryCache } from 'react-query';
import { Hydrate } from 'react-query/hydration';
import { appWithTranslation } from '#/localisation/i18n';
import { AppPageProps } from '#/models/page';
const TextMiningApp = ({ Component, pageProps }: AppPageProps) => {
const queryCache = new QueryCache();
const Layout = Component.Layout ? Component.Layout : React.Fragment;
return (
<ReactQueryCacheProvider queryCache={queryCache}>
<Hydrate state={pageProps.dehydratedState}>
<Layout>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<Component {...pageProps} />
</Layout>
</Hydrate>
</ReactQueryCacheProvider>
);
};
TextMiningApp.getInitialProps = async (appContext: AppContext) => ({
...(await App.getInitialProps(appContext)),
});
export default appWithTranslation(TextMiningApp);
I cannot see why if i have opted out of automatic static generation that req is undefined?
Not sure if this is still needed, but currently getInitialProps in _app.js actually works a little differently than pages. Instead of directly passing context as an argument, it passes an object with ctx and Component properties. So you would need to do something like this:
TextMiningApp.getInitialProps = async( { ctx, Component } ) => {
// This should be working now.
console.log( ctx.req );
}
One catch is that App.getInitialProps it needs to be passed all properties from the original argument:
App.getInitialProps( ctx, Component );
See https://github.com/vercel/next.js/discussions/14913

Resources