How should I improve this behavior in React? - reactjs

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.

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-query useQuery inside a provider with with dynamic parameters

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.

createContext hook not working for react components inside JSP

I am rendering my react component inside an existing JSP page using
ReactDOM.render(
React.createElement(MyReactComponents.myReactComponent, {
props
}),
document.querySelector("#id")
);
and the react component is as follows:
import MyStore from "./MyStore";
const MyReactComponent: React.FC<any> = (props: any) => {
const store = useContext(MyStore);
store.myFunction();
---code---
}
and MyStore is as follows:
export class MyStore{
---Code---
}
export default createContext(new MyStore());
But i'm getting this error:
And one more importing thing to notice is that when I'm trying to render this react component on top of another existing react component, i'm not getting any error and everything is working fine.
Can someone please explain me what might be causing the issue?
I'm not sure, but maybe you are misusing the useContext hook?
Whenever you use it inside a component Child, then at least one of its parent component must call the <Context>.Provider, so that it is initialized down the tree.
In your example, you render MyReactComponent using ReactDOM.render: due this, I suppose MyReactComponent is the first component in your tree. If that is the case, when you use useContext inside it, it cannot find any MyStore context.
So, probably, you just need to wrap your MyReactComponent with a context provider.
export class MyStore { ... }
export const MyStoreContext = createContext(new MyStore());
---
ReactDOM.render(
<MyStoreContext.Provider>
<MyReactComponent {...props />
</MyStoreContext.Provider>
, document.querySelector("#id"));
And then, inside MyReactComponent, you can use const store = useContext(MyStoreContext);.

ES6 React Redux syntax clarification

I'm new to ES6 and Redux. Im looking at some code and trying to understand what is going on in this new ES6 syntax.
I feel like this may be simple but i am not understanding it and it might help someone else in a similar position to me.
i want to know how the following code is creating a react element. im familiar with the React.createClass method, but that doesnt seem to be stated here or at least not explicitly. i can see React is imported, but it isnt mentioned in the rest of the code. so then how the FileTable get turned into a react component?
I can see the const variable FileTable seems to contain what would usually go in the render method of React.createClass, but if that is the case, where would methods like componentDidMount, componentDidUpdate, etc be defined?
Any help on this is greatly appreciated.
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';
const FileTable = ({ fileList, getFileList}) => {
return (
<ul className="filterable-table">
{fileList.map((file)=><li>{file.fileName}</li>)}
</ul>
);
};
FileTable.propTypes = {
fileList: PropTypes.array,
};
const mapStateToProps = (state) => {
return {
fileList: state.fileList
};
};
const mapDispatchToProps = (dispatch) => {
return {
getFileList: () => dispatch(actions.getFileList())
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(FileTable);
You can create react components in 3 ways - React.createClass, ES6 class or
Stateless (pure) function component. This is a stateless component, which means that it doesn't have state, life cycle methods (like componentDidMount or componentDidUpdate), and refs, and as you surmised it's similar to the render method of a react class.
Whenever you need a purely representational dumb component you can use a stateless component, due to its brevity. It goes nicely with redux, as the connect create a smart component that wraps the stateless method.
Regarding performance, stateless components don't have any performance gain over ES6 class component without state. However, Facebook stated that in the future there will be some optimizations.
It doesn't have to be declared here as a React component; React knows about pure functions.
Pure functions don't need to inherit from Component. They're not appropriate for all component types, but for simple HTML renders they're preferred (e.g., see eslint-plugin-react prefer-stateless-function.
Pure functions don't have component lifecycles, associated methods, etc.

Resources