Using QueryClientProvider in a Wrapper - javascript

I want to use a single Wrapper Component in a library im providing. The wrapper should provide stuff like Context and QueryClient. The component looks like this:
import { QueryClient, QueryClientProvider } from "react-query";
const client = new QueryClient();
function Wrapper(props) {
return (
<QueryClientProvider client={client}>
{props.children}
</QueryClientProvider>
);
}
When wrapping children with the Wrapper useQuery throws an error that no QueryClientProvider is set. In this case App uses the useQuery Hook from react-query.
import { Wrapper } from "my-lib";
ReactDOM.render(
<React.StrictMode>
<Wrapper>
<App />
</Wrapper>
</React.StrictMode>,
document.getElementById("root")
);
At first i thought you need a QueryClientProvider directly above an useQuery hook. But for example in Storybook you can define one QueryClient for all stories together. Where did i went wrong?
Edit:
App component:
import { useQuery } from "react-query";
export const App = () => {
const data = useQuery("data", () =>
fetch("https://jsonplaceholder.typicode.com/todos/1").then((res) =>
res.json()
)
);
if (data.isError || data.isLoading) return <>Error/Loading</>;
return <>{data.data.title}</>;
};

Well a few steps lead to my goal:
In the library, put react-query into the devDependencies and peerDependencies.
In the app, put react-query into the dependencies.
In the library, exclude react-query from being bundled by adding it to your bundler config. In my case it's the vite.config.ts:
const path = require("path");
const { defineConfig } = require("vite");
module.exports = defineConfig({
build: {
lib: {
entry: path.resolve(__dirname, "src/index.ts"),
name: "my-lib",
formats: ["es", "umd"],
fileName: "my-lib",
},
rollupOptions: {
external: [
"react",
"react-dom",
"react-query",
],
},
},
});

Related

You cannot render a <Router> inside another <Router> -- using React Router 6 with Storybook

I am understanding the error message, but not sure how I should handle it in this case. I believe that using the decorator below is causing the issue, but the decorator is needed to use the component with Storybook.
Here is the error message:
You cannot render a <Router> inside another <Router>. You should never have more than one in your app.
Believe this is due to the decorator and I can only assume the BrowserRouter found way upstream in my app, but from what I understand, Storybook isn't loading my index file. So I'm unsure how to proceed.
Here is the component, simplified:
export const Component = () => {
...
return (
<Routes>
<Route path="/screening" element={<Screening {...propBag} />} />
</Routes>
);
};
Then, the Story:
import { Story, Meta } from '#storybook/react';
import { MemoryRouter } from 'react-router-dom';
import { Component } from '..';
export default {
title: 'Province',
component: Component,
decorators: [
(Story) => (
<MemoryRouter>
<Story />
</MemoryRouter>
)
],
} as Meta;
const Template: Story = (args) => <IntakeQuestionnaire {...args} />;
export const Province = Template.bind({});
Province.parameters = {};
Province.args = {};
Finally, the preview.js file:
import 'tailwindcss/tailwind.css';
import { MockedProvider } from '#apollo/client/testing';
import { i18n } from './i18next';
export const parameters = {
i18n,
locale: 'en',
locales: {
en: 'English',
fr: 'Franรงais',
},
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
apolloClient: { MockedProvider },
};
export const decorators = [
(Story) => (
<MemoryRouter>
<Story />
</MemoryRouter>
)
];
Not sure why, but removing the decorators array from preview.js file and putting it only in the component Story file fixed this issue. Less than ideal but at least I am unblocked now
export default {
title: 'Province',
component: Component,
decorators: [
(Story) => (
<MemoryRouter>
<Story />
</MemoryRouter>
)
],
} as Meta;
EDIT: see below comment - i was being silly with decorators

SSR with data fetch without state management library

I am trying to make a SSR react app, but not able to pass props from express to the component.
What mistake am i doing?
server.js
import AppPage2 from './src/project02/LandingPage';
......
......
server.get('/project2',async (req,res)=>{
const context = {data:'test'}
const sheet = new ServerStyleSheet();
const content = ReactDOMServer.renderToString(sheet.collectStyles(
<StaticRouter location={req.url} context={context}>
<AppPage2 state={{"foo":"baar"}}/>
</StaticRouter>)
);
const styles = sheet.getStyleTags();
let html = appShell( 'Project 2', content,' project2.bundle.js',styles)
res.status(200).send(html);
res.end()
})
AppPage2(./src/project02/LandingPage)
import React from 'react';
import {Wrapper,Title} from './styleComponent/testStylePage'
.......
.......
class AppPage extends React.Component {
componentDidMount() {
console.log("{{API}}",this,this.props, this.props.staticContext)
}
render(){
return(
<Wrapper>
<Title>This is project 01 </Title>
</Wrapper>
)
}
}
export default AppPage;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import AppPage from './project02/LandingPage'
ReactDOM.hydrate(
<AppPage></AppPage>,
document.querySelector('#root')
);
webpack.client.conf
const path = require('path');
const glob = require("glob");
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const entry = glob.sync("src/*.js")
.reduce((x, y) => Object.assign(x,
{
[y.replace('src/','').replace('.js','')]: `./${y}`,
}
), {});
module.exports = {
target: 'node',
mode: 'development',//development,production
entry: entry,
output: {
filename:'[name].bundle.js',
path: path.resolve(__dirname,'build/public/'),
publicPath: '/build/'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader'
},
},
]
},
plugins: [
// new BundleAnalyzerPlugin()
]
}
I am not able to log console.log("{{API}}",this,this.props, this.props.staticContext) from AppPage2(./src/project02/LandingPage) this page i am sending data from server.js(express)
Your props already passed to your component on Page2, but you're longing it using a function that will never been called,
componentDidMount() is invoked immediately after a component is mounted (inserted into the DOM tree).
in your case, you are not mounting any thing to the DOM, because react will only render your component to html and will not wait for your component to Mount, practically there is no DOM in your NodeJs server, and you're only rendering the component and return it as string, not inserting it to the DOM.
Your props are already there and you can console log them in your class constructor, and in your render method:
class AppPage extends React.Component {
constructor (props){
super(props)
console.log("{{API}}",this,this.props, this.props.staticContext)
}
render(){
//or console log it here
console.log("{{API}}",this,this.props, this.props.staticContext)
return(
<Wrapper>
<Title>This is project 01 </Title>
</Wrapper>
)
}
}
in this state your compoenent will mount and not did mount you can also console log your props using UNSAFE_componentWillMount but as it's name said it's unsafe
ReactJS.org
You can also create your own functions and it will works:
myFunction () {
console.log(this.props.state)
}
myFunction();

No QueryClient set, use QueryClientProvider to set one

I was trying to react query for the first time then I got this at the start of my React app.
import React from 'react'
import { useQuery } from "react-query";
const fetchPanets = async () => {
const result = await fetch('https://swapi.dev/api/people')
return result.json()
}
const Planets = () => {
const { data, status } = useQuery('Planets', fetchPanets)
console.log("data", data, "status", status)
return (
<div>
<h2>Planets</h2>
</div>
)
}
export default Planets
As the error suggests, you need to wrap your application in a QueryClientProvider. This is on the first page of the docs:
import { QueryClient, QueryClientProvider, useQuery } from 'react-query'
const queryClient = new QueryClient()
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
)
}
While this is most commonly caused by not having your application wrapped in a <QueryClientProvider>, in my case it happened because I was importing some shared components, which ended up with a different context. You can fix this by setting the contextSharing option to true
That would look like:
import { QueryClient, QueryClientProvider } from 'react-query'
const queryClient = new QueryClient()
function App() {
return <QueryClientProvider client={queryClient} contextSharing={true}>...</QueryClientProvider>
}
From the docs: (https://react-query.tanstack.com/reference/QueryClientProvider)
contextSharing: boolean (defaults to false)
Set this to true to enable context sharing, which will share the first and at least one instance of the context across the window to ensure that if React Query is used across different bundles or microfrontends they will all use the same instance of context, regardless of module scoping.
Just make changes like below it will work fine
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { QueryClient, QueryClientProvider } from "react-query";
const queryClient = new QueryClient();
ReactDOM.render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>,
document.getElementById('root')
);
import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
const queryClient = new QueryClient();
const fetchPanets = async () => {
const result = await fetch('https://swapi.dev/api/people')
return result.json()
}
const Planets = () => {
const { data, status } = useQuery('Planets', fetchPanets)
console.log("data", data, "status", status)
return (
<div>
<h2>Planets</h2>
</div>
);
}
export default function Wraped(){
return(<QueryClientProvider client={queryClient}>
<Planets/>
</QueryClientProvider>
);
}
Single SPA (micro-frontend) - React Query v3.34.15
I was getting this error while trying to integrate a sigle-spa react parcel into the root application.
I used craco-plugin-single-spa-application for the building of a CRA app as a way to adapt it for a parcel. In the entry config I was pointing to my single-spa-react config.
// craco.config.js
const singleSpaApplicationPlugin = require('craco-plugin-single-spa-application')
module.exports = {
plugins: [
{
plugin: singleSpaApplicationPlugin,
options: {
orgName: 'uh-platform',
projectName: 'hosting',
entry: 'src/config/single-spa-index.cf.js',
orgPackagesAsExternal: false,
reactPackagesAsExternal: true,
externals: [],
minimize: false
}
}
]
}
In the single-spa-index.cf.js file I had the following configs.
import React from 'react'
import ReactDOM from 'react-dom'
import singleSpaReact from 'single-spa-react'
import App from '../App'
const lifecycles = singleSpaReact({
React,
ReactDOM,
rootComponent: App,
errorBoundary() {
return <div>Ocorreu um erro desconhecido!</div>
}
})
export const { bootstrap, mount, unmount } = lifecycles
After reading a bunch of forums and the react-query documentation, the only thing that I figured out I needed to change was pass in the QueryClientProvider the prop contextSharing as true. After had did this change, ran the building and access the route that opens my parcel. I got the same error.
import React from 'react'
import ReactDOM from 'react-dom'
import { QueryClient, QueryClientProvider } from 'react-query'
import { ReactQueryDevtools } from 'react-query/devtools'
import App from './App'
const queryClient = new QueryClient()
const isDevelopmentEnv = process.env.NODE_ENV === 'development'
if (isDevelopmentEnv) {
import('./config/msw/worker').then(({ worker }) => worker.start())
}
ReactDOM.render(
<React.StrictMode>
<QueryClientProvider contextSharing={true} client={queryClient}>
<App />
{isDevelopmentEnv && <ReactQueryDevtools initialIsOpen={false} />}
</QueryClientProvider>
</React.StrictMode>,
document.getElementById('root')
)
But, how do I solved that. Well, it was was simple. I couldn't even imagine why it was working locally. But not after building and integration.
The problem was because I put the React Query Provider inside the index o the application and in my single-spa-index.cf.js I was importing import App from '../App' which really wasn't wrapped by the provider. Once I also was importing App in the application index, where It was wrapped making It works locally. ๐Ÿ˜ข๐Ÿ˜ข
So after figure that out, my code was like that:
CODE AFTER SOLUTION
// craco.config.js
const singleSpaApplicationPlugin = require('craco-plugin-single-spa-application')
module.exports = {
plugins: [
{
plugin: singleSpaApplicationPlugin,
options: {
orgName: 'uh-platform',
projectName: 'hosting',
entry: 'src/config/single-spa-index.cf.js',
orgPackagesAsExternal: false,
reactPackagesAsExternal: true,
externals: [],
minimize: false
}
}
]
}
// src/config/single-spa-index.cf.js
import React from 'react'
import ReactDOM from 'react-dom'
import singleSpaReact from 'single-spa-react'
import App from '../App'
const lifecycles = singleSpaReact({
React,
ReactDOM,
rootComponent: App,
errorBoundary() {
return <div>Ocorreu um erro desconhecido!</div>
}
})
export const { bootstrap, mount, unmount } = lifecycles
// App.tsx
import { QueryClient, QueryClientProvider } from 'react-query'
import { ReactQueryDevtools } from 'react-query/devtools'
import { config } from 'config/react-query'
import Routes from 'routes'
import GlobalStyles from 'styles/global'
import * as S from './styles/shared'
const queryClient = new QueryClient(config)
const isDevelopmentEnv = process.env.NODE_ENV === 'development'
if (isDevelopmentEnv) {
import('./config/msw/worker').then(({ worker }) => worker.start())
}
function App() {
return (
<QueryClientProvider contextSharing={true} client={queryClient}>
<S.PanelWrapper>
<Routes />
<GlobalStyles />
</S.PanelWrapper>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
}
export default App
// index.tsx
import { StrictMode } from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(
<StrictMode>
<App />
</StrictMode>,
document.getElementById('root')
)
Well, it was long but I hope it helps someone that's undergoing for the same problem as mine. ๐Ÿ™Œ๐Ÿ™Œ๐Ÿ™Œ
I was trying to fix the same thing:
I followed the React Query docs
and used the concept of Higher Order Component
See if it helps:
import React from 'react';
import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
import Planet from './Planet';
const queryClient = new QueryClient();
const fetchPlanets = async () => {
const res = await fetch('http://swapi.dev/api/planets/');
return res.json();
}
const Planets = () => {
const { data, status } = useQuery('planets', fetchPlanets);
return (
<div>
<h2>Planets</h2>
{ status === 'loading' && (<div>Loading data...</div>)}
{ status === 'error' && (<div>Error fetching data</div>)}
{
status === 'success' && (
data.results.map(planet =>
<Planet
key={planet.name}
planet={planet}
/>
)
)
}
</div>
)
}
// Higher order function
const hof = (WrappedComponent) => {
// Its job is to return a react component warpping the baby component
return (props) => (
<QueryClientProvider client={queryClient}>
<WrappedComponent {...props} />
</QueryClientProvider>
);
};
export default hof(Planets);
In my case I was importtng from 'react-query' in one place and '#tanstack/react-query' in another.
I got that error when trying to add the react-query devtools.
The problem was I was installing it wrongly according my version, I was using react-query v3.
WRONG FOR react-query V3 (GOOD FOR V4)
import { ReactQueryDevtools } from '#tanstack/react-query-devtools';
OK FOR react-query V3
import { ReactQueryDevtools } from 'react-query/devtools';
In my case I accidentally used two different versions of react-query in my modules.
In my case
Error import { QueryClient, QueryClientProvider } from "#tanstack/react-query";
Solution import { QueryClient, QueryClientProvider } from "react-query";
remove it #tanstack/
Just be careful when upgrade from react-query v3 to #tanstack/react-query v4.
Ensure that you replace all imports as "react-query" to "#tanstack/react-query" and then run yarn remove the lib that you won't use anymore, otherwise you may accidentally import the unexpected one.
This happened to me and caused this error.

How do I implement Sibling Component communication in App shell and SSR

I have adopted a project that was built on this starter kit. This architecture employs App Shell and SSR. I am trying to add a simple search bar and this will mean passing the search keys from the search bar component to the post-list component so they can be filtered. I have found that this is nearly impossible with Context Providers and Consumers. I would like to use Context, but I do not know how to do it. It looks like this starter kit has this as a serious shortcoming and if it could be solved, it would make this kit more useful online.
If you look at the code below and in the link above, you can see that there is a header center and then thee are pages. I need a communication between the header and the pages. You can just use the code in the link to add the sibbling communication.
The use of Hydrate seems to preclude the simple application of a context provider. Hydrate adds components in a parallel way with no way to have the Context Provider above both of them. This pattern I am using here does not work. When I update the provider it does not cause a re-render of the context consumer.
If I have to use something other than Context, like say Redux, then I will accept that answer.
Here is the client entry point:
import { onPageLoad } from 'meteor/server-render';
import MeteorLoadable from 'meteor/nemms:meteor-react-loadable';
import { Switch, Route, Router, Redirect } from 'react-router-dom';
import { ApolloClient } from 'apollo-client';
import { ApolloProvider } from 'react-apollo';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloLink } from 'apollo-link';
import { HttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { onError } from 'apollo-link-error';
import apolloLogger from 'apollo-link-logger';
import { onTokenChange, getLoginToken } from '/app/ui/apollo-client/auth';
import createBrowserHistory from 'history/createBrowserHistory';
import 'unfetch/polyfill';
// Initialise react-intl
import { primaryLocale, otherLocales } from '/app/intl';
// Need to preload of list of loadable components for MeteorLoadable
import '/app/ui/loadables';
// To get started, create an ApolloClient instance and point it at your GraphQL
// server. By default, this client will send queries to the '/graphql' endpoint
// on the same host.
// To avoid asynchronously accessing local storage for every GraphQL request,
// we cache the authorisation token, and update it using an onTokenChange callback
let authToken;
let authTokenInitialised = false;
onTokenChange(({ token }) => { authToken = token; authTokenInitialised = true; });
const withAuthToken = setContext(() => {
if (authTokenInitialised) {
return authToken ? { headers: { authorization: authToken } } : undefined;
}
return getLoginToken()
.then((token) => {
authToken = token;
authTokenInitialised = true;
return authToken ? { headers: { authorization: authToken } } : undefined;
});
});
const resetAuthToken = onError(({ networkError }) => {
if (networkError && networkError.statusCode === 401) {
// Remove cached token on 401 from the server
authToken = null;
authTokenInitialised = false;
}
});
const onErrorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.map(({ message, locations, path }) => console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
));
}
if (networkError) console.log(`[Network error]: ${networkError}`);
});
const client = new ApolloClient({
link: ApolloLink.from([
apolloLogger,
withAuthToken,
resetAuthToken,
onErrorLink,
new HttpLink({
uri: '/graphql',
}),
]),
cache: new InMemoryCache().restore(window.__APOLLO_STATE__),
});
// Inject the data into the app shell.
// If the structure's changed, ssr.js also needs updating.
async function renderAsync() {
const [
React,
{ hydrate, render },
{ default: App },
{ default: HeaderTitle },
{ default: LanguagePicker },
{ default: Routes },
{ default: Menu },
] = await Promise.all([
import('react'),
import('react-dom'),
import('/app/ui/components/smart/app'),
import('/app/ui/components/smart/header/header-title'),
import('/app/ui/components/dumb/language-picker'),
import('/app/ui/routes'),
import('/app/ui/components/smart/menu'),
MeteorLoadable.preloadComponents(),
]);
// Given that we are implementing App Shell Architecture and, therefore,
// injecting (via reactDOM.render) the Header, Menu and Main components into
// different HTML elements, we need a way to share the router 'history' among
// all three mentioned components.
// As a default, for every invocation of 'BrowserRouter', there will be new
// 'history' instance created. Then, changes in the 'history' object in one
// component won't be available in the other components. To prevent this, we are
// relying on the 'Router' component instead of 'BrowserRouter' and defining our
// custom 'history' object by means of 'createBrowserHistory' function. Said
// 'history' object is then passed to every invocation of 'Router' and therefore
// the same 'history' object will be shared among all three mentioned components.
const history = createBrowserHistory();
// Inject react app components into App's Shell
const ClientApp = ({ component }) => (
<Router history={history}>
<ApolloProvider client={client}>
<Switch>
{/* Map our locales to separate routes */}
{ otherLocales.map(locale => (
<Route
key={locale}
path={`/${locale}/`}
render={props => <App component={component} {...props} locale={locale} section="app" />}
/>
))}
{ primaryLocale && (
<Route
key={primaryLocale}
path="/"
render={props => <App component={component} {...props} locale={primaryLocale} section="app" />}
/>
)}
{/* If no valid locale is given, we redirect to same route with the preferred locale prefixed */}
<Route render={({ location }) => <Redirect to={`/${window.__PREFERRED_LOCALE__ || otherLocales[0]}${location.pathname}`} />} />
</Switch>
</ApolloProvider>
</Router>
);
render(<ClientApp component={Menu} />, document.getElementById('menu'));
hydrate(<ClientApp component={HeaderTitle} />, document.getElementById('header-title'));
hydrate(<ClientApp component={LanguagePicker} />, document.getElementById('header-lang-picker'));
hydrate(<ClientApp component={Routes} />, document.getElementById('main'));
}
onPageLoad(() => {
const renderStart = Date.now();
const startupTime = renderStart - window.performance.timing.responseStart;
console.log(`Meteor.startup took: ${startupTime}ms`);
// Register service worker
import('/app/ui/register-sw').then(() => {});
renderAsync().then(() => {
const renderTime = Date.now() - renderStart;
console.log(`renderAsync took: ${renderTime}ms`);
console.log(`Total time: ${startupTime + renderTime}ms`);
});
});
You can create a Context, lets'say AppContext
// you can create some help such as the AppProvider and the useAppContext hook
import { createContext, useContext, useState } from "react";
export const AppContext = createContext({});
export const AppProvider: any = ({ initialState = { search: "" }, children }) => {
const [search, setSearch] = useState(initialState);
return (
<AppContext.Provider value={[search, setSearch]}>
{children}
</AppContext.Provider>
)
};
export const useAppContext: any = () => useContext(AppContext);
then you can plug your provider at same level of apolloProvider
...
import {AppProvider} from './context/AppProvider';
....
<AppProvider> // optionally you can pass an initialState to you context as prop
<ApolloProvider client={client}>
// your code
</ApolloProvider>
</AppProvider>
...
and finally you can use the state (search) defined in your AppContext
import {useAppContext} from './context/AppProvider';
....
export const Component = () => {
const [search, setSearch] = useAppContext()
return <div>{search}</div>
}

React Hot Reloader 3

I'm trying the new React Hot Loader 3 and I'm receiving an warning message:
warning.js:44 Warning: React.createElement: type should not be null,
undefined, boolean, or number. It should be a string (for DOM
elements) or a ReactClass (for composite components). Check the render
method of AppContainer.
This is the App file. What is wrong?
Ps: It's written the same way as on examples.
Ps2: The problem is not in the App since it renders on the render() outside the if statement
Ps3: The warning only appears when the browser tries to 'hot-reload' (when I make changes to any file)
import React from 'react';
import { render } from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import a11y from 'react-a11y'
import Store from './stores/Store';
import App from './components/App/App';
const store = new Store();
if(process.env.NODE_ENV === 'development'){
a11y(React);
}
render(
<AppContainer
component={App}
props={{ store }}
/>,
document.getElementById('root')
);
if (module.hot) {
module.hot.accept('./components/App/App', () => {
render(
<AppContainer
component={require('./components/App/App').default}
props={{ store }}
/>,
document.getElementById('root')
);
});
}
Ok, the problem was with my .babelrc file. I had some other plugins, that I've removed, and it worked:
{
"presets": ["es2015-loose", "react", "stage-1"],
"plugins": [
"react-hot-loader/babel",
"transform-decorators-legacy"
],
}

Categories