reactjs-util
v1.0.2
Published
Javascript/React Hooks/Utils Library
Downloads
3
Readme
JS Utils libs
Fully inspired by Mark E's @react-utils
Helpers for building React apps. Also offers React utilities based on RxJS
Install
// Via NPM
$ npm install --save reactjs-util
// Via Yarn
$ yarn add reactjs-util
Modules
It's often desirable to create self-contained React components called "modules", which from the outside look like a dumb React component, but inside includes a whole sub-tree of interacting components, and can be thought of as a complete mini-app in one component.
An obvious candidate for this is a "page" (e.g. a show page, with its own component tree and state shared between the whole page).
A "module" has the following characteristics:
from the outside, it looks just like a dumb presentational component, whose interface is only props (no context)
it has its own subtree of components, which are either
- dumb (props-only) presentational components, e.g.
SideBar
with no app logic - controllers, which have no styles, but do have logic, e.g.
SideBarController
and only return one thing: their namesake equivalent presentational component, e.g.SideBar
- dumb (props-only) presentational components, e.g.
it has its own list of stores, shared between all its descendant controllers for maintaining state
it has its own list of services (e.g. an api client, a remote control button listener), shared between all its descendant controllers
its props can be accessed by its descendant controllers
createModule create a module, whose descendants can access its stores, services and props
Hooks
useAtom
Wraps a changing value in a React component into a ReadonlyAtom
. The opposite of useRxState
Just as:
useRxState
unwrapsmyNumber$
-->number
(i.e.Observable<number>
--->number
),useAtom
goes the other way and wrapsmyNumber
-->myNumber$
(i.e.number
--->ReadonlyAtom<number>
).
import { useAtom } from "reactjs-util";
const ScoreCard = ({ score }: { score: number }) => {
const score$ = useAtom(score);
// score$ will refer to the same ReadonlyAtom that you can use how you wish!
// For example...
useSubscribe(score$.pipe(withPrevious), ([newScore, oldScore]) => {
console.log("Score changed from", oldScore, "to", newScore);
});
};
useRxState
unwraps a stateful Rx Observable. The opposite of useAtom
. Automatically update from one or more Atom
s or (synchronously emitting) Rx Observable
s.
import { useRxState } from "reactjs-util";
const ScoreCard = () => {
const [name, score] = useRxState([name$, score$]);
return (
<div>
{name} has {score} points!
</div>
);
};
You can either pass a single observable
const name = useRxState(name$);
...or a tuple...
const [name, score] = useRxState([name$, score$]);
...or a lookup...
const { name, theScore } = useRxState({ name: name$, theScore: score$ });
...in each case the returned values are correctly typed.
Also, you can pass a function (that returns a single observable / tuple / lookup as above) to avoid creating unnecessary obvservables on every render; the function will only get evaluated once on initialize.
const ScoreCard = () => {
const name = useRxState(() => user$.map((u) => u.name));
return <div>My name is {name}</div>;
};
useSubscribe
Subscribe to any Rx Observable
, (and clean up by correctly unsubscribing when unmounted).
import { useSubscribe } from "reactjs-util";
const HelloController = () => {
useSubscribe(click$, (click) => {
// do something with the click
});
// ...
};
The second argument is the same callback you'd use if you were subscribing directly, e.g.
if you were doing click$.subscribe((click) => { /* do something */ })
.
The hook manages unsubscribing for you, and also behaves as expected regarding enclosed variables (which isn't a given), i.e.
const someVar = ...;
useSubscribe(click$, (click) => {
// do something with the someVar - it'll be the "correct" one, not the one from a previous render
});
useModule
access stores, services and module props from the containing module. See createModule
createModule
createModule
A "module" is a self-contained React component that has its own lookup of stores and services, and interacts with the outside world only via props.
A module is initialised with stores, services and props, then deeply-nested child controller components can use useModule
to access these as needed.
Usage:
src/myModule/stores.ts
export const createStores = () => ({
timer: new TimerStore(),
// ...
});
src/myModule/services.ts
export const createServices = () => ({
exampleService: new ExampleService(),
// ...
});
src/myModule/index.ts
(the module component itself)
Initialize the stores, services and module props in the module root component.
import { createModule } from "reactjs-util";
import { createStores } from "./stores";
import { createServices } from "./services";
import { RootController } from "./components/RootController";
export type Props = {
onClick: () => void;
initialTime: number;
//...
};
export const [MyModule, useModule] = createModule<
Props,
typeof createStores,
typeof createServices
>({
createStores,
createServices,
root: RootController, // RootController is a controller component you've defined yourself
});
The exported component MyModule
is usable as you would any React component, with its props as the interface:
<MyModule initialTime={time} onClick={handleClick} />
src/myModule/components/ClockController.ts
(some child component)
Then consume the stores / services as needed in deeply-nested child controllers
import { useModule } from "./src/utils/create-module";
export function ClockController() {
const { stores, services } = useModule();
// Use them ...
useEffect(() => {
services.exampleService.doStuff();
});
const currentTime = useRxState(stores.timer.time$);
return <Clock time={time} />;
}
Using module props
You normally won't need to, but the props passed in to the module component from outside will be available...
... to controllers ...
import { useModule } from "./src/utils/create-module";
export function ClockController() {
const { moduleProps$ } = useModule();
function handleClick() {
moduleProps$.get().onClick(); // gets the "onClick" prop passed in to the module itself and calls it
}
// ...
}
... and to createServices
and createStores
if you need them
import { ReadonlyAtom } from "reactjs-util";
import { Props } from "./src/utils/create-module";
export const createStores = (moduleProps$: ReadonlyAtom<Props>) => ({
timer: new TimerStore(moduleProps$.get().initialTime),
// ...
});
Semantic release
Release management is automated using semantic-release.