@realism/core v0.0.3
Realism Core
Installation
npm i @realism/coreor
yarn add @realism/coreDocumentation
The @realism/core library is a group of basic react-hooks for more rational appllication data controll in the React-world.
1. useSlice
The useSlice is a top level utility which creates slice of data with different hooks for it's control. Slice of data is a plain javascript object which can be passed as a component prop or through React context etc. It simplifies a control over one object unlike multiple separated states wich created via the useState hook from the React library.
To create a slice we need to describe a type of the slice and set a default value:
import { FC, useEffect } from 'react';
import { useSlice, TSlice } from '@realism/core';
type TPerson = {
name: string;
age: number;
};
const DEFAULT_PERSON: TPerson = {
name: '',
age: 0,
};
const fetchPerson = async () => ({
name: 'John',
age: 30,
});
const Person: FC = () => {
const person = useSlice(DEFAULT_PERSON);
useEffect(() => {
fetchPerson().then((data) => {
person.actions.setState(data);
});
}, [person]); // it doesn't matter because of the slice is an immutable object
return <PersonCard person={person} />;
};
type TPersonCardProps = {
person: TSlice<TPerson>;
};
const PersonCard: FC<TPersonCardProps> = ({ person }) => {
const name = person.selectors.useName();
if (!name) {
return <span>Loading...</span>;
}
return (
<div>
<span>My name is {name}!</span>
</div>
);
};A slice as said before is a javascript object that has 3 properties: actions, selectors and getState. Let's take a closer look at this:
actionsallows us to change values inside of theslice;selectorsallow us to get values from thesliceinside of a component and subscribe a component to the slice's data changing;getStateallows us to get values from thesliceinside simple funtions;
Actions is an object with methods for the slice's data changing. It has two common actions and some special actions which depend on the slice's data structure. For example, the slice Person from the code above has following actions:
{
// Common actions
setState: (arg0: TPerson) => void;
cleanState: () => void;
// Special actions
setName: (arg0: string) => void;
setAge: (arg0: number) => void;
}As we can see common actions includes setState and cleanState actions. setState action receives one argument which must be a whole slice's data object. The object will be set to the slice as whole data. cleanState action restores slice's data to the default value which was specified during slice creation.
Special actions are formed by concatenating a word set with a name of the slice's data property. For example, if we have name property the action has name setName. A special action receives one argument with type of the property. Thus we have as many special actions as there were properties in the slice's data object.
Selectors is an object with react hooks for the getting whole slice's data or separate pieces of it. It's only used inside of a react component or other react hooks. Selectors subscribe a react component to slice's data changing. It has one common selector and some special selectors which depend on the slice’s data structure. For example, the slice Person from the code above has following selectors:
{
// Common selectors
useState: () => TPerson;
// Special selectors
useName: () => string;
useAge: () => number;
}Common selector useState is used for getting whole slice's data. That's why the selector subscribes a react-component to any property changing. It's better to use special selectors except the case when you need whole slice's data in one component.
Special selectors are formed by concatenating a word use with a name of the slice's data property. For example, if we have name property the selector has name useName. A special selector doesn't receive any arguments and returns value with a type of corresponding data property. Thus we have as many special selectors as there were properties in the slice's data object.
getState is a pure function which returns a whole slice's data object. Data is actual on the moment of the getState method calling. We shouldn't use getState inside of react component or react hooks because of it doesn't subscribe the component to slice's data changing. The method is used inside of callback or methods where we can't use react hooks.
Important! A slice is an immutable object. It means that a slice doesn't rerender a component in which it was created after the slice's data changing. To rerender a component we have to use hooks from the slice's selectors object.
2. useEmitter
The useEmitter is utility wich creates emitter. Emitter allows us to subscribe to methods and emit the methods anywhere in an react application. The emitter doesn't use browser events or similar abilities. Inside the emitter only react opportunities are used.
When creating an emitter we need to specify a type of methods which we'll use. The type is an object with keys of event names and values of methods. Let's create an emitter with two methods:
import { FC, createContext, useEffect, useCallback, useContext } from 'react';
import { useEmitter, DEFAULT_EMITTER_CONTEXT, TEmitter } from '@realism/core';
type TComponentEmitter = {
send: (arg0: string) => void;
}
const ComponentContext = createContext<TEmitter<TComponentEmitter>>(DEFAULT_EMITTER_CONTEXT);
const Component: FC = () => {
const emitter = useEmitter<TComponentEmitter>();
const onSend = useCallback((value: string) => {
console.log({ value });
}, []);
useEffect(() => {
return emitter.subscribe('send', onSend);
}, [
emitter, // it doesn't matter because of the emmiter is an immutable object
onSend,
]);
return (
<ComponentContext.Provider value={emitter}>
<ComponentChild />
</ComponentContext.Provider>
);
};
const ComponentChild: FC = () => {
const emitter = useContext(ComponentContext);
const handleClick = useCallback(() => {
emitter.emit('send', 'clicked');
}, [emitter]); // it doesn't matter because of the emmiter is an immutable object
return (
<button onClick={handleClick}>Click me</button>
);
};The emitter is an object which has three methods:
subscribeis used for regestring a method into the emitter. Thesubscribemethod receives two arguments: name and method. It's possible to subscribe several methods using one name. Thesubscribemethod returns a function which can be used for unsubscribing the method. That's why the unsubscribing function was returned from the effect's callback in the example above.emitis used for calling a method which was specified in thesubscribemethod erlier. Theemitmethod receives more than one arguments. First argument is required. It specifies a name of the subscribed method. Rest arguments are arguments of the subscried method. Theemitmethod retuns nothing. It calls all methods which were subscribed by thesubscribemethod.useRenderingSubscriptionis a react hook which is used for regestring a method into the emitter likesubscribemethod. But theuseRenderingSubscriptionhook does it during the first component's rendering unlike thesubscribemethod which should be used only in theuseEffectcallback after the first rendering. It solves the problem when we want to useemitmethods in theuseEffectof child components. In this case theuseEffectof the child component will be calld before theuseEffectof the parent component.
3. useRegistry
The useRegistry is utility which creates registry. Registry allow us to store functions by keys. The utility is used under the hood of the useEmitter. Registry is an javascript object which has several methods to save, get and remove functions by it's keys:
addmethod adds a function to theregistryby a key. It receives two arguments:keyandfunction. It's allowed to call multiple times theaddmethod with the same key. All functions with the same key will be added to one array. The array will gotten by the key using methodget.addmethod returns a key wich can be used to remove added function.getmethod returns an array of functions which were added to theregistryby theaddmethod. The method receives a key wich was used in theaddmethod. If we pass unknown key to thegetmethod then empty array will be returned without any error.removemethod removes one function by a key which was returned byaddmethod.clearmethod removes all functions which were added by theaddmethod. It receives a key which was specified in theaddmethod.
import { FC, useCallback, useEffect, ChangeEventHandler } from 'react';
import { useRegistry } from '@realism/core';
const CLEAR_KEY = 'clear';
const Component: FC = () => {
const registry = useRegistry();
const clearState = useCallback(() => {
console.log('Clearing...');
}, []);
const handleCheck = useCallback<ChangeEventHandler<HTMLInputElement>>(({ target }) => {
if (target.value) {
registry.add(CLEAR_KEY, clearState);
} else {
registry.clear(CLEAR_KEY);
}
}, [
registry, // it doesn't matter because of the registry is an immutable object
clearState
]);
useEffect(() => () => {
const clearingHandlers = registry.get(CLEAR_KEY);
clearingHandlers.forEach((handler) => {
handler();
});
}, [registry]); // it doesn't matter because of the registry is an immutable object
return (
<input type="checkbox" onChange={handleCheck} />
);
};4. useFirstRender
The useFirstRender is a react hook which can be helpfull if we want to do some logic only during the first component render. The hook returns react ref object. The current property of the object contains a flag about the first rendering.
import { FC, useEffect, useState } from 'react';
import { useFirstRender } from '@realism/core';
type TComponentProps = {
data: string;
};
const Component: FC<TComponentProps> = ({ data }) => {
const isFirstRender = useFirstRender();
const [localData, setLocalData] = useState(data);
useEffect(() => {
if (!isFirstRender.current) {
setLocalData(data);
}
}, [data]);
return null;
};