react-use#useLocalStorage TypeScript Examples
The following examples show how to use
react-use#useLocalStorage.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.tsx From easy-email with MIT License | 6 votes |
PresetColorsProvider: React.FC<{}> = (props) => {
const [currentColors, setCurrentColors] = useLocalStorage<string[]>(
CURRENT_COLORS_KEY,
defaultPresetColor
);
const colorDivRef = useRef(document.createElement('div'));
const addCurrentColor = useCallback(
(newColor: string) => {
colorDivRef.current.style.color = '';
colorDivRef.current.style.color = newColor;
if (colorDivRef.current.style.color) {
const newColors = [...new Set([...currentColors!, newColor])]
.filter(Boolean)
.slice(0, 16);
setCurrentColors(newColors);
}
},
[currentColors, setCurrentColors]
);
const value = useMemo(() => {
return {
colors: currentColors!,
addCurrentColor,
};
}, [addCurrentColor, currentColors]);
return useMemo(() => {
return (
<PresetColorsContext.Provider value={value}>
{props.children}
</PresetColorsContext.Provider>
);
}, [props.children, value]);
}
Example #2
Source File: useI18n.ts From back-home-safe with GNU General Public License v3.0 | 6 votes |
[UseI18nProvider, useI18n] = constate(() => {
const [language, setLanguage] = useLocalStorage(
"language",
languageType["ZH-HK"]
);
const { i18n } = useTranslation();
useEffect(() => {
i18n.changeLanguage(language);
}, [i18n, language]);
return {
language: language || languageType["ZH-HK"],
setLanguage,
};
})
Example #3
Source File: useMigration.ts From back-home-safe with GNU General Public License v3.0 | 6 votes |
useMigration = () => {
const [, , removePasswordHash] = useLocalStorage<string | null>(
"password_hash",
null
);
useEffect(() => {
// Old version store password with SHA256, which can be brute-forced
removePasswordHash();
}, [removePasswordHash]);
const { unlocked, setValue } = useData();
// Old versions travel records has no unique id
useEffect(() => {
setValue((prev) => ({
...prev,
travelRecords: prev.travelRecords.map((item) => {
if (has("id", item)) return item;
return { ...(item as TravelRecord), id: uuid() };
}),
}));
}, [unlocked, setValue]);
}
Example #4
Source File: HomeView.tsx From joplin-utils with MIT License | 6 votes |
HomeView: React.FC = () => {
const [language] = useLocalStorage<LanguageEnum>('language', getLanguage())
return (
<Card>
<ReactMarkdown className={css.home}>
{language === LanguageEnum.En ? README : README_ZH_CN}
</ReactMarkdown>
</Card>
)
}
Example #5
Source File: AutoSaveAndRestoreEmail.tsx From easy-email with MIT License | 5 votes |
export function AutoSaveAndRestoreEmail() {
const formState = useFormState<any>();
const { reset, mutators } = useForm();
const { id = 'new' } = useQuery<{ id: string }>();
const [currentEmail, setCurrentEmail] =
useLocalStorage<IEmailTemplate | null>(id, null);
const dirty = getIsFormTouched(formState.touched as any);
const [visible, setVisible] = useState(Boolean(currentEmail));
useEffect(() => {
if (dirty) {
setCurrentEmail(formState.values);
}
}, [dirty, formState.values, setCurrentEmail]);
useInterval(() => {
if (dirty) {
setCurrentEmail(formState.values);
}
}, 5000);
const onRestore = () => {
if (currentEmail) {
reset(currentEmail);
setCurrentEmail(null);
setVisible(false);
mutators.setFieldTouched(Object.keys(formState.touched || {})[0], true);
}
};
const onDiscard = () => {
setCurrentEmail(null);
setVisible(false);
};
const onBeforeConfirm = () => {
setCurrentEmail(null);
};
return (
<>
<Modal
title='Restore email?'
visible={Boolean(visible && currentEmail)}
onOk={onRestore}
okText='Restore'
cancelText='Discard'
onCancel={onDiscard}
style={{ zIndex: 10000 }}
>
<p>Are you want to restore unsaved email?</p>
</Modal>
<WarnAboutUnsavedChanges onBeforeConfirm={onBeforeConfirm} />
</>
);
}
Example #6
Source File: context.tsx From storefront with MIT License | 5 votes |
AuthProvider: React.FC = (props) => {
const router = useRouter();
const [userData, setUserData] = useLocalStorage<UserData>('authToken');
/**
* Login the user.
*
* @param newUserData
* @param urlToRedirect URL where user needs to be redirected after login.
*/
const login = (newUserData: UserData, urlToRedirect = '/my-account') => {
// Set the authToken, user ID and username in localStorage.
setUserData(newUserData);
// Redirect the user to the given URL.
if (urlToRedirect != null) {
router.push(urlToRedirect);
}
};
/**
* Logout the user.
*
* @param urlToRedirect URL where user needs to be redirected after logout.
*/
const logout = (urlToRedirect = '/login') => {
// Set auth data value in localStorage to empty.
setUserData(undefined);
// Redirect the user to the given URL.
if (urlToRedirect != null) {
router.push(urlToRedirect);
}
};
return (
<AuthContext.Provider
value={{
authToken: userData?.authToken,
login,
logout,
userData: userData?.authToken == null ? null : userData,
}}
{...props}
/>
);
}
Example #7
Source File: useCamera.ts From back-home-safe with GNU General Public License v3.0 | 5 votes |
[UseCameraProvider, useCamera] = constate(() => {
const [hasCameraSupport] = useState("mediaDevices" in navigator);
const [preferredCameraId, setPreferredCameraId] = useLocalStorage(
"preferred_camera_id",
"AUTO"
);
const [cameraList, setCameraList] = useState<MediaDeviceInfo[] | null>(null);
const initCameraList = useCallback(async () => {
try {
if (
!hasCameraSupport ||
!hasIn("enumerateDevices", navigator.mediaDevices)
) {
setCameraList([]);
return;
}
const deviceList = await navigator.mediaDevices.enumerateDevices();
const cameraList = deviceList.filter<MediaDeviceInfo>(
(device): device is MediaDeviceInfo => device.kind === "videoinput"
);
setCameraList(cameraList);
} catch (e) {
alert("Unable to list device.\n\n" + e);
}
}, [hasCameraSupport]);
useEffect(() => {
initCameraList();
}, [hasCameraSupport, initCameraList]);
useEffect(() => {
if (
cameraList !== null &&
preferredCameraId !== "AUTO" &&
!any(({ deviceId }) => deviceId === preferredCameraId, cameraList)
) {
setPreferredCameraId("AUTO");
}
}, [cameraList, setPreferredCameraId, preferredCameraId]);
return {
preferredCameraId: !any(
({ deviceId }) => deviceId === preferredCameraId,
cameraList || []
)
? "AUTO"
: preferredCameraId,
cameraList: cameraList || [],
setPreferredCameraId,
hasCameraSupport,
};
})
Example #8
Source File: useStakeSignatures.tsx From mStable-apps with GNU Lesser General Public License v3.0 | 5 votes |
useStakeSignatures = () =>
useLocalStorage<StakeSignatures>('stakeSignatures', {}) as unknown as [StakeSignatures, Dispatch<SetStateAction<StakeSignatures>>]
Example #9
Source File: LayoutView.tsx From joplin-utils with MIT License | 5 votes |
LayoutView: React.FC = () => {
const [language, setLanguage] = useLocalStorage<LanguageEnum>(
'language',
getLanguage(),
)
const [{ value: list }, fetch] = useAsyncFn(
async (language: LanguageEnum) => {
console.log('language: ', language)
await i18n.init({ en, zhCN }, language)
return routeList.map((item) => ({
...item,
title: i18n.t(item.title as any),
}))
},
[],
)
useMount(() => fetch(language!))
const [refreshKey, { inc }] = useCounter(0)
async function changeLanguage(value: LanguageEnum) {
setLanguage(value)
await fetch(value)
inc()
}
return (
<Layout className={css.app}>
<Layout.Sider className={css.sider} width="max-content">
<h2 className={css.logo}>Joplin Batch</h2>
<Menu>
{list &&
list.map((item) => (
<Menu.Item key={item.path as string}>
<Link to={item.path as string}>{item.title}</Link>
</Menu.Item>
))}
</Menu>
</Layout.Sider>
<Layout>
<Layout.Header className={css.header}>
<Select
options={[
{ label: 'English', value: LanguageEnum.En },
{ label: '中文', value: LanguageEnum.ZhCN },
]}
value={language}
onChange={changeLanguage}
/>
</Layout.Header>
<Layout.Content className={css.main}>
{list && <RouterView key={refreshKey} />}
</Layout.Content>
</Layout>
</Layout>
)
}
Example #10
Source File: SettingsView.tsx From joplin-utils with MIT License | 5 votes |
SettingsView: React.FC = () => {
const [form] = Form.useForm<Config>()
async function onFinish() {
if (!(await form.validateFields())) {
return
}
const values = form.getFieldsValue()
console.log('onFinish: ', values)
try {
joplinApiGenerator.token = values.token
joplinApiGenerator.baseUrl = values.baseUrl
await joplinApiGenerator.noteApi.list({ limit: 1 })
setSettings(values)
message.success(i18n.t('settings.msg.success'))
} catch (e) {
console.error(e)
message.error(i18n.t('settings.msg.error'))
}
}
const [settings, setSettings] = useLocalStorage<Config>('settings')
return (
<Card>
<h2>{i18n.t('settings.title')}</h2>
<Form
form={form}
onFinish={onFinish}
initialValues={
{ token: settings?.token, baseUrl: settings?.baseUrl ?? 'http://localhost:41184' } as Partial<Config>
}
>
<Form.Item
name={'baseUrl' as keyof Config}
label={i18n.t('settings.form.baseUrl')}
rules={[{ required: true }]}
>
<Input type={'url'} />
</Form.Item>
<Form.Item name={'token' as keyof Config} label={i18n.t('settings.form.token')} rules={[{ required: true }]}>
<Input.Password />
</Form.Item>
<Form.Item>
<Button type={'primary'} htmlType={'submit'}>
{i18n.t('settings.action.submit')}
</Button>
</Form.Item>
</Form>
</Card>
)
}
Example #11
Source File: collection.tsx From react-notion-x with MIT License | 4 votes |
CollectionViewBlock: React.FC<{
block: types.CollectionViewBlock | types.CollectionViewPageBlock
className?: string
}> = ({ block, className }) => {
const { recordMap, showCollectionViewDropdown } = useNotionContext()
const { view_ids: viewIds } = block
const collectionId = getBlockCollectionId(block, recordMap)
const [isMounted, setIsMounted] = React.useState(false)
React.useEffect(() => {
setIsMounted(true)
}, [])
const defaultCollectionViewId = viewIds[0]
const [collectionState, setCollectionState] = useLocalStorage(block.id, {
collectionViewId: defaultCollectionViewId
})
const collectionViewId =
(isMounted &&
viewIds.find((id) => id === collectionState.collectionViewId)) ||
defaultCollectionViewId
const onChangeView = React.useCallback(
(collectionViewId: string) => {
console.log('change collection view', collectionViewId)
setCollectionState({
...collectionState,
collectionViewId
})
},
[collectionState, setCollectionState]
)
let { width: windowWidth } = useWindowSize()
if (isServer) {
windowWidth = 1024
}
const collection = recordMap.collection[collectionId]?.value
const collectionView = recordMap.collection_view[collectionViewId]?.value
const collectionData =
recordMap.collection_query[collectionId]?.[collectionViewId]
const parentPage = getBlockParentPage(block, recordMap)
const { style, width, padding } = React.useMemo(() => {
const style: React.CSSProperties = {}
if (collectionView?.type !== 'table' && collectionView?.type !== 'board') {
return {
style,
width: 0,
padding: 0
}
}
const width = windowWidth
// TODO: customize for mobile?
const maxNotionBodyWidth = 708
let notionBodyWidth = maxNotionBodyWidth
if (parentPage?.format?.page_full_width) {
notionBodyWidth = (width - 2 * Math.min(96, width * 0.08)) | 0
} else {
notionBodyWidth =
width < maxNotionBodyWidth
? (width - width * 0.02) | 0 // 2vw
: maxNotionBodyWidth
}
const padding =
isServer && !isMounted ? 96 : ((width - notionBodyWidth) / 2) | 0
style.paddingLeft = padding
style.paddingRight = padding
return {
style,
width,
padding
}
}, [windowWidth, parentPage, collectionView?.type, isMounted])
// console.log({
// width,
// padding
// })
if (!(collection && collectionView && collectionData)) {
console.warn('skipping missing collection view for block', block.id, {
collectionId,
collectionViewId,
collectionView,
collectionData,
recordMap
})
return null
}
const title = getTextContent(collection.name).trim()
if (collection.icon) {
block.format = {
...block.format,
page_icon: collection.icon
}
}
return (
<div className={cs('notion-collection', className)}>
<div className='notion-collection-header' style={style}>
{title && (
<div className='notion-collection-header-title'>
<PageIcon
block={block}
className='notion-page-title-icon'
hideDefaultIcon
/>
{title}
</div>
)}
{viewIds.length > 1 && showCollectionViewDropdown && (
<CollectionViewDropdownMenu
collectionView={collectionView}
collectionViewId={collectionViewId}
viewIds={viewIds}
onChangeView={onChangeView}
/>
)}
</div>
<CollectionView
collection={collection}
collectionView={collectionView}
collectionData={collectionData}
padding={padding}
width={width}
/>
</div>
)
}
Example #12
Source File: index.tsx From easy-email with MIT License | 4 votes |
export function useCollection() {
const [collection, setCollection] = useLocalStorage(
COLLECTION_KEY,
defaultData
);
const addCollection = useCallback(
(payload: CollectedBlock) => {
if (!collection) return;
collection[0].blocks.push({
id: payload.id,
title: payload.label,
description: payload.helpText,
thumbnail: payload.thumbnail,
data: payload.data,
});
setCollection([...collection]);
Message.success('Added to collection!');
},
[collection, setCollection]
);
const removeCollection = useCallback(
(id: string) => {
if (!collection) return;
collection[0].blocks = collection[0].blocks.filter(
(item) => item.id !== id
);
setCollection([...collection]);
Message.success('Remove collection');
},
[collection, setCollection]
);
const collectionCategory = useMemo((): BlockMarketCategory | null => {
if (!collection) return null;
const blockComponents = collection[0].blocks.map((item) => ({
id: item.id,
type: item.data.type,
title: item.title,
payload: item.data,
description: item.description,
thumbnail: item.thumbnail,
component: () => (
<BlockMaskWrapper
key={item.id}
type={item.data.type}
payload={item.data}
>
<div style={{ position: 'relative' }}>
<Picture src={item.thumbnail} />
<div
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
zIndex: 2,
}}
/>
<div
onClick={() => removeCollection(item.id)}
style={{
position: 'absolute',
top: 0,
right: 0,
transform: 'translate(27px, 35px)',
zIndex: 3,
}}
>
<IconFont iconName='icon-delete' />
</div>
</div>
</BlockMaskWrapper>
),
}));
return {
title: 'Collection',
name: 'Collection',
blocks: blockComponents,
};
}, [collection, removeCollection]);
return {
removeCollection,
addCollection,
collectionCategory,
};
}
Example #13
Source File: CheckoutForm.tsx From storefront with MIT License | 4 votes |
CheckoutForm: React.VFC<Props> = ({ cart, customer, loading, onSubmit }) => {
const router = useRouter();
const [creditCard, setCreditCard] = useState<{ cardType: string; lastFour: string }>();
const [paymentMethod, setPaymentMethod] = useLocalStorage<string>('paymentMethod');
const [paymentNonce, setPaymentNonce] = useState<string>();
const [shipToDifferentAddress, setShipToDifferentAddress] = useState(
!isShippingSameAsBilling(customer.shipping, customer.billing),
);
useEffect(() => {
if (router.query.step === 'review' && paymentMethod === 'braintree_cc' && creditCard == null) {
router.push('/checkout/billing-address');
}
}, [creditCard, paymentMethod, router]);
/**
* Handle changing steps.
*
* @param step The current step
*/
const handleChangeStep = (step: Partial<{ path: string }>) => {
// Everytime we change steps we have to check if both addresses match
setShipToDifferentAddress(!isShippingSameAsBilling(customer.shipping, customer.billing));
if (router.query.step != null && router.query.step !== step.path) {
router.replace('/checkout/[step]', `/checkout/${step.path}`, { shallow: true });
}
};
/**
* Called when the user wants both addresses to be the same.
*
* @param shippingSameAsBilling Whether addresses should match or not
*/
const handleShippingSameAsBillingChange = (shippingSameAsBilling: boolean) => {
setShipToDifferentAddress(!shippingSameAsBilling);
};
/**
* Handle submit of last step.
*/
const handleSubmit = ({
customerNote,
metaData,
transactionId,
}: Pick<CheckoutMutationVariables, 'customerNote' | 'metaData' | 'transactionId'>) => {
onSubmit({
customerNote,
metaData,
paymentMethod,
shipToDifferentAddress,
transactionId,
});
};
return (
<Grid container spacing={4}>
<Grid item xs={12} md={8}>
<Stepper
steps={[
{ path: 'billing-address', title: 'Billing Address' },
{ path: 'shipping-address', title: 'Shipping Address' },
{ path: 'shipping-options', title: 'Shipping Method' },
{ path: 'payment', title: 'Payment Method' },
{ path: 'review', title: 'Review' },
]}
activeStep={`${router.query.step}`}
onChangeStep={handleChangeStep}
>
{({ handleNext }) => [
<Step key="billing-address">
<BillingForm defaultValues={customer.billing} onSubmit={handleNext} />
</Step>,
<Step key="shipping-address">
<ShippingForm
defaultValues={customer.shipping}
shipToDifferentAddress={shipToDifferentAddress}
onShippingSameAsBillingChange={handleShippingSameAsBillingChange}
onSubmit={handleNext}
/>
</Step>,
<Step key="shipping-options">
<ShippingMethods
availableShippingMethods={cart.availableShippingMethods}
chosenShippingMethods={cart.chosenShippingMethods}
onSubmit={handleNext}
/>
</Step>,
<Step key="payment">
<PaymentMethods
customer={customer}
paymentMethod={paymentMethod}
onSubmit={(values) => {
setPaymentMethod(values.paymentMethod);
setPaymentNonce(values.paymentNonce);
setCreditCard(values.creditCard);
handleNext();
}}
/>
</Step>,
<Step key="review">
<CheckoutReview
cart={cart}
creditCard={creditCard}
customer={customer}
loading={loading}
paymentMethod={paymentMethod}
paymentNonce={paymentNonce}
onSubmit={handleSubmit}
/>
</Step>,
]}
</Stepper>
</Grid>
<Grid item xs={12} md={4}>
<CartSummary cart={cart} />
</Grid>
</Grid>
);
}
Example #14
Source File: App.tsx From back-home-safe with GNU General Public License v3.0 | 4 votes |
App = () => {
useMigration();
const [finishedTutorial, setFinishedTutorial] = useLocalStorage(
"finished_tutorial",
false
);
const [confirmPageIcon, setConfirmPageIcon] = useLocalStorage<string | null>(
"confirmPageIcon",
null
);
const { lockStore, unlocked, isEncrypted } = useData();
const { pathname } = useLocation();
const browserHistory = useHistory();
const handleBlur = useCallback(() => {
if (pathname !== "/qrReader" && pathname !== "/cameraSetting") lockStore();
}, [lockStore, pathname]);
useEffect(() => {
window.addEventListener("blur", handleBlur);
return () => {
window.removeEventListener("blur", handleBlur);
};
}, [handleBlur]);
const pageMap = useMemo<
{ route: RouteProps; component: React.ReactNode; privateRoute: boolean }[]
>(
() => [
{
privateRoute: false,
route: { exact: true, path: "/tutorial" },
component: <Tutorial setFinishedTutorial={setFinishedTutorial} />,
},
{
privateRoute: false,
route: { exact: true, path: "/login" },
component: <Login />,
},
{
privateRoute: true,
route: {
exact: true,
path: "/",
},
component: <MainScreen />,
},
{
privateRoute: true,
route: {
exact: true,
path: "/confirm/:id",
},
component: <Confirm confirmPageIcon={confirmPageIcon} />,
},
{
privateRoute: true,
route: {
exact: true,
path: "/qrGenerator",
},
component: <QRGenerator />,
},
{
privateRoute: true,
route: {
exact: true,
path: "/disclaimer",
},
component: <Disclaimer />,
},
{
privateRoute: true,
route: {
exact: true,
path: "/qrReader",
},
component: <QRReader />,
},
{
privateRoute: true,
route: {
exact: true,
path: "/cameraSetting",
},
component: <CameraSetting />,
},
{
privateRoute: true,
route: {
exact: true,
path: "/confirmPageSetting",
},
component: (
<ConfirmPageSetting
confirmPageIcon={confirmPageIcon}
setConfirmPageIcon={setConfirmPageIcon}
/>
),
},
{
privateRoute: true,
route: {
exact: true,
path: "/vaccinationQRReader",
},
component: <VaccinationQRReader />,
},
],
[confirmPageIcon, setConfirmPageIcon, setFinishedTutorial]
);
// transition group cannot use switch component, thus need manual redirect handling
// ref: https://reactcommunity.org/react-transition-group/with-react-router
useEffect(() => {
if (!unlocked && pathname !== "/login") {
browserHistory.replace("/login");
}
if (unlocked && pathname === "/login") {
browserHistory.replace("/");
}
}, [isEncrypted, unlocked, browserHistory, pathname]);
useEffect(() => {
if (!finishedTutorial && pathname !== "/tutorial") {
browserHistory.replace("/tutorial");
}
if (finishedTutorial && pathname === "/tutorial") {
browserHistory.replace("/");
}
}, [finishedTutorial, browserHistory, pathname]);
useEffect(() => {
const hasMatch = any(({ route }) => {
if (!route.path) return false;
return !isNil(matchPath(pathname, route));
}, pageMap);
if (!hasMatch) {
browserHistory.replace("/");
}
}, [browserHistory, pathname, pageMap]);
return (
<>
<GlobalStyle />
{pageMap.map(({ route, component, privateRoute }) =>
privateRoute && !unlocked ? (
<React.Fragment key={String(route.path)} />
) : (
<Route {...route} key={String(route.path)}>
{({ match }) => (
<CSSTransition
in={match != null}
timeout={300}
classNames="page"
unmountOnExit
>
<div className="page">
<Suspense fallback={<PageLoading />}>{component}</Suspense>
</div>
</CSSTransition>
)}
</Route>
)
)}
</>
);
}
Example #15
Source File: useEncryptedStore.ts From back-home-safe with GNU General Public License v3.0 | 4 votes |
useEncryptedStore = <T extends T[] | Object>({
key,
defaultValue: fallbackValue,
passthroughOnly = false,
}: {
key: string;
defaultValue: T;
passthroughOnly?: boolean;
}) => {
const [incognito, setIncognito] = useLocalStorage("incognito", false);
const [defaultValue] = useState<T>(fallbackValue);
const [password, setPassword] = useState<string | null>(null);
const [savedValue, setSavedValue, removeSavedValue] = useLocalStorage<string>(
key,
passthroughOnly
? undefined
: password
? encryptValue(JSON.stringify(defaultValue), password)
: JSON.stringify(defaultValue)
);
const isEncrypted = useMemo(() => {
try {
if (!savedValue) return false;
JSON.parse(savedValue);
} catch (e) {
return true;
}
return false;
}, [savedValue]);
const [decryptedValue, setDecryptedValue] = useState<T>(
!isEncrypted && savedValue ? JSON.parse(savedValue) : defaultValue
);
const [unlocked, setUnlocked] = useState(!isEncrypted);
useDeepCompareEffect(() => {
if (!unlocked || incognito || passthroughOnly) return;
if (isEncrypted && !password) return;
const value = JSON.stringify(decryptedValue);
console.log(value);
setSavedValue(password ? encryptValue(value, password) : value);
}, [decryptedValue]);
const initPassword = useCallback(
(newPassword: string) => {
if (isEncrypted || passthroughOnly) return;
const data = encryptValue(
savedValue || JSON.stringify(defaultValue),
newPassword
);
setSavedValue(data);
setPassword(newPassword);
},
[passthroughOnly, savedValue, setSavedValue, defaultValue, isEncrypted]
);
const unlockStore = useCallback(
(password: string) => {
if (!isEncrypted) return true;
try {
const decryptedValue = decryptValue(
savedValue || JSON.stringify(defaultValue),
password
);
setDecryptedValue(JSON.parse(decryptedValue));
setPassword(password);
setUnlocked(true);
return true;
} catch (e) {
return false;
}
},
[defaultValue, savedValue, isEncrypted]
);
const lockStore = useCallback(() => {
if (!isEncrypted) return;
setUnlocked(false);
setPassword(null);
setDecryptedValue(defaultValue);
}, [isEncrypted, defaultValue]);
return {
isEncrypted,
unlockStore,
lockStore,
value: decryptedValue,
setValue: setDecryptedValue,
initPassword,
unlocked,
incognito,
setIncognito,
password,
destroy: removeSavedValue,
hasData: !isNil(savedValue),
};
}
Example #16
Source File: useTravelRecord.ts From back-home-safe with GNU General Public License v3.0 | 4 votes |
[UseTravelRecordProvider, useTravelRecord] = constate(() => {
const { currentTime } = useTime();
const {
value: { travelRecords },
setValue,
} = useData();
const browserHistory = useHistory();
const [autoRemoveRecordDay, setAutoRemoveRecordDay] = useLocalStorage(
"auto_remove_record_after",
30
);
const { pastTravelRecord, currentTravelRecord } = useMemo(() => {
const { pastTravelRecord, currentTravelRecord } = travelRecords.reduce<{
pastTravelRecord: TravelRecord[];
currentTravelRecord: TravelRecord[];
}>(
(acc, item) => {
const isPast =
item.outTime && dayjs(item.outTime).isBefore(currentTime);
return isPast
? {
pastTravelRecord: [item, ...acc.pastTravelRecord],
currentTravelRecord: [...acc.currentTravelRecord],
}
: {
pastTravelRecord: [...acc.pastTravelRecord],
currentTravelRecord: [item, ...acc.currentTravelRecord],
};
},
{
pastTravelRecord: [],
currentTravelRecord: [],
}
);
return {
pastTravelRecord: sortRecord(pastTravelRecord),
currentTravelRecord: sortRecord(currentTravelRecord),
};
}, [travelRecords, currentTime]);
useEffect(() => {
setValue((prev) => ({
...prev,
travelRecords: prev.travelRecords.filter(
({ inTime }) =>
currentTime.diff(inTime, "day") <= (autoRemoveRecordDay || 30)
),
}));
}, [currentTime, setValue, autoRemoveRecordDay]);
const createTravelRecord = useCallback(
(record: TravelRecord) => {
setValue((prev) => ({
...prev,
travelRecords: [record, ...prev.travelRecords],
}));
},
[setValue]
);
const getTravelRecord = useCallback(
(id: string) => find(({ id: itemId }) => itemId === id, travelRecords),
[travelRecords]
);
const updateTravelRecord = useCallback(
(id: string, data: Partial<TravelRecord>) => {
setValue((prev) => {
const index = findIndex(
({ id: itemId }) => itemId === id,
prev.travelRecords
);
if (index < 0) return prev;
return {
...prev,
travelRecords: adjust(
index,
(currentRecord) => ({ ...currentRecord, ...data }),
prev.travelRecords
),
};
});
},
[setValue]
);
const removeTravelRecord = useCallback(
(id: string) => {
setValue((prev) => ({
...prev,
travelRecords: reject(
({ id: itemId }) => itemId === id,
prev.travelRecords
),
}));
},
[setValue]
);
const enterLocation = useCallback(
(location: Location & { inputType: travelRecordInputType }) => {
const id = uuid();
const now = dayjs();
const record = {
...location,
id,
inTime: now.toISOString(),
outTime: now.add(4, "hour").toISOString(),
};
createTravelRecord(record);
browserHistory.push({ pathname: `/confirm/${id}`, state: record });
},
[createTravelRecord, browserHistory]
);
return {
travelRecords,
currentTravelRecord,
pastTravelRecord,
createTravelRecord,
getTravelRecord,
updateTravelRecord,
removeTravelRecord,
setAutoRemoveRecordDay,
autoRemoveRecordDay,
enterLocation,
};
})