From c2b9c3c0e25d615ddb266a01dae77e21ca219ddf Mon Sep 17 00:00:00 2001 From: Tiago Gimenes Date: Sat, 14 Aug 2021 11:57:44 -0300 Subject: [PATCH 01/14] add session to sdk --- packages/store-sdk/src/index.ts | 12 ++ packages/store-sdk/src/session/Provider.tsx | 169 +++++++++++++++++++ packages/store-sdk/src/session/useSession.ts | 4 + yarn.lock | 34 ++++ 4 files changed, 219 insertions(+) create mode 100644 packages/store-sdk/src/session/Provider.tsx create mode 100644 packages/store-sdk/src/session/useSession.ts diff --git a/packages/store-sdk/src/index.ts b/packages/store-sdk/src/index.ts index 6a7a2ab8d9..7dc1abc812 100644 --- a/packages/store-sdk/src/index.ts +++ b/packages/store-sdk/src/index.ts @@ -43,3 +43,15 @@ export type { InitialState as UIInitialState, } from './ui/Provider' export { useGlobalUIState } from './ui/useGlobalUIState' + +// Session +export { + Provider as SessionProvider, + Context as SessionContext, +} from './session/Provider' +export type { + Actions as SessionActions, + Effects as SessionEffects, + InitialState as SessionInitialState, +} from './ui/Provider' +export { useSession } from './session/useSession' diff --git a/packages/store-sdk/src/session/Provider.tsx b/packages/store-sdk/src/session/Provider.tsx new file mode 100644 index 0000000000..23e7de9d1e --- /dev/null +++ b/packages/store-sdk/src/session/Provider.tsx @@ -0,0 +1,169 @@ +import React, { createContext, useMemo, useReducer } from 'react' +import type { FC, Dispatch } from 'react' + +interface Currency { + code: string // USD + symbol: string // $ +} + +interface User { + id: string // user id +} + +interface BaseState { + locale: string // en-US + currency: Currency + country: string // BRA + channel: string | null + region: string | null + priceTable: string | null + postalCode: string | null + user: User | null +} + +type State = Record & BaseState + +interface BaseContextValue extends BaseState { + setLocale: (locale: string) => void + setCurrency: (currency: Currency) => void + setCountry: (country: string) => void + setChannel: (channel: string | null) => void + setRegion: (region: string | null) => void + setPriceTable: (table: string | null) => void + setPostalCode: (postalCode: string | null) => void + setUser: (user: User) => void +} + +export type ContextValue = Record & BaseContextValue + +type Action = + | { + type: 'SET_LOCALE' + data: string + } + | { + type: 'SET_CURRENCY' + data: Currency + } + | { + type: 'SET_COUNTRY' + data: string + } + | { + type: 'SET_CHANNEL' + data: string | null + } + | { + type: 'SET_REGION' + data: string | null + } + | { + type: 'SET_PRICE_TABLE' + data: string | null + } + | { + type: 'SET_POSTAL_CODE' + data: string | null + } + | { + type: 'SET_USER' + data: User | null + } + | { + type: string + data?: any + } + +export const Context = createContext(undefined) +Context.displayName = 'StoreSessionContext' + +export type Actions = Record State> + +export type Effects = ( + dispatch: Dispatch +) => Omit + +const baseInitialState: State = { + currency: { + code: 'USD', + symbol: '$', + }, + country: 'USA', + locale: 'en', + postalCode: null, + priceTable: null, + channel: null, + region: null, + user: null, +} + +const reducer = (actions: Actions) => { + const allActions: Actions = { + SET_LOCALE: (state, locale) => ({ ...state, locale }), + SET_CURRENCY: (state, currency) => ({ ...state, currency }), + SET_COUNTRY: (state, country) => ({ ...state, country }), + SET_CHANNEL: (state, channel) => ({ ...state, channel }), + SET_REGION: (state, region) => ({ ...state, region }), + SET_PRICE_TABLE: (state, table) => ({ ...state, table }), + SET_POSTAL_CODE: (state, postalCode) => ({ ...state, postalCode }), + SET_USER: (state, user) => ({ ...state, user }), + ...actions, + } + + return (state: State, { type, data }: Action) => { + const maybeAction = allActions[type] + + if (typeof maybeAction === 'function') { + return maybeAction(state, data) + } + + throw new Error('Unknown Session state') + } +} + +export type InitialState = Record + +interface Props { + actions?: Actions + effects?: Effects + initialState?: InitialState +} + +const defaultEffects: Effects = () => ({}) + +export const Provider: FC = ({ + children, + actions = {}, + effects = defaultEffects, + initialState = {}, +}) => { + const [state, dispatch] = useReducer(reducer(actions), { + ...baseInitialState, + ...initialState, + }) + + const value = useMemo( + () => ({ + ...state, + ...effects(dispatch), + setLocale: (locale: string) => + dispatch({ type: 'SET_LOCALE', data: locale }), + setCurrency: (currency: Currency) => + dispatch({ type: 'SET_CURRENCY', data: currency }), + setCountry: (country: string) => + dispatch({ type: 'SET_COUNTRY', data: country }), + setChannel: (channel: string | null) => + dispatch({ type: 'SET_CHANNEL', data: channel }), + setRegion: (region: string | null) => + dispatch({ type: 'SET_REGION', data: region }), + setPriceTable: (table: string | null) => + dispatch({ type: 'SET_PRICE_TABLE', data: table }), + setPostalCode: (postalCode: string | null) => + dispatch({ type: 'SET_POSTAL_CODE', data: postalCode }), + setUser: (user: User) => dispatch({ type: 'SET_USER', data: user }), + }), + [effects, state] + ) + + return {children} +} diff --git a/packages/store-sdk/src/session/useSession.ts b/packages/store-sdk/src/session/useSession.ts new file mode 100644 index 0000000000..c1efbbfd34 --- /dev/null +++ b/packages/store-sdk/src/session/useSession.ts @@ -0,0 +1,4 @@ +import { Context } from './Provider' +import { useContext } from '../utils/useContext' + +export const useSession = () => useContext(Context) diff --git a/yarn.lock b/yarn.lock index c2a881cd0b..fd038b4bae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6684,6 +6684,14 @@ "@typescript-eslint/types" "4.22.0" "@typescript-eslint/visitor-keys" "4.22.0" +"@typescript-eslint/scope-manager@4.28.5": + version "4.28.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.28.5.tgz#3a1b70c50c1535ac33322786ea99ebe403d3b923" + integrity sha512-PHLq6n9nTMrLYcVcIZ7v0VY1X7dK309NM8ya9oL/yG8syFINIMHxyr2GzGoBYUdv3NUfCOqtuqps0ZmcgnZTfQ== + dependencies: + "@typescript-eslint/types" "4.28.5" + "@typescript-eslint/visitor-keys" "4.28.5" + "@typescript-eslint/scope-manager@4.29.0": version "4.29.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.29.0.tgz#cf5474f87321bedf416ef65839b693bddd838599" @@ -6702,6 +6710,11 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.22.0.tgz#0ca6fde5b68daf6dba133f30959cc0688c8dd0b6" integrity sha512-sW/BiXmmyMqDPO2kpOhSy2Py5w6KvRRsKZnV0c4+0nr4GIcedJwXAq+RHNK4lLVEZAJYFltnnk1tJSlbeS9lYA== +"@typescript-eslint/types@4.28.5": + version "4.28.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.28.5.tgz#d33edf8e429f0c0930a7c3d44e9b010354c422e9" + integrity sha512-MruOu4ZaDOLOhw4f/6iudyks/obuvvZUAHBDSW80Trnc5+ovmViLT2ZMDXhUV66ozcl6z0LJfKs1Usldgi/WCA== + "@typescript-eslint/types@4.29.0": version "4.29.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.29.0.tgz#c8f1a1e4441ea4aca9b3109241adbc145f7f8a4e" @@ -6733,6 +6746,19 @@ semver "^7.3.2" tsutils "^3.17.1" +"@typescript-eslint/typescript-estree@4.28.5": + version "4.28.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.5.tgz#4906d343de693cf3d8dcc301383ed638e0441cd1" + integrity sha512-FzJUKsBX8poCCdve7iV7ShirP8V+ys2t1fvamVeD1rWpiAnIm550a+BX/fmTHrjEpQJ7ZAn+Z7ZZwJjytk9rZw== + dependencies: + "@typescript-eslint/types" "4.28.5" + "@typescript-eslint/visitor-keys" "4.28.5" + debug "^4.3.1" + globby "^11.0.3" + is-glob "^4.0.1" + semver "^7.3.5" + tsutils "^3.21.0" + "@typescript-eslint/typescript-estree@4.29.0": version "4.29.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.29.0.tgz#af7ab547757b86c91bfdbc54ff86845410856256" @@ -6762,6 +6788,14 @@ "@typescript-eslint/types" "4.22.0" eslint-visitor-keys "^2.0.0" +"@typescript-eslint/visitor-keys@4.28.5": + version "4.28.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.5.tgz#ffee2c602762ed6893405ee7c1144d9cc0a29675" + integrity sha512-dva/7Rr+EkxNWdJWau26xU/0slnFlkh88v3TsyTgRS/IIYFi5iIfpCFM4ikw0vQTFUR9FYSSyqgK4w64gsgxhg== + dependencies: + "@typescript-eslint/types" "4.28.5" + eslint-visitor-keys "^2.0.0" + "@typescript-eslint/visitor-keys@4.29.0": version "4.29.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.0.tgz#1ff60f240def4d85ea68d4fd2e4e9759b7850c04" From 7ac2cb4d6b03ae992ee2d91fcba1271aa5510ea1 Mon Sep 17 00:00:00 2001 From: Tiago Gimenes Date: Mon, 16 Aug 2021 08:26:33 -0300 Subject: [PATCH 02/14] improve session sdk --- packages/store-sdk/src/index.ts | 4 +- packages/store-sdk/src/session/Provider.tsx | 141 +++--------------- packages/store-sdk/src/session/useSession.ts | 3 +- .../store-sdk/src/utils/useLocalStorage.ts | 56 +++++++ 4 files changed, 85 insertions(+), 119 deletions(-) create mode 100644 packages/store-sdk/src/utils/useLocalStorage.ts diff --git a/packages/store-sdk/src/index.ts b/packages/store-sdk/src/index.ts index 7dc1abc812..8aed2eaa31 100644 --- a/packages/store-sdk/src/index.ts +++ b/packages/store-sdk/src/index.ts @@ -53,5 +53,7 @@ export type { Actions as SessionActions, Effects as SessionEffects, InitialState as SessionInitialState, -} from './ui/Provider' + Currency, + User, +} from './session/Provider' export { useSession } from './session/useSession' diff --git a/packages/store-sdk/src/session/Provider.tsx b/packages/store-sdk/src/session/Provider.tsx index 23e7de9d1e..fae2d32d1d 100644 --- a/packages/store-sdk/src/session/Provider.tsx +++ b/packages/store-sdk/src/session/Provider.tsx @@ -1,16 +1,18 @@ -import React, { createContext, useMemo, useReducer } from 'react' -import type { FC, Dispatch } from 'react' +import React, { createContext, useMemo } from 'react' +import type { FC } from 'react' -interface Currency { +import { useLocalStorage } from '../utils/useLocalStorage' + +export interface Currency { code: string // USD symbol: string // $ } -interface User { +export interface User { id: string // user id } -interface BaseState { +interface Session { locale: string // en-US currency: Currency country: string // BRA @@ -21,69 +23,14 @@ interface BaseState { user: User | null } -type State = Record & BaseState - -interface BaseContextValue extends BaseState { - setLocale: (locale: string) => void - setCurrency: (currency: Currency) => void - setCountry: (country: string) => void - setChannel: (channel: string | null) => void - setRegion: (region: string | null) => void - setPriceTable: (table: string | null) => void - setPostalCode: (postalCode: string | null) => void - setUser: (user: User) => void +export interface ContextValue extends Session { + setSession: (session: Session) => void } -export type ContextValue = Record & BaseContextValue - -type Action = - | { - type: 'SET_LOCALE' - data: string - } - | { - type: 'SET_CURRENCY' - data: Currency - } - | { - type: 'SET_COUNTRY' - data: string - } - | { - type: 'SET_CHANNEL' - data: string | null - } - | { - type: 'SET_REGION' - data: string | null - } - | { - type: 'SET_PRICE_TABLE' - data: string | null - } - | { - type: 'SET_POSTAL_CODE' - data: string | null - } - | { - type: 'SET_USER' - data: User | null - } - | { - type: string - data?: any - } - export const Context = createContext(undefined) Context.displayName = 'StoreSessionContext' -export type Actions = Record State> - -export type Effects = ( - dispatch: Dispatch -) => Omit - -const baseInitialState: State = { +const baseInitialState: Session = { currency: { code: 'USD', symbol: '$', @@ -97,72 +44,32 @@ const baseInitialState: State = { user: null, } -const reducer = (actions: Actions) => { - const allActions: Actions = { - SET_LOCALE: (state, locale) => ({ ...state, locale }), - SET_CURRENCY: (state, currency) => ({ ...state, currency }), - SET_COUNTRY: (state, country) => ({ ...state, country }), - SET_CHANNEL: (state, channel) => ({ ...state, channel }), - SET_REGION: (state, region) => ({ ...state, region }), - SET_PRICE_TABLE: (state, table) => ({ ...state, table }), - SET_POSTAL_CODE: (state, postalCode) => ({ ...state, postalCode }), - SET_USER: (state, user) => ({ ...state, user }), - ...actions, - } - - return (state: State, { type, data }: Action) => { - const maybeAction = allActions[type] - - if (typeof maybeAction === 'function') { - return maybeAction(state, data) - } - - throw new Error('Unknown Session state') - } -} - export type InitialState = Record interface Props { - actions?: Actions - effects?: Effects - initialState?: InitialState + initialState?: Session + namespace?: string } -const defaultEffects: Effects = () => ({}) - export const Provider: FC = ({ children, - actions = {}, - effects = defaultEffects, - initialState = {}, + initialState, + namespace = 'main', }) => { - const [state, dispatch] = useReducer(reducer(actions), { - ...baseInitialState, - ...initialState, - }) + const [session, setSession] = useLocalStorage( + `${namespace}::store::session`, + () => ({ + ...baseInitialState, + ...initialState, + }) + ) const value = useMemo( () => ({ - ...state, - ...effects(dispatch), - setLocale: (locale: string) => - dispatch({ type: 'SET_LOCALE', data: locale }), - setCurrency: (currency: Currency) => - dispatch({ type: 'SET_CURRENCY', data: currency }), - setCountry: (country: string) => - dispatch({ type: 'SET_COUNTRY', data: country }), - setChannel: (channel: string | null) => - dispatch({ type: 'SET_CHANNEL', data: channel }), - setRegion: (region: string | null) => - dispatch({ type: 'SET_REGION', data: region }), - setPriceTable: (table: string | null) => - dispatch({ type: 'SET_PRICE_TABLE', data: table }), - setPostalCode: (postalCode: string | null) => - dispatch({ type: 'SET_POSTAL_CODE', data: postalCode }), - setUser: (user: User) => dispatch({ type: 'SET_USER', data: user }), + ...session, + setSession: (data: Session) => setSession({ ...session, ...data }), }), - [effects, state] + [session, setSession] ) return {children} diff --git a/packages/store-sdk/src/session/useSession.ts b/packages/store-sdk/src/session/useSession.ts index c1efbbfd34..b4d494928c 100644 --- a/packages/store-sdk/src/session/useSession.ts +++ b/packages/store-sdk/src/session/useSession.ts @@ -1,4 +1,5 @@ +import type { ContextValue } from './Provider' import { Context } from './Provider' import { useContext } from '../utils/useContext' -export const useSession = () => useContext(Context) +export const useSession = () => useContext(Context) as T diff --git a/packages/store-sdk/src/utils/useLocalStorage.ts b/packages/store-sdk/src/utils/useLocalStorage.ts new file mode 100644 index 0000000000..f22739fe5c --- /dev/null +++ b/packages/store-sdk/src/utils/useLocalStorage.ts @@ -0,0 +1,56 @@ +/** + * Safe localstorage interface. These try..catch are usefull because + * some browsers may block accesss to these APIs due to security policies + * + * Also, the local storage value is lazy-loaded to avoid hydration mimatch + * between server/browser. When state is 'hydrated', the in the heap is the + * same as the value in local storage + */ +import { useState, useEffect, useCallback } from 'react' + +const getItem = (key: string) => { + try { + return JSON.parse(window.localStorage.getItem(key) ?? 'null') as T | null + } catch (err) { + return null + } +} + +const setItem = (key: string, value: T | null) => { + try { + window.localStorage.setItem(key, JSON.stringify(value)) + } catch (err) { + // noop + } +} + +const isFunction = (x: T | (() => T)): x is () => T => + typeof x === 'function' + +export const useLocalStorage = ( + key: string, + initialValue: T | (() => T) +) => { + const [data, setData] = useState(() => ({ + payload: isFunction(initialValue) ? initialValue() : initialValue, + state: 'initial', + })) + + useEffect(() => { + if (data.state === 'inital') { + const item = getItem(key) ?? data.payload + + setData({ payload: item, state: 'hydrated' }) + setItem(key, item) + } else { + setItem(key, data.payload) + } + }, [data.payload, data.state, key]) + + const setPayload = useCallback( + (value: T) => setData((state) => ({ ...state, payload: value })), + [] + ) + + return [data.payload, setPayload] as [typeof data.payload, typeof setPayload] +} From 9719ca42e17cba049df3f0389867d6c47cd232eb Mon Sep 17 00:00:00 2001 From: Tiago Gimenes Date: Mon, 16 Aug 2021 11:11:13 -0300 Subject: [PATCH 03/14] export local storage --- packages/store-sdk/src/index.ts | 5 +++-- packages/store-sdk/src/session/Provider.tsx | 2 +- packages/store-sdk/src/session/useSession.ts | 3 +-- packages/store-sdk/src/{utils => storage}/useLocalStorage.ts | 0 4 files changed, 5 insertions(+), 5 deletions(-) rename packages/store-sdk/src/{utils => storage}/useLocalStorage.ts (100%) diff --git a/packages/store-sdk/src/index.ts b/packages/store-sdk/src/index.ts index 8aed2eaa31..77a62a6a19 100644 --- a/packages/store-sdk/src/index.ts +++ b/packages/store-sdk/src/index.ts @@ -50,10 +50,11 @@ export { Context as SessionContext, } from './session/Provider' export type { - Actions as SessionActions, - Effects as SessionEffects, InitialState as SessionInitialState, Currency, User, } from './session/Provider' export { useSession } from './session/useSession' + +// Storage +export { useLocalStorage } from './storage/useLocalStorage' diff --git a/packages/store-sdk/src/session/Provider.tsx b/packages/store-sdk/src/session/Provider.tsx index fae2d32d1d..81ba71bd0e 100644 --- a/packages/store-sdk/src/session/Provider.tsx +++ b/packages/store-sdk/src/session/Provider.tsx @@ -1,7 +1,7 @@ import React, { createContext, useMemo } from 'react' import type { FC } from 'react' -import { useLocalStorage } from '../utils/useLocalStorage' +import { useLocalStorage } from '../storage/useLocalStorage' export interface Currency { code: string // USD diff --git a/packages/store-sdk/src/session/useSession.ts b/packages/store-sdk/src/session/useSession.ts index b4d494928c..c1efbbfd34 100644 --- a/packages/store-sdk/src/session/useSession.ts +++ b/packages/store-sdk/src/session/useSession.ts @@ -1,5 +1,4 @@ -import type { ContextValue } from './Provider' import { Context } from './Provider' import { useContext } from '../utils/useContext' -export const useSession = () => useContext(Context) as T +export const useSession = () => useContext(Context) diff --git a/packages/store-sdk/src/utils/useLocalStorage.ts b/packages/store-sdk/src/storage/useLocalStorage.ts similarity index 100% rename from packages/store-sdk/src/utils/useLocalStorage.ts rename to packages/store-sdk/src/storage/useLocalStorage.ts From 5e942b3019217a3285c468ecb71a8253a75d9b9f Mon Sep 17 00:00:00 2001 From: Tiago Gimenes Date: Mon, 16 Aug 2021 12:50:40 -0300 Subject: [PATCH 04/14] add tests --- packages/store-sdk/src/session/Provider.tsx | 2 +- .../store-sdk/src/storage/useLocalStorage.ts | 2 +- .../store-sdk/test/session/Provider.test.tsx | 53 +++++++++++++++++++ .../test/storage/useLocalStorage.test.ts | 21 ++++++++ 4 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 packages/store-sdk/test/session/Provider.test.tsx create mode 100644 packages/store-sdk/test/storage/useLocalStorage.test.ts diff --git a/packages/store-sdk/src/session/Provider.tsx b/packages/store-sdk/src/session/Provider.tsx index 81ba71bd0e..ae2da5f9d7 100644 --- a/packages/store-sdk/src/session/Provider.tsx +++ b/packages/store-sdk/src/session/Provider.tsx @@ -47,7 +47,7 @@ const baseInitialState: Session = { export type InitialState = Record interface Props { - initialState?: Session + initialState?: Partial namespace?: string } diff --git a/packages/store-sdk/src/storage/useLocalStorage.ts b/packages/store-sdk/src/storage/useLocalStorage.ts index f22739fe5c..c192e8d8e1 100644 --- a/packages/store-sdk/src/storage/useLocalStorage.ts +++ b/packages/store-sdk/src/storage/useLocalStorage.ts @@ -37,7 +37,7 @@ export const useLocalStorage = ( })) useEffect(() => { - if (data.state === 'inital') { + if (data.state === 'initial') { const item = getItem(key) ?? data.payload setData({ payload: item, state: 'hydrated' }) diff --git a/packages/store-sdk/test/session/Provider.test.tsx b/packages/store-sdk/test/session/Provider.test.tsx new file mode 100644 index 0000000000..46a5959e40 --- /dev/null +++ b/packages/store-sdk/test/session/Provider.test.tsx @@ -0,0 +1,53 @@ +import { renderHook } from '@testing-library/react-hooks' + +import { SessionProvider, useSession } from '../../src' + +test('Session Provider: Set initial session values', async () => { + const { result } = renderHook(useSession, { + wrapper: SessionProvider, + initialProps: { initialState: { channel: 'test-channel' } }, + }) + + expect(result.current.channel).toBe('test-channel') +}) + +test('Session Provider: Hydrate values from localstorage', async () => { + // Renders once with a custom initial state + const initialState = { channel: 'test-channel' } + const { result: r1 } = renderHook(useSession, { + wrapper: SessionProvider, + initialProps: { initialState }, + }) + + expect(r1.current.channel).toBe(initialState.channel) + + // Renders again. Now we should have stored the past session + // on localstorage and we should be able to hydrate from it + const { result: r2 } = renderHook(useSession, { + wrapper: SessionProvider, + }) + + expect(r2.current.channel).toBe(initialState.channel) +}) + +test('Session Provider: Different namespaces', async () => { + const { result: r1 } = renderHook(useSession, { + wrapper: SessionProvider, + initialProps: { namespace: 'n1 ' }, + }) + + const { result: r2 } = renderHook(useSession, { + wrapper: SessionProvider, + initialProps: { namespace: 'n2 ', initialState: { channel: '1' } }, + }) + + expect(r1.current.channel).toBeNull() + expect(r2.current.channel).toBe('1') + + const { result: r3 } = renderHook(useSession, { + wrapper: SessionProvider, + initialProps: { namespace: 'n2 ' }, + }) + + expect(r3.current.channel).toBe('1') +}) diff --git a/packages/store-sdk/test/storage/useLocalStorage.test.ts b/packages/store-sdk/test/storage/useLocalStorage.test.ts new file mode 100644 index 0000000000..411e8a9769 --- /dev/null +++ b/packages/store-sdk/test/storage/useLocalStorage.test.ts @@ -0,0 +1,21 @@ +import { renderHook } from '@testing-library/react-hooks' + +import { useLocalStorage } from '../../src' + +test('useLocalStorage: Hydrate with initial value', async () => { + const hook = renderHook(() => useLocalStorage('k', { a: 1 })) + + expect(hook.result.current[0].a).toBe(1) +}) + +test('useLocalStorage: Read value from localStorage after hydration', async () => { + const key = 'k' + const storedValue = { a: 1 } + const initialValue = { a: 2 } + + localStorage.setItem(key, JSON.stringify(storedValue)) + + const hook = renderHook(() => useLocalStorage(key, initialValue)) + + expect(hook.result.current[0]).toEqual(storedValue) +}) From c39ffee14d1cab3b351a3f387138d90867e9fac2 Mon Sep 17 00:00:00 2001 From: Tiago Gimenes Date: Mon, 16 Aug 2021 12:59:23 -0300 Subject: [PATCH 05/14] scope typiings --- packages/store-sdk/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/store-sdk/src/index.ts b/packages/store-sdk/src/index.ts index 77a62a6a19..9498f756fe 100644 --- a/packages/store-sdk/src/index.ts +++ b/packages/store-sdk/src/index.ts @@ -51,8 +51,8 @@ export { } from './session/Provider' export type { InitialState as SessionInitialState, - Currency, - User, + Currency as SessionCurrency, + User as SessionUser, } from './session/Provider' export { useSession } from './session/useSession' From 73ef7b25382735ad217f632e51e0efd837d23b16 Mon Sep 17 00:00:00 2001 From: Tiago Gimenes Date: Mon, 16 Aug 2021 13:55:34 -0300 Subject: [PATCH 06/14] Update packages/store-sdk/src/storage/useLocalStorage.ts Co-authored-by: Igor Brasileiro --- packages/store-sdk/src/storage/useLocalStorage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/store-sdk/src/storage/useLocalStorage.ts b/packages/store-sdk/src/storage/useLocalStorage.ts index c192e8d8e1..f9db3629f0 100644 --- a/packages/store-sdk/src/storage/useLocalStorage.ts +++ b/packages/store-sdk/src/storage/useLocalStorage.ts @@ -2,7 +2,7 @@ * Safe localstorage interface. These try..catch are usefull because * some browsers may block accesss to these APIs due to security policies * - * Also, the local storage value is lazy-loaded to avoid hydration mimatch + * Also, the local storage value is lazy-loaded to avoid hydration mismatch * between server/browser. When state is 'hydrated', the in the heap is the * same as the value in local storage */ From e7a05726c398641e7d152af299bdc990cf032625 Mon Sep 17 00:00:00 2001 From: Tiago Gimenes Date: Mon, 16 Aug 2021 17:48:44 -0300 Subject: [PATCH 07/14] Update packages/store-sdk/src/session/Provider.tsx Co-authored-by: Igor Brasileiro --- packages/store-sdk/src/session/Provider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/store-sdk/src/session/Provider.tsx b/packages/store-sdk/src/session/Provider.tsx index ae2da5f9d7..ef106e24e7 100644 --- a/packages/store-sdk/src/session/Provider.tsx +++ b/packages/store-sdk/src/session/Provider.tsx @@ -4,7 +4,7 @@ import type { FC } from 'react' import { useLocalStorage } from '../storage/useLocalStorage' export interface Currency { - code: string // USD + code: string // Ex: USD symbol: string // $ } From beb5b363071d5e589ed5a9960bdeb8c3e227a233 Mon Sep 17 00:00:00 2001 From: Tiago Gimenes Date: Mon, 16 Aug 2021 17:48:50 -0300 Subject: [PATCH 08/14] Update packages/store-sdk/src/session/Provider.tsx Co-authored-by: Igor Brasileiro --- packages/store-sdk/src/session/Provider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/store-sdk/src/session/Provider.tsx b/packages/store-sdk/src/session/Provider.tsx index ef106e24e7..f8d044fd57 100644 --- a/packages/store-sdk/src/session/Provider.tsx +++ b/packages/store-sdk/src/session/Provider.tsx @@ -5,7 +5,7 @@ import { useLocalStorage } from '../storage/useLocalStorage' export interface Currency { code: string // Ex: USD - symbol: string // $ + symbol: string // Ex: $ } export interface User { From 7baf2e7d3018d0c7a50b6f500dbac44209abe6d7 Mon Sep 17 00:00:00 2001 From: Tiago Gimenes Date: Tue, 17 Aug 2021 14:39:58 -0300 Subject: [PATCH 09/14] fix typings --- .../store-sdk/src/storage/useLocalStorage.ts | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/store-sdk/src/storage/useLocalStorage.ts b/packages/store-sdk/src/storage/useLocalStorage.ts index f9db3629f0..c2cb1cfdab 100644 --- a/packages/store-sdk/src/storage/useLocalStorage.ts +++ b/packages/store-sdk/src/storage/useLocalStorage.ts @@ -2,11 +2,11 @@ * Safe localstorage interface. These try..catch are usefull because * some browsers may block accesss to these APIs due to security policies * - * Also, the local storage value is lazy-loaded to avoid hydration mismatch - * between server/browser. When state is 'hydrated', the in the heap is the - * same as the value in local storage + * Also, the local storage value is lazy-loaded to avoid hydration mimatch + * between server/browser. When state is 'hydrated', the value in the heap + * is the same as the value in local storage */ -import { useState, useEffect, useCallback } from 'react' +import { useState, useEffect, useMemo } from 'react' const getItem = (key: string) => { try { @@ -47,10 +47,12 @@ export const useLocalStorage = ( } }, [data.payload, data.state, key]) - const setPayload = useCallback( - (value: T) => setData((state) => ({ ...state, payload: value })), - [] + return useMemo( + () => + [ + data.payload, + (value: T) => setData((state) => ({ ...state, payload: value })), + ] as const, + [data.payload] ) - - return [data.payload, setPayload] as [typeof data.payload, typeof setPayload] } From 0269f6fd116288051b793c27b7d693f97d2b76af Mon Sep 17 00:00:00 2001 From: Tiago Gimenes Date: Tue, 17 Aug 2021 14:55:46 -0300 Subject: [PATCH 10/14] remove region/price table --- packages/store-sdk/src/session/Provider.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/store-sdk/src/session/Provider.tsx b/packages/store-sdk/src/session/Provider.tsx index f8d044fd57..6982e3e26b 100644 --- a/packages/store-sdk/src/session/Provider.tsx +++ b/packages/store-sdk/src/session/Provider.tsx @@ -17,8 +17,6 @@ interface Session { currency: Currency country: string // BRA channel: string | null - region: string | null - priceTable: string | null postalCode: string | null user: User | null } @@ -38,9 +36,7 @@ const baseInitialState: Session = { country: 'USA', locale: 'en', postalCode: null, - priceTable: null, channel: null, - region: null, user: null, } From 14f72177b8e2b4e2ea7538e265d4e89f1bc4e1cc Mon Sep 17 00:00:00 2001 From: Tiago Gimenes Date: Tue, 17 Aug 2021 18:00:07 -0300 Subject: [PATCH 11/14] add indexed db --- packages/store-sdk/package.json | 9 + packages/store-sdk/src/index.ts | 2 +- packages/store-sdk/src/session/Provider.tsx | 4 +- .../store-sdk/src/storage/useLocalStorage.ts | 58 ------ packages/store-sdk/src/storage/useStorage.ts | 71 +++++++ .../store-sdk/test/session/Provider.test.tsx | 40 +--- packages/store-sdk/test/setup.js | 3 + .../test/storage/useLocalStorage.test.ts | 21 -- .../store-sdk/test/storage/useStorage.test.ts | 24 +++ yarn.lock | 186 +++++++++++------- 10 files changed, 229 insertions(+), 189 deletions(-) delete mode 100644 packages/store-sdk/src/storage/useLocalStorage.ts create mode 100644 packages/store-sdk/src/storage/useStorage.ts create mode 100644 packages/store-sdk/test/setup.js delete mode 100644 packages/store-sdk/test/storage/useLocalStorage.test.ts create mode 100644 packages/store-sdk/test/storage/useStorage.test.ts diff --git a/packages/store-sdk/package.json b/packages/store-sdk/package.json index 21cbfab450..aad9f7ea6a 100644 --- a/packages/store-sdk/package.json +++ b/packages/store-sdk/package.json @@ -27,6 +27,11 @@ "size": "size-limit", "analyze": "size-limit --why" }, + "jest": { + "setupFiles": [ + "./test/setup.js" + ] + }, "size-limit": [ { "path": "dist/store-sdk.cjs.production.min.js", @@ -42,10 +47,14 @@ }, "devDependencies": { "@size-limit/preset-small-lib": "^4.10.2", + "fake-indexeddb": "^3.1.3", "react": "^17.0.2", "size-limit": "^4.10.2", "tsdx": "^0.14.1", "tslib": "^2.2.0", "typescript": "^4.2.4" + }, + "dependencies": { + "idb-keyval": "^5.1.3" } } diff --git a/packages/store-sdk/src/index.ts b/packages/store-sdk/src/index.ts index 9498f756fe..9b134558f8 100644 --- a/packages/store-sdk/src/index.ts +++ b/packages/store-sdk/src/index.ts @@ -57,4 +57,4 @@ export type { export { useSession } from './session/useSession' // Storage -export { useLocalStorage } from './storage/useLocalStorage' +export { useStorage } from './storage/useStorage' diff --git a/packages/store-sdk/src/session/Provider.tsx b/packages/store-sdk/src/session/Provider.tsx index 6982e3e26b..a1dcac5958 100644 --- a/packages/store-sdk/src/session/Provider.tsx +++ b/packages/store-sdk/src/session/Provider.tsx @@ -1,7 +1,7 @@ import React, { createContext, useMemo } from 'react' import type { FC } from 'react' -import { useLocalStorage } from '../storage/useLocalStorage' +import { useStorage } from '../storage/useStorage' export interface Currency { code: string // Ex: USD @@ -52,7 +52,7 @@ export const Provider: FC = ({ initialState, namespace = 'main', }) => { - const [session, setSession] = useLocalStorage( + const [session, setSession] = useStorage( `${namespace}::store::session`, () => ({ ...baseInitialState, diff --git a/packages/store-sdk/src/storage/useLocalStorage.ts b/packages/store-sdk/src/storage/useLocalStorage.ts deleted file mode 100644 index c2cb1cfdab..0000000000 --- a/packages/store-sdk/src/storage/useLocalStorage.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Safe localstorage interface. These try..catch are usefull because - * some browsers may block accesss to these APIs due to security policies - * - * Also, the local storage value is lazy-loaded to avoid hydration mimatch - * between server/browser. When state is 'hydrated', the value in the heap - * is the same as the value in local storage - */ -import { useState, useEffect, useMemo } from 'react' - -const getItem = (key: string) => { - try { - return JSON.parse(window.localStorage.getItem(key) ?? 'null') as T | null - } catch (err) { - return null - } -} - -const setItem = (key: string, value: T | null) => { - try { - window.localStorage.setItem(key, JSON.stringify(value)) - } catch (err) { - // noop - } -} - -const isFunction = (x: T | (() => T)): x is () => T => - typeof x === 'function' - -export const useLocalStorage = ( - key: string, - initialValue: T | (() => T) -) => { - const [data, setData] = useState(() => ({ - payload: isFunction(initialValue) ? initialValue() : initialValue, - state: 'initial', - })) - - useEffect(() => { - if (data.state === 'initial') { - const item = getItem(key) ?? data.payload - - setData({ payload: item, state: 'hydrated' }) - setItem(key, item) - } else { - setItem(key, data.payload) - } - }, [data.payload, data.state, key]) - - return useMemo( - () => - [ - data.payload, - (value: T) => setData((state) => ({ ...state, payload: value })), - ] as const, - [data.payload] - ) -} diff --git a/packages/store-sdk/src/storage/useStorage.ts b/packages/store-sdk/src/storage/useStorage.ts new file mode 100644 index 0000000000..d9852c3924 --- /dev/null +++ b/packages/store-sdk/src/storage/useStorage.ts @@ -0,0 +1,71 @@ +/** + * Safe IDB storage interface. These try..catch are usefull because + * some browsers may block accesss to these APIs due to security policies + * + * Also, the stored value is lazy-loaded to avoid hydration mimatch + * between server/browser. When state is 'hydrated', the value in the heap + * is the same as the value in IDB + */ +import { useState, useEffect, useMemo } from 'react' +import { get, set } from 'idb-keyval' + +const getItem = async (key: string) => { + try { + const value = await get(key) + + return value ?? null + } catch (err) { + return null + } +} + +const setItem = async (key: string, value: T | null) => { + try { + await set(key, value) + } catch (err) { + // noop + } +} + +const isFunction = (x: T | (() => T)): x is () => T => + typeof x === 'function' + +export const useStorage = (key: string, initialValue: T | (() => T)) => { + const [data, setData] = useState(() => ({ + payload: isFunction(initialValue) ? initialValue() : initialValue, + state: 'initial', + })) + + useEffect(() => { + let cancel = false + + const effect = async () => { + if (data.state === 'initial') { + const item = (await getItem(key)) ?? data.payload + + if (!cancel) { + setData({ payload: item, state: 'hydrated' }) + } + } else { + setItem(key, data.payload) + } + } + + effect() + + return () => { + cancel = true + } + }, [data.payload, data.state, key]) + + const memoized = useMemo( + () => + [ + data.payload, + (value: T) => setData((state) => ({ ...state, payload: value })), + ] as const, + [data.payload] + ) + + return memoized +} diff --git a/packages/store-sdk/test/session/Provider.test.tsx b/packages/store-sdk/test/session/Provider.test.tsx index 46a5959e40..2e3d78abfc 100644 --- a/packages/store-sdk/test/session/Provider.test.tsx +++ b/packages/store-sdk/test/session/Provider.test.tsx @@ -1,4 +1,5 @@ import { renderHook } from '@testing-library/react-hooks' +import { set } from 'idb-keyval' import { SessionProvider, useSession } from '../../src' @@ -11,43 +12,18 @@ test('Session Provider: Set initial session values', async () => { expect(result.current.channel).toBe('test-channel') }) -test('Session Provider: Hydrate values from localstorage', async () => { +test('Session Provider: Hydrate values from storage', async () => { // Renders once with a custom initial state - const initialState = { channel: 'test-channel' } - const { result: r1 } = renderHook(useSession, { - wrapper: SessionProvider, - initialProps: { initialState }, - }) + const storedState = { channel: 'test-channel' } - expect(r1.current.channel).toBe(initialState.channel) - - // Renders again. Now we should have stored the past session - // on localstorage and we should be able to hydrate from it - const { result: r2 } = renderHook(useSession, { - wrapper: SessionProvider, - }) - - expect(r2.current.channel).toBe(initialState.channel) -}) + await set('main::store::session', storedState) -test('Session Provider: Different namespaces', async () => { - const { result: r1 } = renderHook(useSession, { + // We should have stored the past session on storage and we should be able to hydrate from it + const run = renderHook(useSession, { wrapper: SessionProvider, - initialProps: { namespace: 'n1 ' }, }) - const { result: r2 } = renderHook(useSession, { - wrapper: SessionProvider, - initialProps: { namespace: 'n2 ', initialState: { channel: '1' } }, - }) - - expect(r1.current.channel).toBeNull() - expect(r2.current.channel).toBe('1') - - const { result: r3 } = renderHook(useSession, { - wrapper: SessionProvider, - initialProps: { namespace: 'n2 ' }, - }) + await run.waitForValueToChange(() => run.result.current.channel) - expect(r3.current.channel).toBe('1') + expect(run.result.current.channel).toBe(storedState.channel) }) diff --git a/packages/store-sdk/test/setup.js b/packages/store-sdk/test/setup.js new file mode 100644 index 0000000000..2e62cb40a3 --- /dev/null +++ b/packages/store-sdk/test/setup.js @@ -0,0 +1,3 @@ +/* eslint-disable no-undef */ +// Fake indexedDB +globalThis.indexedDB = require('fake-indexeddb') diff --git a/packages/store-sdk/test/storage/useLocalStorage.test.ts b/packages/store-sdk/test/storage/useLocalStorage.test.ts deleted file mode 100644 index 411e8a9769..0000000000 --- a/packages/store-sdk/test/storage/useLocalStorage.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { renderHook } from '@testing-library/react-hooks' - -import { useLocalStorage } from '../../src' - -test('useLocalStorage: Hydrate with initial value', async () => { - const hook = renderHook(() => useLocalStorage('k', { a: 1 })) - - expect(hook.result.current[0].a).toBe(1) -}) - -test('useLocalStorage: Read value from localStorage after hydration', async () => { - const key = 'k' - const storedValue = { a: 1 } - const initialValue = { a: 2 } - - localStorage.setItem(key, JSON.stringify(storedValue)) - - const hook = renderHook(() => useLocalStorage(key, initialValue)) - - expect(hook.result.current[0]).toEqual(storedValue) -}) diff --git a/packages/store-sdk/test/storage/useStorage.test.ts b/packages/store-sdk/test/storage/useStorage.test.ts new file mode 100644 index 0000000000..157163ff11 --- /dev/null +++ b/packages/store-sdk/test/storage/useStorage.test.ts @@ -0,0 +1,24 @@ +import { renderHook } from '@testing-library/react-hooks' +import { set } from 'idb-keyval' + +import { useStorage } from '../../src' + +test('useStorage: Hydrate with initial value', async () => { + const hook = renderHook(() => useStorage('k', { a: 1 })) + + expect(hook.result.current[0].a).toBe(1) +}) + +test('useStorage: Read value from localStorage after hydration', async () => { + const key = 'k' + const storedValue = { a: 1 } + const initialValue = { a: 2 } + + set(key, storedValue) + + const run = renderHook(() => useStorage(key, initialValue)) + + await run.waitForValueToChange(() => run.result.current[0].a) + + expect(run.result.current[0]).toEqual(storedValue) +}) diff --git a/yarn.lock b/yarn.lock index fd038b4bae..3b654659dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6610,27 +6610,27 @@ integrity sha512-S9q47ByT2pPvD65IvrWp7qppVMpk9WGMbVq9wbWZOHg6tnXSD4vyhao6nOSBwwfDdV2p3Kx9evA9vI+XWTfDvw== "@typescript-eslint/eslint-plugin@^2.10.0", "@typescript-eslint/eslint-plugin@^2.12.0", "@typescript-eslint/eslint-plugin@^4", "@typescript-eslint/eslint-plugin@^4.18.0", "@typescript-eslint/eslint-plugin@^4.28.1": - version "4.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.29.0.tgz#b866c9cd193bfaba5e89bade0015629ebeb27996" - integrity sha512-eiREtqWRZ8aVJcNru7cT/AMVnYd9a2UHsfZT8MR1dW3UUEg6jDv9EQ9Cq4CUPZesyQ58YUpoAADGv71jY8RwgA== + version "4.29.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.29.2.tgz#f54dc0a32b8f61c6024ab8755da05363b733838d" + integrity sha512-x4EMgn4BTfVd9+Z+r+6rmWxoAzBaapt4QFqE+d8L8sUtYZYLDTK6VG/y/SMMWA5t1/BVU5Kf+20rX4PtWzUYZg== dependencies: - "@typescript-eslint/experimental-utils" "4.29.0" - "@typescript-eslint/scope-manager" "4.29.0" + "@typescript-eslint/experimental-utils" "4.29.2" + "@typescript-eslint/scope-manager" "4.29.2" debug "^4.3.1" functional-red-black-tree "^1.0.1" regexpp "^3.1.0" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/experimental-utils@4.29.0": - version "4.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.29.0.tgz#19b1417602d0e1ef325b3312ee95f61220542df5" - integrity sha512-FpNVKykfeaIxlArLUP/yQfv/5/3rhl1ov6RWgud4OgbqWLkEq7lqgQU9iiavZRzpzCRQV4XddyFz3wFXdkiX9w== +"@typescript-eslint/experimental-utils@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.29.2.tgz#5f67fb5c5757ef2cb3be64817468ba35c9d4e3b7" + integrity sha512-P6mn4pqObhftBBPAv4GQtEK7Yos1fz/MlpT7+YjH9fTxZcALbiiPKuSIfYP/j13CeOjfq8/fr9Thr2glM9ub7A== dependencies: "@types/json-schema" "^7.0.7" - "@typescript-eslint/scope-manager" "4.29.0" - "@typescript-eslint/types" "4.29.0" - "@typescript-eslint/typescript-estree" "4.29.0" + "@typescript-eslint/scope-manager" "4.29.2" + "@typescript-eslint/types" "4.29.2" + "@typescript-eslint/typescript-estree" "4.29.2" eslint-scope "^5.1.1" eslint-utils "^3.0.0" @@ -6659,13 +6659,13 @@ eslint-utils "^2.0.0" "@typescript-eslint/parser@^2.10.0", "@typescript-eslint/parser@^2.12.0", "@typescript-eslint/parser@^4", "@typescript-eslint/parser@^4.18.0", "@typescript-eslint/parser@^4.28.1": - version "4.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.29.0.tgz#e5367ca3c63636bb5d8e0748fcbab7a4f4a04289" - integrity sha512-+92YRNHFdXgq+GhWQPT2bmjX09X7EH36JfgN2/4wmhtwV/HPxozpCNst8jrWcngLtEVd/4zAwA6BKojAlf+YqA== + version "4.29.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.29.2.tgz#1c7744f4c27aeb74610c955d3dce9250e95c370a" + integrity sha512-WQ6BPf+lNuwteUuyk1jD/aHKqMQ9jrdCn7Gxt9vvBnzbpj7aWEf+aZsJ1zvTjx5zFxGCt000lsbD9tQPEL8u6g== dependencies: - "@typescript-eslint/scope-manager" "4.29.0" - "@typescript-eslint/types" "4.29.0" - "@typescript-eslint/typescript-estree" "4.29.0" + "@typescript-eslint/scope-manager" "4.29.2" + "@typescript-eslint/types" "4.29.2" + "@typescript-eslint/typescript-estree" "4.29.2" debug "^4.3.1" "@typescript-eslint/scope-manager@4.19.0": @@ -6684,21 +6684,13 @@ "@typescript-eslint/types" "4.22.0" "@typescript-eslint/visitor-keys" "4.22.0" -"@typescript-eslint/scope-manager@4.28.5": - version "4.28.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.28.5.tgz#3a1b70c50c1535ac33322786ea99ebe403d3b923" - integrity sha512-PHLq6n9nTMrLYcVcIZ7v0VY1X7dK309NM8ya9oL/yG8syFINIMHxyr2GzGoBYUdv3NUfCOqtuqps0ZmcgnZTfQ== +"@typescript-eslint/scope-manager@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.29.2.tgz#442b0f029d981fa402942715b1718ac7fcd5aa1b" + integrity sha512-mfHmvlQxmfkU8D55CkZO2sQOueTxLqGvzV+mG6S/6fIunDiD2ouwsAoiYCZYDDK73QCibYjIZmGhpvKwAB5BOA== dependencies: - "@typescript-eslint/types" "4.28.5" - "@typescript-eslint/visitor-keys" "4.28.5" - -"@typescript-eslint/scope-manager@4.29.0": - version "4.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.29.0.tgz#cf5474f87321bedf416ef65839b693bddd838599" - integrity sha512-HPq7XAaDMM3DpmuijxLV9Io8/6pQnliiXMQUcAdjpJJSR+fdmbD/zHCd7hMkjJn04UQtCQBtshgxClzg6NIS2w== - dependencies: - "@typescript-eslint/types" "4.29.0" - "@typescript-eslint/visitor-keys" "4.29.0" + "@typescript-eslint/types" "4.29.2" + "@typescript-eslint/visitor-keys" "4.29.2" "@typescript-eslint/types@4.19.0": version "4.19.0" @@ -6710,15 +6702,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.22.0.tgz#0ca6fde5b68daf6dba133f30959cc0688c8dd0b6" integrity sha512-sW/BiXmmyMqDPO2kpOhSy2Py5w6KvRRsKZnV0c4+0nr4GIcedJwXAq+RHNK4lLVEZAJYFltnnk1tJSlbeS9lYA== -"@typescript-eslint/types@4.28.5": - version "4.28.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.28.5.tgz#d33edf8e429f0c0930a7c3d44e9b010354c422e9" - integrity sha512-MruOu4ZaDOLOhw4f/6iudyks/obuvvZUAHBDSW80Trnc5+ovmViLT2ZMDXhUV66ozcl6z0LJfKs1Usldgi/WCA== - -"@typescript-eslint/types@4.29.0": - version "4.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.29.0.tgz#c8f1a1e4441ea4aca9b3109241adbc145f7f8a4e" - integrity sha512-2YJM6XfWfi8pgU2HRhTp7WgRw78TCRO3dOmSpAvIQ8MOv4B46JD2chnhpNT7Jq8j0APlIbzO1Bach734xxUl4A== +"@typescript-eslint/types@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.29.2.tgz#fc0489c6b89773f99109fb0aa0aaddff21f52fcd" + integrity sha512-K6ApnEXId+WTGxqnda8z4LhNMa/pZmbTFkDxEBLQAbhLZL50DjeY0VIDCml/0Y3FlcbqXZrABqrcKxq+n0LwzQ== "@typescript-eslint/typescript-estree@4.19.0": version "4.19.0" @@ -6746,26 +6733,13 @@ semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/typescript-estree@4.28.5": - version "4.28.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.5.tgz#4906d343de693cf3d8dcc301383ed638e0441cd1" - integrity sha512-FzJUKsBX8poCCdve7iV7ShirP8V+ys2t1fvamVeD1rWpiAnIm550a+BX/fmTHrjEpQJ7ZAn+Z7ZZwJjytk9rZw== - dependencies: - "@typescript-eslint/types" "4.28.5" - "@typescript-eslint/visitor-keys" "4.28.5" - debug "^4.3.1" - globby "^11.0.3" - is-glob "^4.0.1" - semver "^7.3.5" - tsutils "^3.21.0" - -"@typescript-eslint/typescript-estree@4.29.0": - version "4.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.29.0.tgz#af7ab547757b86c91bfdbc54ff86845410856256" - integrity sha512-8ZpNHDIOyqzzgZrQW9+xQ4k5hM62Xy2R4RPO3DQxMc5Rq5QkCdSpk/drka+DL9w6sXNzV5nrdlBmf8+x495QXQ== +"@typescript-eslint/typescript-estree@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.29.2.tgz#a0ea8b98b274adbb2577100ba545ddf8bf7dc219" + integrity sha512-TJ0/hEnYxapYn9SGn3dCnETO0r+MjaxtlWZ2xU+EvytF0g4CqTpZL48SqSNn2hXsPolnewF30pdzR9a5Lj3DNg== dependencies: - "@typescript-eslint/types" "4.29.0" - "@typescript-eslint/visitor-keys" "4.29.0" + "@typescript-eslint/types" "4.29.2" + "@typescript-eslint/visitor-keys" "4.29.2" debug "^4.3.1" globby "^11.0.3" is-glob "^4.0.1" @@ -6788,20 +6762,12 @@ "@typescript-eslint/types" "4.22.0" eslint-visitor-keys "^2.0.0" -"@typescript-eslint/visitor-keys@4.28.5": - version "4.28.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.5.tgz#ffee2c602762ed6893405ee7c1144d9cc0a29675" - integrity sha512-dva/7Rr+EkxNWdJWau26xU/0slnFlkh88v3TsyTgRS/IIYFi5iIfpCFM4ikw0vQTFUR9FYSSyqgK4w64gsgxhg== +"@typescript-eslint/visitor-keys@4.29.2": + version "4.29.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.2.tgz#d2da7341f3519486f50655159f4e5ecdcb2cd1df" + integrity sha512-bDgJLQ86oWHJoZ1ai4TZdgXzJxsea3Ee9u9wsTAvjChdj2WLcVsgWYAPeY7RQMn16tKrlQaBnpKv7KBfs4EQag== dependencies: - "@typescript-eslint/types" "4.28.5" - eslint-visitor-keys "^2.0.0" - -"@typescript-eslint/visitor-keys@4.29.0": - version "4.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.0.tgz#1ff60f240def4d85ea68d4fd2e4e9759b7850c04" - integrity sha512-LoaofO1C/jAJYs0uEpYMXfHboGXzOJeV118X4OsZu9f7rG7Pr9B3+4HTU8+err81rADa4xfQmAxnRnPAI2jp+Q== - dependencies: - "@typescript-eslint/types" "4.29.0" + "@typescript-eslint/types" "4.29.2" eslint-visitor-keys "^2.0.0" "@vtex-components/accordion@^0.2.4": @@ -8454,6 +8420,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base64-arraybuffer-es6@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/base64-arraybuffer-es6/-/base64-arraybuffer-es6-0.7.0.tgz#dbe1e6c87b1bf1ca2875904461a7de40f21abc86" + integrity sha512-ESyU/U1CFZDJUdr+neHRhNozeCv72Y7Vm0m1DCbjX3KBjT6eYocvAJlSk6+8+HkVwXlT1FNxhGW6q3UKAlCvvw== + base64-arraybuffer@0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812" @@ -10079,7 +10050,7 @@ core-js-pure@^3.8.2: resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.11.1.tgz#fd52fa8c8b7b797b3606524b3d97278a8d8e7f09" integrity sha512-2JukQi8HgAOCD5CSimxWWXVrUBoA9Br796uIA5Z06bIjt7PBBI19ircFaAxplgE1mJf3x2BY6MkT/HWA/UryPg== -core-js@^2.4.0: +core-js@^2.4.0, core-js@^2.5.3: version "2.6.12" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== @@ -12647,6 +12618,14 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= +fake-indexeddb@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fake-indexeddb/-/fake-indexeddb-3.1.3.tgz#76d59146a6b994b9bb50ac9949cbd96ad6cca760" + integrity sha512-kpWYPIUGmxW8Q7xG7ampGL63fU/kYNukrIyy9KFj3+KVlFbE/SmvWebzWXBiCMeR0cPK6ufDoGC7MFkPhPLH9w== + dependencies: + realistic-structured-clone "^2.0.1" + setimmediate "^1.0.5" + fast-copy@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-2.1.1.tgz#f5cbcf2df64215e59b8e43f0b2caabc19848083a" @@ -14939,6 +14918,13 @@ icss-utils@^5.0.0, icss-utils@^5.1.0: resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== +idb-keyval@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-5.1.3.tgz#6ef5dff371897c23f144322dc6374eadd6a345d9" + integrity sha512-N9HbCK/FaXSRVI+k6Xq4QgWxbcZRUv+SfG1y7HJ28JdV8yEJu6k+C/YLea7npGckX2DQJeEVuMc4bKOBeU/2LQ== + dependencies: + safari-14-idb-fix "^1.0.4" + identity-obj-proxy@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz#94d2bda96084453ef36fbc5aaec37e0f79f1fc14" @@ -17644,7 +17630,7 @@ lodash.without@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac" integrity sha1-PNRXSgC2e643OpS3SHcmQFB7eqw= -"lodash@>=3.5 <5", lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@~4.17.20: +"lodash@>=3.5 <5", lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.7.0, lodash@~4.17.20: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -22369,6 +22355,16 @@ reakit@^1.3.0: reakit-utils "^0.15.1" reakit-warning "^0.6.1" +realistic-structured-clone@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/realistic-structured-clone/-/realistic-structured-clone-2.0.3.tgz#8a252a87db8278d92267ad7a168c4f43fa485795" + integrity sha512-XYTwWZi5+lU4Wf+rnsQ7pukN9hF2cbJJf/yruBr1w23WhGflM6WoTBkdMVAun+oHFW2mV7UquyYo5oOI7YLJrQ== + dependencies: + core-js "^2.5.3" + domexception "^1.0.1" + typeson "^6.1.0" + typeson-registry "^1.0.0-alpha.20" + realpath-native@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c" @@ -23085,6 +23081,11 @@ sade@^1.4.2: dependencies: mri "^1.1.0" +safari-14-idb-fix@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/safari-14-idb-fix/-/safari-14-idb-fix-1.0.4.tgz#5c68ba63e2a8ae0d89a0aa1e13fe89e3aef7da19" + integrity sha512-4+Y2baQdgJpzu84d0QjySl70Kyygzf0pepVg8NVg4NnQEPpfC91fAn0baNvtStlCjUUxxiu0BOMiafa98fRRuA== + safe-buffer@*, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -25044,6 +25045,13 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" +tr46@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" + integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== + dependencies: + punycode "^2.1.1" + trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" @@ -25383,6 +25391,20 @@ typescript@^4.3.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.3.tgz#5401db69bd3203daf1851a1a74d199cb3112c11a" integrity sha512-rUvLW0WtF7PF2b9yenwWUi9Da9euvDRhmH7BLyBG4DCFfOJ850LGNknmRpp8Z8kXNUPObdZQEfKOiHtXuQHHKA== +typeson-registry@^1.0.0-alpha.20: + version "1.0.0-alpha.39" + resolved "https://registry.yarnpkg.com/typeson-registry/-/typeson-registry-1.0.0-alpha.39.tgz#9e0f5aabd5eebfcffd65a796487541196f4b1211" + integrity sha512-NeGDEquhw+yfwNhguLPcZ9Oj0fzbADiX4R0WxvoY8nGhy98IbzQy1sezjoEFWOywOboj/DWehI+/aUlRVrJnnw== + dependencies: + base64-arraybuffer-es6 "^0.7.0" + typeson "^6.0.0" + whatwg-url "^8.4.0" + +typeson@^6.0.0, typeson@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/typeson/-/typeson-6.1.0.tgz#5b2a53705a5f58ff4d6f82f965917cabd0d7448b" + integrity sha512-6FTtyGr8ldU0pfbvW/eOZrEtEkczHRUtduBnA90Jh9kMPCiFNnXIon3vF41N0S4tV1HHQt4Hk1j4srpESziCaA== + ua-parser-js@^0.7.18: version "0.7.25" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.25.tgz#67689fa263a87a52dabbc251ede89891f59156ce" @@ -26033,6 +26055,11 @@ webidl-conversions@^4.0.2: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== +webidl-conversions@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" + integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== + webpack-assets-manifest@^5.0.6: version "5.0.6" resolved "https://registry.yarnpkg.com/webpack-assets-manifest/-/webpack-assets-manifest-5.0.6.tgz#1fe7baf9b57f2d28ff09fcaef3d678cc15912b88" @@ -26406,6 +26433,15 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" +whatwg-url@^8.4.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" + integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== + dependencies: + lodash "^4.7.0" + tr46 "^2.1.0" + webidl-conversions "^6.1.0" + which-boxed-primitive@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" From d13414227b4d293db6204cc34e1a941bfffd421f Mon Sep 17 00:00:00 2001 From: Tiago Gimenes Date: Tue, 17 Aug 2021 18:37:00 -0300 Subject: [PATCH 12/14] fix race coditions --- packages/store-sdk/src/storage/useStorage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/store-sdk/src/storage/useStorage.ts b/packages/store-sdk/src/storage/useStorage.ts index d9852c3924..119dd42806 100644 --- a/packages/store-sdk/src/storage/useStorage.ts +++ b/packages/store-sdk/src/storage/useStorage.ts @@ -62,7 +62,7 @@ export const useStorage = (key: string, initialValue: T | (() => T)) => { () => [ data.payload, - (value: T) => setData((state) => ({ ...state, payload: value })), + (value: T) => setData({ state: 'hydrated', payload: value }), ] as const, [data.payload] ) From 63cb09d70620e59519a403b58d34de0cab471987 Mon Sep 17 00:00:00 2001 From: Tiago Gimenes Date: Wed, 18 Aug 2021 09:25:25 -0300 Subject: [PATCH 13/14] Update packages/store-sdk/src/storage/useStorage.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ícaro Azevedo --- packages/store-sdk/src/storage/useStorage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/store-sdk/src/storage/useStorage.ts b/packages/store-sdk/src/storage/useStorage.ts index 119dd42806..6ec6a7ac80 100644 --- a/packages/store-sdk/src/storage/useStorage.ts +++ b/packages/store-sdk/src/storage/useStorage.ts @@ -2,7 +2,7 @@ * Safe IDB storage interface. These try..catch are usefull because * some browsers may block accesss to these APIs due to security policies * - * Also, the stored value is lazy-loaded to avoid hydration mimatch + * Also, the stored value is lazy-loaded to avoid hydration mismatch * between server/browser. When state is 'hydrated', the value in the heap * is the same as the value in IDB */ From 79bf0b040c1145490b8b083f85db38a70a200d9e Mon Sep 17 00:00:00 2001 From: Tiago Gimenes Date: Wed, 18 Aug 2021 12:17:15 -0300 Subject: [PATCH 14/14] remove another racing condition --- packages/store-sdk/src/storage/useStorage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/store-sdk/src/storage/useStorage.ts b/packages/store-sdk/src/storage/useStorage.ts index 6ec6a7ac80..73309a7db4 100644 --- a/packages/store-sdk/src/storage/useStorage.ts +++ b/packages/store-sdk/src/storage/useStorage.ts @@ -46,7 +46,7 @@ export const useStorage = (key: string, initialValue: T | (() => T)) => { if (!cancel) { setData({ payload: item, state: 'hydrated' }) } - } else { + } else if (!cancel) { setItem(key, data.payload) } }