How to use a utility class instance in react different components and prevent reinitialization and only one instance is used on all components - reactjs

I have a class in a separate file and two or more different react components that need to use the class methods
One approach was I initially created an instance of the class outside the react components to prevent re rendering and having re-initialize the class
const utilityClass = new UtilityClass()
function ReactComponent() {
const doSomething = () => {
return utilityClass.doingSomething()
}
}
but then for the second react component in a different file I will have to do the same thing right like below
const utilityClass = new UtilityClass()
function SecondReactComponent() {
const doSomething = () => {
return utilityClass.doingSomething()
}
}
Even though it wont re-initialize on component re-render I am still creating an instance of the utility class multiple times across the different react components so I tried useMemo which also worked like below:
function SecondReactComponent() {
const utilityClass = useMemo(() => new utilityClass(), []);
const doSomething = () => {
return utilityClass.doingSomething()
}
}
And I am wondering which is the best approach because I also tried useCallback and for some reason that did not work and will appreciate if someone gave me more insights on the best practice to do this thanks

Just instantiate the class and export it at the top level of one of your modules. For example:
./First.jsx:
// class UtilityClass {/* ...*/}
export const utilityClass = new UtilityClass();
export function ReactComponent () {
const doSomething = () => {
return utilityClass.doingSomething();
};
}
Then, in every other module where you want to use it (in other components, etc.), just import and use it:
./Second.jsx:
import {utilityClass} from './First';
export function SecondReactComponent () {
const doSomething = () => {
return utilityClass.doingSomething();
};
}

Related

Adding functions to lit web components in react with typescript

I have a web component i created in lit, which takes in a function as input prop. but the function is not being triggered from the react component.
import React, { FC } from 'react';
import '#webcomponents/widgets'
declare global {
namespace JSX {
interface IntrinsicElements {
'webcomponents-widgets': WidgetProps
}
}
}
interface WidgetProps extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> {
var1: string,
successCallback: Function,
}
const App = () =>{
const onSuccessCallback = () =>{
console.log("add some logic here");
}
return(<webcomponents-widgets var1="test" successCallBack={onSuccessCallback}></webcomponents-widgets>)
}
How can i trigger the function in react component? I have tried this is vue 3 and is working as expected.
Am i missing something?
As pointed out in this answer, React does not handle function props for web components properly at this time.
While it's possible to use a ref to add the function property imperatively, I would suggest the more idiomatic way of doing things in web components is to not take a function as a prop but rather have the web component dispatch an event on "success" and the consumer to write an event handler.
So the implementation of <webcomponents-widgets>, instead of calling
this.successCallBack();
would instead do
const event = new Event('success', {bubbles: true, composed: true});
this.dispatch(event);
Then, in your React component you can add the event listener.
const App = () => {
const widgetRef = useRef();
const onSuccessCallback = () => {
console.log("add some logic here");
}
useEffect(() => {
widgetRef.current?.addEventListener('success', onSuccessCallback);
return () => {
widgetRef.current?.removeEventListener('success', onSuccessCallback);
}
}, []);
return(<webcomponents-widgets var1="test" ref={widgetRef}></webcomponents-widgets>);
}
The #lit-labs/react package let's you wrap the web component, turning it into a React component so you can do this kind of event handling declaratively.
React does not handle Web Components as well as other frameworks (but it is planned to be improved in the future).
What is happening here is that your successCallBack parameter gets converted to a string. You need to setup a ref on your web component and set successCallBack from a useEffect:
const App = () => {
const widgetRef = useRef();
const onSuccessCallback = () =>{
console.log("add some logic here");
}
useEffect(() => {
if (widgetRef.current) {
widgetRef.current.successCallBack = onSuccessCallback;
}
}, []);
return(<webcomponents-widgets var1="test" ref={widgetRef}></webcomponents-widgets>)
}

React High Order Components: best idiomatic way to avoid re-rendering the wrapper?

I have a little app where page components can have a layout like so:
// publicPage.js
export default function PublicPage() {...}
PublicPage.layout = NarrowLayout;
// narrowLayout.js
export default function NarrowLayout({children}) {
return <Container maxWidth="xs">{children}</Container>
}
and the rendering pipeline of the application wraps the page with its layout (similar to Next.js <= 12).
I'd like to have a Higher Order Component (HOC) that allows nesting layouts, like so:
// withAuth.js
export function withAuth(LayoutComponent) {
return (props) => <Authenticated><LayoutComponent {...props} /></Authenticated>
}
export function Authenticated({children}) {
const [userId, setUserId] = useState(null);
useEffect(() => {
// Some code to set up authentication
}, [])
return userId ? children : <LoadingSpinner />
}
// pageRequiringLogin1.js
export default function PageRequiringLogin1() {...}
PageRequiringLogin1.layout = withAuth(NarrowLayout);
// pageRequiringLogin2.js
export default function PageRequiringLogin2() {...}
PageRequiringLogin2.layout = withAuth(NarrowLayout);
Problem is, the code in the useEffect above runs every time I navigate between PageRequiringLogin1 and PageRequiringLogin2.
I understand this happens because withAuth generates a different function instance on every invocation, so React views it as a different component and unmounts the entire tree.
My solution is to cache the results of the HOC:
// withAuth.js
const withAuthCache = {};
export function withAuth(LayoutComponent) {
return withAuthCache[LayoutComponent] ??= (props) => <Authenticated><LayoutComponent {...props} /></Authenticated>
}
This works, but it feels unidiomatic. Is there a better way to do this with React?

Incorrect use of react hooks but no hook errors/faults

React hooks can only be used inside
Functional components
Other hooks
If you don't follow the above rules, react complains about not following the rules via a fault
But, the below code works (albeit with lint errors) without any stack-trace/errors
import React from 'react'
import { useState, createContext, useContext } from "react";
const SomethingContext = createContext("what is the thing");
// Correct usage
const useSomething = () => {
const something = useContext(SomethingContext);
return something;
};
//Incorrect usage
const getTheThing = () => {
const theThing = useContext(SomethingContext); // works with useState as well (ex below)
return theThing;
};
//Incorect usage
const getTheThingState = () => {
const [theThing, setTheThing] = useState("I am the state");
return theThing;
};
const Child = () => {
const theThing = getTheThing(); //OR getTheThingState()
return `The thing is "${theThing}"`;
};
export default function App() {
return (
<div className="App">
<SomethingContext.Provider value={"it is something"}>
<Child />
</SomethingContext.Provider>
</div>
);
}
Here's a demo of the above code. The linter points out the incorrect usage, but the odd part where I'm confused is that React itself doesn't throw any error.
Not able to figure out WHY?
All resources point out to fix the above problem by changing the function into a component i.e function's variable name (I'm aware of it)
const GetTheThing = () => {
//...
But, when does react decide to say "okay, you are not supposed to do that" vs "okay, I'll let this one slide".
Technically there is no problem with the code provided. As the invocation of useContext and useState happening inside component's code. And there is no issue of having one outer function, which just calls the hook inside. Yes, we are breaking the Only Call Hooks at the Top Level rule. But if you go through rationale behind this rule - you'll see that the ordering of invocations is the key here. And having function without any nested if statements still guarantees the ordering. So your code:
export const useSomething = () => {
const something = useContext(SomethingContext);
return something;
};
const getTheThing = () => {
const theThing = useSomething();
return theThing;
};
const Child = () => {
const theThing = getTheThing();
return `The thing is "${theThing}"`;
};
Still complies with correct ordering of:
Render fn of Child component started
useContext invoked
Render fn of Child component finished

How to instantiate, store, and modify class variables in react-native?

I have class A
export class A {
constructor(data){...}
...
}
And a component that imports A but shouldn't instantiate it immediately.
...
import { A } from './A'
export const Acomponent = () => {
var aInstance;
const apiObject = apiCall(); // call some api
useEffect(() => {
if(apiObject.status === "success"){
aInstance = new A(apiObject.data);
... // aInstance is still undefined here
}
}, [apiObject]);
}
Is a var the best way to store the class instance in this case? I don't think a state would be an option because class functions would directly change the state without using the setState function.
You probably want useRef here.
import { A } from './A'
export const Acomponent = () => {
const aRef = useRef()
useEffect(() => {
const apiObject = apiCall(); // call some api
if(apiObject.status === "success"){
aRef.current = new A(apiObject.data);
}
}, [apiObject]);
return <>{aRef.current.whatever}</>
}
Just know that any changes to aRef will not re-render. If that's a problem, then this is the wrong solution.
Or you could store the A construction params in state and build a new one every render. Or better memoize it so you only build a new one when it would create a different result:
const [aParams, setAParams] = useState({})
const aInstance = useMemo(() => new A(aParams), [aParams])
useEffect(() => {
if(apiObject.status === "success") {
setAParams(apiObject.data)
}
}, [])
Or you could store the instance in state, but you have to treat the class as immutable. That means that if any value in that instance should change, you need to create a new instance and save it in state. Most classes can't easily be made to work this way, so it's probably not a great idea.

Use Hooks within class that doesn't doesn't get rendered

I have a child class that returns some JSX (an ion-item) it uses a hook to control some ion-icons (using the hook like a state but only because I can use useEffect which is very handy).
I also have a Bluetooth class. This is home to all the important Bluetooth functions. The reason it is in its own class is because I need this code accessible everywhere in the app (including the list of devices (the ion-items mentioned above) it creates).
I do it like this:
const _Bluetooth = () => {
const [state, setState] = useState([]);
useEffect(() => {
addDevice();
}, [state]);
const devices: any[] = [];
const bluetoothInitialize = () => {
for (let i = 0; i < 5; i++) {
let a = {name: "test", mac: i.toString(), connected:false}
setState({found: [...state.found, a]});
}
}
const connect = (id) => {
console.log("connecting");
}
const addDevice = () => {
let d = state.found[state.found.length - 1]
devices.push(<BluetoothDeviceItem key={d.mac} mac={d.mac} name={d.name} onClick={(id) => connect(id)} connectingState={d.connected ? 'connected' : 'not_connected'}></BluetoothDeviceItem>);
}
return {
devices, bluetoothInitialize, connect
};
}
export default _Bluetooth;
I then create an instance of this class in another file which acts as a global file and then other files import this global file giving access to that one instance of the class:
import _Bluetooth from '../components/bluetooth/Bluetooth'
export const Bluetooth = _Bluetooth();
Unfortunately the _Bluetooth class doesn't work. Since I am using a hook, React expects the component to be rendered and therefore the component needs to return JSX. However I don't want it to return JSX but rather the accessible functions and variables.
Like I said above I am using these hooks more like states but only because of the useEffect function.
I could easily get this working by doing:
const state = {found: []}
and then directly pushing items to the array. This takes away my ability of using useEffect which makes my life a little bit easier but also cleans up the code a little bit.
Is it possible to use hooks without rendering the components / returning any JSX?

Resources