react-query useQuery inside a provider with with dynamic parameters - reactjs

I'm using react query because it's super powerful but I'm struggling trying to share my data across many components inside a provider. I'm wondering if this is the right approach.
PostsContext.js
import React, {useState} from 'react';
import { useTemplate } from '../hooks';
export const PostsContext = React.createContext({});
export const PostsProvider = ({ children }) => {
const fetchTemplate = useTemplate(templateId);
const context = {
fetchTemplate,
};
return <PostsContext.Provider value={context}>{children}</PostsContext.Provider>;
};
useTemplate.js
import React from 'react';
import { useQuery } from 'react-query'
import { getTemplateApi } from "../api";
export default function useTemplate(templateId) {
return useQuery(["templateId", templateId], () => getTemplateApi(templateId), {
initialData: [],
enabled:false,
});
}
and then my component that uses the context
function Posts () {
const { fetchTemplate } = useContext(PostsContext);
console.log(fetchTemplate.isLoading)
fetchTemplate.refetch() <---- how can I refetch with a different templateId?
return {...}
}
I'm looking for a way to dynamically call my hook with a different templateId but with the hook inside the provider so I can use it all over my app. Is this the right approach? I have deeply nested components that I don't want to prop drill.

You don’t need an extra way to distribute your data, like react context. Just call useQuery with the same key wherever you need to, and react query will do the rest. It is best to abstract that away in a custom hook.
refetch should only be used if you want to refetch with the exact same parameters. For changing parameters, it’s best to. make them part of your query key, because react query will refetch whenever the query key changes.
So in your example, you only need to call useTemplate with a different templateId. templateId itself is local state (which template has been selected by the user or so), and how you make that globally available is up to you.

Related

Using React.Context with Nextjs13 server-side components

Next13 was released a week ago, and I am trying to migrate a next12 app to a next13.
I want to use server-side components as much as possible, but I can't seem to use
import { createContext } from 'react';
in any server component.
I am getting this error:
Server Error
Error:
You're importing a component that needs createContext. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.
,----
1 | import { createContext } from 'react';
: ^^^^^^^^^^^^^
`----
Maybe one of these should be marked as a client entry with "use client":
Is there an alternative here or do I have to resort to prop drilling to get server-side rendering?
It seems like I can use createServerContext
import { createServerContext } from 'react';
If you're using Typescript and React 18, you'll also need to add "types": ["react/next"] to your tsconfig.json compiler options, since this is a not-yet-stable function.
This is a new feature from React's SSR to recognize whether a component is client-side or server-side. In your case, createContext is only available on the client side.
If you only use this component for client-side, you can define 'use client'; on top of the component.
'use client';
import { createContext } from 'react';
You can check this Next.js document and this React RFC for the details
According to Next.js 13 beta documentation, you cannot use context in Server Components:
In Next.js 13, context is fully supported within Client Components, but it cannot be created or consumed directly within Server Components. This is because Server Components have no React state (since they're not interactive), and context is primarily used for rerendering interactive components deep in the tree after some React state has been updated
However, there are alternative ways to handle data in the new approach, depending on your case. F.e. if you fetched the data from the server in a parent component and then passed it down the tree through Context, you can now fetch the data directly in all the components that depend on this data. React 18 will dedupe (de-duplicate) the fetches, so there are no unnecessary requests.
There are more alternatives in the documentation.
I've made a tiny package to handle context in server components, works with latest next.js, it's called server-only-context:
https://www.npmjs.com/package/server-only-context
Usage:
import serverContext from 'server-only-context';
export const [getLocale, setLocale] = serverContext('en')
export const [getUserId, setUserId] = serverContext('')
import { setLocale, setUserId } from '#/context'
export default function UserPage({ params: { locale, userId } }) {
setLocale(locale)
setUserId(userId)
return <MyComponent/>
}
import { getLocale, getUserId } from '#/context'
export default function MyComponent() {
const locale = getLocale()
const userId = getUserId()
return (
<div>
Hello {userId}! Locale is {locale}.
</div>
)
}
This is the code for it, it's really simple:
import 'server-only'
import { cache } from 'react'
export default <T>(defaultValue: T): [() => T, (v: T) => void] => {
const getRef = cache(() => ({ current: defaultValue }))
const getValue = (): T => getRef().current
const setValue = (value: T) => {
getRef().current = value
}
return [getValue, setValue]
}

How to prevent useEffect in customHook being called with every import of custom hook?

I'm writing chat app using react js and socket.io library.
All the logic where I subscribe to events form server and emit some events is written in useEffect of custom hook.
Then I return all data I need from this custom hook and reuse it in components that I need. However, I realized that logic written in useEffect is called every time I import this custom hook to external component.
If I put all the logic outside of useEffect, it's called even more times than custom hook is imported.
How do I prevent it if it's possible at all?
If it's not possible, what solution could you please suggest? I don't want to use redux for this app, I thought to keep everything in this custom hook component and just reuse data from it where I need.
I can't share working example because it won't work without server part so here is a simple codesandbox example. You can see in console that it's rendered twice.
https://codesandbox.io/s/custom-hook-bfc5j?file=/src/useChat.js
It renders twice because you call useChat() two times in your app (one in App.js, other in Text.js) What you can do is to create a reference of useChat component in your App.js and pass is as a prop to Text.js like:
App.js
import React from "react";
import useChat from "./useChat";
import Text from "./Text";
import "./styles.css";
export default function App() {
const myUseChat = useChat();
const { printMessage } = myUseChat;
return (
<div className="App">
<button onClick={printMessage}>Print</button>
<Text myUseChat={myUseChat} />
</div>
);
}
Text.js
import React from "react";
import useChat from "./useChat";
import "./styles.css";
export default function Text(props) {
const { text } = props.myUseChat;
return <div className="App">{text}</div>;
}
If you want to set up some side effects once but also consume the resulting data in multiple places, one way is to use the context feature.
// ws/context.jsx, or similar
const WsContext = React.createContext(defaultValue);
export const WsProvider = props => {
const [value, setValue] = useState(someInitialValue);
useEffect(() => {
// do expensive things, call setValue with new results
});
return (
<WsContext.Provider value={value}>
{props.children}
</WsContext.Provider>
);
};
export const useCustomHook = () => {
const value = useContext(WsContext);
// perhaps do some other things specific to this hook usage
return value;
};
You can expect the hook to work in any component that is a descendant of <WsProvider> in React's rendered tree of elements.
If you use the hook in a non-descendant of the provider component, the value returned will be the defaultValue we initialized the context instance with.

React/Redux global state management based on Socket.IO responses on client side?

I have an web app with multiple features like private messaging, buying, offers etc. I want to make it to work real time so I decided to use socket.io. I use redux for global state management, but I don't know how can I combine this with socket.IO. This was my idea:
1.Creating a file for socket handling with with exported functions to App.js to create a socket connection, sending and listening different data.
2.Whenever I got something relevant for example a notification or a buying request I update my redux state.
3.Finally in my components I will use useEffect for those global redux states and if it changes I will rerender my component based on my changed state.
Is this a good approach? If not which is a proper way to globally mangage my components based on socket recieved informations?
In general, depending on your needs I see nothing wrong with this approach. I will provide one actionable example here. My example will assume TypeScript as it's easier to transform to JavaScript (in case you do not use TypeScript) than the other way around.
In relation to your 1st question I would suggest to establish and pass Websocket connection as a context as you use it everywhere in your application and create custom hook to use the connection anywhere:
import React, { createContext, FunctionComponent, ReactNode, useContext, useEffect, useMemo, useState } from 'react';
import io from 'socket.io-client';
export const WebsocketContext = createContext<SocketIOClient.Socket | null>(null);
const WebsocketProvider: FunctionComponent<{ children: ReactNode }> = ({ children }: { children: ReactNode }) => {
const [connection, setConnection] = useState<SocketIOClient.Socket | null>(null);
const options: SocketIOClient.ConnectOpts = useMemo(() => ({}), []);
useEffect(() => {
try {
const socketConnection = io(process.env.BASE_URL || '127.0.0.1', options);
setConnection(socketConnection);
} catch (err) {
console.log(err);
}
}, [options]);
return <WebsocketContext.Provider value={connection}>{children}</WebsocketContext.Provider>;
};
export const useWebsocket = (): SocketIOClient.Socket | null => {
const ctx = useContext(WebsocketContext);
if (ctx === undefined) {
throw new Error('useWebsocket can only be used inside WebsocketContext');
}
return ctx;
};
export default WebsocketProvider;
Above we create context which has type SocketIOClient.Socket and defaults to null, as when connection is not yet ready we must assign default value. Then we create Websocket provider as FunctionComponent which accepts children(s) and holds connection state with useState hook eventually returning provider with Websocket connection. I also mention SocketIOClient.ConnectOpts as depending on your needs you might want to provide connection options; either statically or dynamically when using the hook. Furthermore useEffect hook which will try to establish the connection or throw an error. The only dependency which will rerun this hook is connection options in case they will dynamically change.
Finally we have custom hook useWebsocket which we can import in any component and use inside our context provider. Simply wrap your root component (or any other hierarchy level) with context provider to provide the context like in the example below:
import React, { FunctionComponent } from 'react';
import { BrowserRouter as Router, Switch, Route, Redirect } from 'react-router-dom';
import { v4 as uuid } from 'uuid';
import routes from './App.routes';
import WebsocketProvider from './websocket.context';
const App: FunctionComponent = () => {
return (
<WebsocketProvider>
<Router>
<Switch>
{routes.map((route) => (
<Route key={uuid()} {...route} />
))}
</Switch>
<Redirect to='/' />
</Router>
</WebsocketProvider>
);
};
export default App;
In relation to your 2nd question you can for example have ´useEffect´ hook to react when connection emits and update your Redux (or other global state management) store. Here I also use Elvis operator to check if the connection is not ready yet (if its not ready yet as null the useEffect hook will re-render on socket connection change when its ready):
import React, { FunctionComponent, useEffect, useState } from 'react';
import { useWebsocket } from './websocket.context';
const Foo: FunctionComponent = () => {
const dispatch = useDispatch();
const socket = useWebsocket();
useEffect(() => {
socket?.on('myEmitEvent', (data: myEmitData) => {
dispatch(myStoreAction(data));
});
return () => {
socket?.off('myEmitEvent');
};
}, [socket, dispatch]);
return ...
};
export default Foo;
In relation to your 3rd question as you mention you can use useEffect hook or more simply useSelector hook from react-redux package which automatically captures your state changes triggering re-render on necessary elements.
In short, your idea hits the ballpark and I hope that with this brief actionable example you will be able to refine solution which works for you.

How should I improve this behavior in React?

in my company we are using ReactJS to develop our website. We also have legacy code in jQuery (I know, we are trying to change everything to React). My problem is that we have some global functions that we have to pass throughout all the component tree. For instance, we have a control function that we have to pass throughout 8 components, but only the last one actually calls it.
So, I wonder if there's a way to avoid this problem. Another problem is that we have several react trees on the page, because as I said, we have some legacy code in jQuery. Any ideas/suggestions?
(pls if this question does not belong in this forum let me know)
So you have to create yout context like this:
import React from "react";
const YourContext = React.createContext({ func: null });
export default YourContext ;
then in your parent component you can initialize it and make it available in child components:
import React from "react";
import YourContext from "./YourContext";
const YourParentComponent = () => (
<YourContext.Provider value={{ func: () => {} }}>
....
</YourContext.Provider>
);
and in your child components you can use it:
import React, { useContext } from "react";
import YourContext from "../YourContext";
const YourChildComponent = () => {
const { func } = useContext(YourContext);
Have a look at React Context, it will allow you to pass data through the components tree without passing down the props.

How to import and call a react pure function which is using redux connect and its functions? (Only React functional components no classes)

Suppose I have a react pure function named SignIn() in One.js :
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {GoogleSignin, statusCodes} from '#react-native-community/google-signin';
import {getToken, saveToken} from '../actions/token';
const SignIn = async ({token, getToken, saveToken}) => {
const savedToken = await getToken();
console.log(token.loading, savedToken);
SignIn.propTypes = {
token: PropTypes.string.isRequired,
getToken: PropTypes.func.isRequired,
saveToken: PropTypes.func.isRequired,
};
};
const mapStateToProps = state => {
console.log('state : ', state);
return {
token: state.token,
};
};
export default connect(mapStateToProps, {saveToken, getToken})(SignIn);
I want to use this SignIn() function in another Two.js react file so that getToken() which is a redux function and other functions will be called inside file One.js and then i can use those functions inside file Two.js but the problem is because of redux connect, i am not able to export and use them. How can i import and use this kind of function inside Two.js file ?
connect function can only be implemented with react components that renders actual jsx, and for it to work you need to return jsx elements or null and call it like this <SignIn />.. in my opinion if you want to implement some logic with the use of redux, you can make a custom hook, implement useSelector or useDispatch inside it, and either return the data you want or just do your effect inside it then return nothing.
hope this helps.
here's an example from react-redux docs https://react-redux.js.org/api/hooks#usedispatch
What Worked for me was declaring the functions that i want to export inside the redux actions, so i created a new action for any function that i want to use. Make sure to make use of loading state of initial state otherwise functions can be called infinite times because of re-rendering.

Resources