Watch the video tutorial!
npm i reconnect.js
import React from 'react';
import {useOutlet, useOutletSetter, useOutletDeclaration} from 'reconnect.js';
function App() {
useOutletDeclaration('add', 0);
return (
<div style={{padding: 10}}>
<Value />
<ActionBar />
</div>
);
}
function Value() {
const [value] = useOutlet('add');
return <h1>{value}</h1>;
}
function ActionBar(props) {
return (
<div>
<Add />
<Sub />
<Reset />
</div>
);
}
function Add() {
const [value, setValue] = useOutlet('add');
return <button onClick={() => setValue(value + 1)}>+1</button>;
}
function Sub() {
const [_, setValue] = useOutlet('add');
return <button onClick={() => setValue((value) => value - 1)}>-1</button>;
}
function Reset() {
const setValue = useOutletSetter('add');
return <button onClick={() => setValue(0)}>RESET</button>;
}
export default App;
As you can see, the value backed by the add
outlet can be shared between:
- Sibling components like
Add
andSub
. - Nested components like
Value
and the childrenAdd
andSub
insideActionBar
.
It just works without extra configurations!
React can pass state into child components easily. But if you'd like to share states between sibling components, normally you will have to:
- create the state in your parent component
- pass the value down as props into child components
- pass the setter function down as props into child components if child components need to modify them
When things come to deeply nested components, it's getting worse.
If you'd like to share states between some deeply nested components, you normally has two options:
- Use
React Context
. Not that trivial, the rough steps looks like this:- Create a context via
React.createContext()
- Create
Provider
component, wrap theContext.Provider
- Put the state you'd like to share into the created
Provider
- Export your context instance. For the components who'd like to access the shared state, import it and call
useContext
. - BTW, if you'd like to modify the state from child components, normally you will need to create a separated Context and Provider. So you will have to iterate through above steps again.
- Create a context via
- Or, you might choose to use another
state management library
such asRedux
, that brings another layers of complexity. These libraries are awesome, but sometimes you just want things to be simpler.
Use value
and setter
for an outlet. It might create new outlet if initialValue
is passed. The signature is just like React.useState
.
- The
key
can be anything, not limited to astring
. - The
initialValue
can be a callback function, which returns the actual value. - If the
initialValue
is passed, Reconnect.js allows creating new outlet by callinggetNewOutlet()
. Otherwise, it callsgetOutlet()
to retrieve existing one. - The
setter
returned from this API can also accept a callback as input. And if so, it will call it to retrieve the actual value. - The
options
can be used to control advanced behavior for the outlet object. Normally we won't need to pass it. You can seegetNewOutlet
API for more detail about theoptions
.
declare function useOutlet<T>(
key: any,
initialValue?: initialValueOrGetter<T>,
options?: OutletOptions,
): [T, (value: nextValueOrGetter<T>) => void];
Use a new outlet:
function Add() {
const [value, setValue] = useOutlet('add', 0);
return <button onClick={() => setValue(value + 1)}>+1</button>;
}
Use an already existed outlet:
function Add() {
const [value, setValue] = useOutlet('add');
return <button onClick={() => setValue(value + 1)}>+1</button>;
}
Use setter
for an outlet. When your component only need the setter rather than the value backed by the outlet, use this API to avoid your component from re-rendering due to the value changes.
declare function useOutletSetter<T>(
key: any,
): (value: nextValueOrGetter<T>) => void;
function ResetValue() {
const setValue = useOutletSetter('add');
return <button onClick={() => setValue(0)}>RESET</button>;
}
Declare an outlet without using its value and setter. If the outlet doesn't exist yet, create it.
It's useful when you want to declare an outlet for your child components, but the root component itself doesn't need either value and setter, so when the value is changed, the root component won't re-render.
declare function useOutletDeclaration<T>(
key: any,
initialValue: initialValueOrGetter<T>,
): void;
function App() {
useOutletDeclaration('add', 0);
return (
<div style={{padding: 10}}>
<Value />
<Add />
</div>
);
}
function Add() {
const [value, setValue] = useOutlet('add');
return <button onClick={() => setValue(value + 1)}>+1</button>;
}
function Value() {
const [value] = useOutlet('add');
return <h1>{value}</h1>;
}
Get partial state from an outlet by passing a selector function.
When a component uses this hook, it will be rendered only when the selected value is changed, which let us uses partial of the outlet state rather than the whole state.
When using this hook, the selector passed in should be wrapped with React.useCallback().
declare function useOutletSelector<T, U>(key: any, selector: (state: T) => U): U;
function PartialValue() {
const selector = React.useCallback(state => state.inner.value, []);
const partialValue = useOutletSelector('test', selector);
return (
<div>{partialValue}</div>
)
}
An outlet let producers or consumers to publish or subscribe value changes.
export interface Outlet<T> {
/**
* Subscribe to value changes.
*
* @param handler - The value change listener function
* @returns A function to unregister the value change
*/
register: (handler: valueChangeListener<T>) => unregisterOutlet;
/**
* Change the value backed by this outlet and publish to all subscribers.
*
* @param value - The value you'd like to change or a callback function to produce the value.
*/
update: (value: nextValueOrGetter<T>) => void;
/**
* Get the value backed by this outlet
*/
getValue: () => T;
/**
* Get the subscribers count for this outlet
*/
getRefCnt: () => number;
}
Get or create an outlet using given key.
This method first search global registry to see if the key has a corresponding outlet, if so returns it. Otherwise it will create a new outlet and set to global registry, then returns it.
- The
initialValue
can be a callback function, which returns the actual value. - The
options
has a propertyautoDelete
, which indicates whetherReconnect.js
should automatically remove the outlet when the number of subscribers down to 0. The default value istrue
, but when you create outlets explicitly by callinggetNewOutlet
, you should set it to false.
export interface OutletOptions {
autoDelete?: boolean;
}
declare function getNewOutlet<T>(
key: any,
initialValue: initialValueOrGetter<T>,
options?: OutletOptions,
): Outlet<T>;
const ValueOutlet = getNewOutlet('value', 0, {autoDelete: false});
if (typeof window !== undefined) {
// so you can call it from browser's inspector
window.addOne = () => {
ValueOutlet.update(ValueOutlet.getValue() + 1);
};
}
function App() {
return (
<div style={{padding: 10}}>
<Value />
</div>
);
}
function Value() {
const [value] = useOutlet('value');
return <h1>{value}</h1>;
}
Get an existing outlet. If not found, return the NullOutlet
singleton.
declare function getOutlet<T>(key: any): Outlet<T>;
Check if the outlet already exists.
declare function hasOutlet(key: any): boolean;
Force remove the outlet from global registry.
By default, this function won't remove outlets with remaining subscribers, unless the force
flag is set to true
.
declare function removeOutlet(key: any, force?: boolean): void;
Test if the outlet is NullOutlet singleton.
Normally applications don't need to call this API.
declare function isNull(outlet: Outlet<any>): boolean;
More than welcome