react-signaler
v1.0.6
Published
Signals for react
Downloads
37
Maintainers
Readme
react-signaler
Based on: https://github.com/tc39/proposal-signals
!IMPORTANT! - This is not preact library like this @preact/signals-react. This library is made for react.
Usage
- signal<Type>(value: Type): Signal<Type>
- computed<Type>(compute: ()=>Type, watch?: (Signal | ReadonlySignal)[]): ReadonlySignal<Type>
- effect(handler: ()=>void, watch?: (Signal | ReadonlySignal)[]): Dispose
- lazyEffect(handler: ()=>void, watch: (Signal | ReadonlySignal)[]): Dispose
- Features
signal<Type>(value: Type): Signal<Type>
Signal with atomic value
import { signal } from 'react-signaler';
const num = signal(5);
const Component: FC = () => {
return <h1>Num: {num()}</h1>;
};
Signal with object value
import { signal } from 'react-signaler';
const num = signal({ num: 5 });
const Component: FC = () => {
return <h1>Num: {num((value) => value.num)}</h1>;
};
List of signal
import { listOfSignal } from './list-of-signal';
const Component: FC = () => {
return <div>{listOfSignal.map((signal, idxAsKey) => signal(idxAsKey))}</div>;
};
import { listOfSignal } from './list-of-signal';
const Component: FC = () => {
return (
<ul>
{listOfSignal.map((signal, idxAsKey) =>
signal((value) => <li>{value}</li>, idxAsKey),
)}
</ul>
);
};
Get Signal value
import { signal } from 'react-signaler';
const num = signal({ num: 5 });
console.log(num.get()); // { num: 5 }
console.log(num.get((value) => value.num)); // 5
Set Signal value
import { signal } from 'react-signaler';
const num = signal({ num: 5 });
num.set({ num: 10 }); // { num: 10 }
Update Signal value
import { signal } from 'react-signaler';
const num = signal({ num: 5 });
num.update((value) => ({ ...value, num: 10 })); // { num: 10 }
Mutate Signal value
import { signal } from 'react-signaler';
const num = signal({ num: 5 });
num.mutate((value) => (value.num = 10)); // { num: 10 }
Use Signal value in react component
import { signal } from 'react-signaler';
const text = signal('');
const Component: FC = () => {
return (
<div>
<h1>text: {text()}</h1>
{text((value) => (
<input value={value} onChange={(ev) => text.set(ev.target.value)} />
))}
</div>
);
};
useSignal() Create Signal in react component
import { useSignal } from 'react-signaler';
const Component: FC = () => {
const { signal } = useSignal();
const num = signal('unique-signal-key', () => 5);
return <h1>Num: {num()}</h1>;
};
computed<Type>(compute: ()=>Type, watch?: (Signal | ReadonlySignal)[]): ReadonlySignal<Type>
watch?: (Signal | ReadonlySignal)[] optional argument.In some cases signals are not read in computed but depend on some signals. Watch array is useful for this cases.
Computed values are memoized but, they are always recomputed when the related signals are changed.
Create Computed
import { signal, computed } from 'react-signaler';
const name = signal('Peter');
const upperCaseName = computed(() => name.get().toUpperCase());
const Component: FC = () => {
return <h1>name: {upperCaseName()}</h1>;
};
List of computed
import { listOfComputed } from './list-of-computed';
const Component: FC = () => {
return (
<div>{listOfComputed.map((computed, idxAsKey) => computed(idxAsKey))}</div>
);
};
import { listOfComputed } from './list-of-computed';
const Component: FC = () => {
return (
<ul>
{listOfComputed.map((computed, idxAsKey) =>
computed((value) => <li>{value}</li>, idxAsKey),
)}
</ul>
);
};
Create Computed object value
import { signal, computed } from 'react-signaler';
const name = signal('Peter');
const transformedName = computed(() => ({
upperCase: name.get().toUpperCase(),
lowerCase: name.get().toLowerCase(),
}));
const Component: FC = () => {
return <h1>name: {transformedName((value) => value.lowerCase)}</h1>;
};
Read Computed value
import { signal, computed } from 'react-signaler';
const name = signal('Peter');
const transformedName = computed(() => ({
upperCase: name.get().toUpperCase(),
lowerCase: name.get().toLowerCase(),
}));
console.log(transformedName.get()); // { upperCase: "PETER", lowerCase: "peter" }
console.log(transformedName.get((value) => value.upperCase)); // PETER
useComputed() Create Computed in react component
import { useSignal, useComputed } from 'react-signaler';
const Component: FC = () => {
const { signal } = useSignal();
const { computed } = useComputed();
const name = signal('name', () => 'Peter');
const upperCaseName = computed('upperCaseName', () =>
name.get().toUpperCase(),
);
return <h1>name: {upperCaseName()}</h1>;
};
effect(handler: ()=>void, watch?: (Signal | ReadonlySignal)[]): Dispose
watch?: (Signal | ReadonlySignal)[] optional argument.In some cases signals are not read in effect but depend on some signals. Watch array is useful for this cases.
Effects always run once immediately after created.
Effects always run lazy after each related signals are changed.
Crate effect
import { signal, effect } from 'react-signaler';
const name = signal('Peter');
const dispose = effect(() => {
console.log(name.get());
});
// first console output: Peter
name.set('Erik');
// console output from lazy call: Erik
Dispose effect
Effects are disposables with dispose(...(Signal | ReadonlySignal)[]): void function.Example:
dispose();
If the effect is related to multiple signals, there is a possibility to dispose one or more signals.Important if some of the related signals are disposed from the effect but the effect triggered then the disposed signals automatically are subscribing again.Example:
dispose(nameSignal, ageSignal);
useSignalEffect() Create Effect in react component
import { useSignal, useComputed, useSignalEffect } from 'react-signaler';
const Component: FC = () => {
const { signal } = useSignal();
const { computed } = useComputed();
const { effect } = useSignalEffect();
const name = signal('name', () => 'Peter');
const upperCaseName = computed('upperCaseName', () =>
name.get().toUpperCase(),
);
const dispose = effect('log-user-name', () => {
console.log(upperCaseName.get());
});
return <h1>name: {upperCaseName()}</h1>;
};
lazyEffect(handler: ()=>void, watch: (Signal | ReadonlySignal)[]): Dispose
Lazy effect almost the same as effect.
Lazy effect only runs after each related signals are changed.
Lazy effect watch: (Signal | ReadonlySignal)[] argument is required.
Create Lazy Effect
import { signal, lazyEffect } from 'react-signaler';
const name = signal('Peter');
const dispose = lazyEffect(() => {
console.log(name.get());
}, [name]);
name.set('Erik');
// first console output from lazy call: Erik
Create Lazy Effect inside react component
import { useSignal, useComputed, useSignalLazyEffect } from 'react-signaler';
const Component: FC = () => {
const { signal } = useSignal();
const { computed } = useComputed();
const { lazyEffect } = useSignalLazyEffect();
const name = signal('name', () => 'Peter');
const upperCaseName = computed('upperCaseName', () =>
name.get().toUpperCase(),
);
const dispose = lazyEffect(
'log-user-name',
() => {
console.log(upperCaseName.get());
},
[upperCaseName],
);
return <h1>name: {upperCaseName()}</h1>;
};
Features
batch<R>(cb: () => R): R
Batch can avoid unnecessary update steps.
import { signal, effect, batch } from 'react-signaler';
const count = signal(0);
const dispose = effect(() => {
console.log(count.get());
});
// first console output: 0
count.set(1);
count.set(2);
count.set(3);
// console output: 1
// console output: 2
// console output: 3
batch(() => {
count.set(1);
count.set(2);
count.set(3);
});
// after batch console output: 3
untrack<R>(signal: Signal<R>): Runtrack<R>(signal: ReadonlySignal<R>): Runtrack<R>(cb: () => R): R
Untrack can avoid any subscribes.
import { signal, effect } from 'react-signaler';
import { thisFunctionReadOtherSignals } from 'somewhere';
const name = signal('');
const dispose = effect(() => {
const name = name.get();
untrack(() => {
thisFunctionReadOtherSignals(name);
});
});
name.set('Peter');