react-solid-signals
Using signals in react to boost performance
react-solid-signals
Using Signals in react to boost performance.
Problem with react useState
export default function App() {
const [count, setCounter] = useState(0);
return (
<div className="App">
<h1>Hello useState </h1>
<h3>Counter is {count}</h3>
<button
onClick={() => {
setCounter((prev) => prev + 1);
}}
>
Increment Counter
</button>
<SomeHeavyComponent />
</div>
);
}
The component SomeHeavyComponent
is going to get re-rendered whenenver the count
is changed. This is a big problem, that we solve on a daily basis when we consider performance as our key metric. Careful state management with many clever tricks will solve many such performance problems. But the question is what if we can avoid this problem altogether. What if we can avoid this problem without using a different JS framework like Solid JS and still uisng the rich ecosystem of React JS.
Try it on codesanbox https://codesandbox.io/s/compassionate-water-jkzfib?file=/src/App.tsx
useSignal
Usage
import { useSignal } from 'react-solid-signals';
const SomeComponent = ()=>{
const [counter, setCounter] = useSignal(0);
return <>
<div>
Counter is {counter.jsx()}
<br></br>
<button onClick={()=>{
setCounter((prev)=>prev+1)
}}>Increment Counter</button>
</div>
//otherComponents
</>
}
useSignal returns an array, with first element as an object, which contains 2 signalGetters
{ value, jsx }
. Whenever you want to access the signal value inside a jsx, use counter.jsx()
otherwise use counter.value()
.
The second element of the returned array is a setter which is identical to the setState
React dispatch setter we use with useState
.
Now even when the counter value is changed continously when the user clicks on the button, none of components in the jsx tree is re-rendered, only the part {counter.jsx}
is re-rendered.
useEffectSignal
Standard React useEffect
wont work with signals,to perform side effects whenever signals change, we need useEffectSignal
.
Usage
import { useEffectSignal } from 'react-solid-signals';
useEffectSignal(()=>{
//this function will be executed whenever its dependency is changed
console.log('counter',counter.value())
},[counter])
useEffectSignal
takes a function as the first argument and a dependency array as the second argument. Dependency array must be an array of signals only.
useMemoSignal
Usage
import { useMemoSignal } from 'react-solid-signals';
const memosizedData = useMemoSignal(()=>{
console.log('counter',counter.value())
return counter.value() * 4;
},[counter])
memosizedData
is also a signal and have the same two getters { jsx, value }
. The memosizedData signal will be updated only when the counter signal is changed. The hook also accepts a 3rd argument as defaultValue
, if provided then the defaultValue will be used to initilize the signal.
Show
In React while using state we use conditional rendering like the below
const SomeComponent = () => {
const [counter, setCounter] = useState(0)
return <div>
{counter ===2 ?
<div>Counter is 2</div>:
<div> Counter is not 2</div>
}
</div>
}
This type of conditional rendering will not work with signals.
To achieve the same functionality using signals, we must use Show
component provided by the library as follows:
import { Show, useSignal } from 'react-solid-signals';
const SomeComponent = () => {
const [counter, setCounter] = useSignal(0)
return <div>
<Show jsxCallback={()=>{
return count.value() === 2 ? 'Counter is 2' : 'Counter is not 2';
}} deps={[counter]}/>
</div>
}
A jsxCallback
is a function which returns an JSX element and deps
is an array of signals. JSX callback is again re-evaluated whenever the signals given in the dependency array is changed
Use signals in redux
You can use react-redux useSelector
hook, but it will again cause re-render of the components. Create the below redux hook to select signals from redux store. You must provide your store instance in signalStoreInitializer
.
import { signalStoreInitializer } from "react-solid-signals";
import { store } from "./store";
export const useSignalSelector = signalStoreInitializer(store);
If you are using typescript then use the below hook.
import { store } from "./store";
import {
TypedUseSelectorSignalHook,
signalStoreInitializer,
} from "react-solid-signals";
export type RootState = ReturnType<typeof store.getState>
export const useSignalAppSelector: TypedUseSelectorSignalHook<RootState> =
signalStoreInitializer(store);
Usage of useSignalAppSelector
/ useSignalSelector
in a component
const SomeComponent = ()=>{
const count = useSignalAppSelector((state)=>state.main.count,0)
return <>
{count.jx()}
</>
}
Currently you must provide a default value in the hook as the second parameter. This default value will be used unless the signal is not updated with the correct value from the store.
There are no limitations of the getter singal.value(), all the methods, properties is possible on the data is also supported by this getter.
Current Limitations of signal.jsx() getter.
- Join operation on signal.jsx() is not supported (if the data is of type Array)
- Filter operation on signal.jsx() is not supported
- Nested arrays are not suppported
- null/undefined not supported