packages/recoil-sync/RecoilSync_URLTransit.js (105 lines of code) (raw):
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+recoil
* @flow strict-local
* @format
*/
'use strict';
import type {RecoilURLSyncOptions} from './RecoilSync_URL';
const {DefaultValue} = require('Recoil');
const {useRecoilURLSync} = require('./RecoilSync_URL');
const React = require('react');
const {useCallback, useMemo} = require('react');
const err = require('recoil-shared/util/Recoil_err');
const transit = require('transit-js');
export type TransitHandler<T, S> = {
tag: string,
class: Class<T>,
write: T => S,
read: S => T,
};
export type RecoilURLSyncTransitOptions = $Rest<
{
...RecoilURLSyncOptions,
// $FlowIssue[unclear-type]
handlers?: $ReadOnlyArray<TransitHandler<any, any>>,
},
{
serialize: mixed => string,
deserialize: string => mixed,
},
>;
const BUILTIN_HANDLERS = [
{
tag: 'Date',
class: Date,
write: x => x.toISOString(),
read: str => new Date(str),
},
{
tag: 'Set',
class: Set,
write: x => Array.from(x),
read: arr => new Set(arr),
},
{
tag: 'Map',
class: Map,
write: x => Array.from(x.entries()),
read: arr => new Map(arr),
},
{
tag: '__DV',
class: DefaultValue,
write: () => 0, // number encodes the smallest in URL
read: () => new DefaultValue(),
},
];
function useRecoilURLSyncTransit({
handlers: handlersProp = [],
...options
}: RecoilURLSyncTransitOptions): void {
if (options.location.part === 'href') {
throw err('"href" location is not supported for Transit encoding');
}
const handlers = useMemo(
() => [...BUILTIN_HANDLERS, ...handlersProp],
[handlersProp],
);
const writer = useMemo(
() =>
transit.writer('json', {
handlers: transit.map(
handlers
.map(handler => [
handler.class,
transit.makeWriteHandler({
tag: () => handler.tag,
rep: handler.write,
}),
])
.flat(1),
),
}),
[handlers],
);
const serialize = useCallback(x => writer.write(x), [writer]);
const reader = useMemo(
() =>
transit.reader('json', {
handlers: handlers.reduce((c, {tag, read}) => {
c[tag] = read;
return c;
}, {}),
mapBuilder: {
init: () => ({}),
add: (ret, key, val) => {
ret[key] = val;
return ret;
},
finalize: ret => ret,
},
}),
[handlers],
);
const deserialize = useCallback(x => reader.read(x), [reader]);
useRecoilURLSync({...options, serialize, deserialize});
}
function RecoilURLSyncTransit(props: RecoilURLSyncTransitOptions): React.Node {
useRecoilURLSyncTransit(props);
return null;
}
module.exports = {useRecoilURLSyncTransit, RecoilURLSyncTransit};