-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix initialisation makes too many rerenders (#9)
- Loading branch information
1 parent
8f58108
commit 1fa9324
Showing
8 changed files
with
153 additions
and
73 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
export enum EventType { | ||
ASK_FOR_INITIALISATION = "ASK_FOR_INITIALISATION", | ||
DATA_FOR_INITIALISATION = "DATA_FOR_INITIALISATION", | ||
DATA_UPDATE = "DATA_UPDATE", | ||
} | ||
|
||
export const allEventsTypes: EventType[] = [ | ||
EventType.DATA_UPDATE, | ||
EventType.DATA_FOR_INITIALISATION, | ||
EventType.ASK_FOR_INITIALISATION, | ||
]; | ||
|
||
function getSuffix(messageType: EventType): string { | ||
switch (messageType) { | ||
case EventType.ASK_FOR_INITIALISATION: | ||
return "ask-for-initialisation"; | ||
case EventType.DATA_FOR_INITIALISATION: | ||
return "data_initialisation"; | ||
case EventType.DATA_UPDATE: | ||
return "data_update"; | ||
} | ||
} | ||
|
||
export function computeStorageKeyForEvent(key: string, eventType: EventType) { | ||
const suffix: string = getSuffix(eventType); | ||
return `${key}-${suffix}`; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { computeStorageKeyForEvent, EventType } from "./eventsTypes"; | ||
|
||
export type Listener<Value> = (value: Value) => void; | ||
type StorageListener = Listener<StorageEvent>; | ||
|
||
function wrapListenerForKey<Value>(storageKey: string, listener: Listener<Value>): StorageListener { | ||
return ({ key, newValue }: StorageEvent) => { | ||
const eventIsOnThisState: boolean = key === storageKey && newValue !== null; | ||
if (eventIsOnThisState) { | ||
const newValueParsed: Value = JSON.parse(newValue!); | ||
listener(newValueParsed); | ||
} | ||
}; | ||
} | ||
|
||
export function addListenerOnStorage<Value>( | ||
key: string, | ||
messageType: EventType, | ||
listener: Listener<Value> | ||
): StorageListener { | ||
const storageKey: string = computeStorageKeyForEvent(key, messageType); | ||
const wrappedListener = wrapListenerForKey<Value>(storageKey, listener); | ||
window.addEventListener("storage", wrappedListener); | ||
return wrappedListener; | ||
} | ||
|
||
export function removeListenerOnStorage(storageListener: StorageListener): void { | ||
window.removeEventListener("storage", storageListener); | ||
} | ||
|
||
export function notify<Value>(key: string, messageType: EventType, value: Value): void { | ||
const storageKey: string = computeStorageKeyForEvent(key, messageType); | ||
localStorage.setItem(storageKey, JSON.stringify(value)); | ||
localStorage.removeItem(storageKey); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { DependencyList, MutableRefObject, useLayoutEffect, useRef } from "react"; | ||
import { addListenerOnStorage, Listener, notify, removeListenerOnStorage } from "./messages"; | ||
import { SetState } from "./useTabsState"; | ||
import { allEventsTypes, EventType } from "./eventsTypes"; | ||
|
||
interface ActionOnEventParams<State> { | ||
key: string; | ||
state: State; | ||
setState: SetState<State>; | ||
isAlreadyInitialisedRef: MutableRefObject<boolean>; | ||
} | ||
|
||
function notifyOwnDataForInitialisation<State>({ | ||
key, | ||
state, | ||
isAlreadyInitialisedRef, | ||
}: ActionOnEventParams<State>): [Listener<State>, DependencyList] { | ||
const listener = () => { | ||
isAlreadyInitialisedRef.current = true; | ||
notify(key, EventType.DATA_FOR_INITIALISATION, state); | ||
}; | ||
|
||
const dependencies = [state]; | ||
return [listener, dependencies]; | ||
} | ||
|
||
function initialiseOwnStateIfNotAlreadyInitialised<State>({ | ||
setState, | ||
isAlreadyInitialisedRef, | ||
}: ActionOnEventParams<State>): [Listener<State>, DependencyList] { | ||
const listener = (eventData: State) => { | ||
if (!isAlreadyInitialisedRef.current) { | ||
setState(eventData); | ||
isAlreadyInitialisedRef.current = true; | ||
} | ||
}; | ||
|
||
const dependencies = [setState, isAlreadyInitialisedRef.current]; | ||
return [listener, dependencies]; | ||
} | ||
|
||
function getActionOnEvent<State>( | ||
eventTypeRecieved: EventType | ||
): (actionOnEventParams: ActionOnEventParams<State>) => [Listener<State>, DependencyList] { | ||
switch (eventTypeRecieved) { | ||
case EventType.ASK_FOR_INITIALISATION: | ||
return notifyOwnDataForInitialisation; | ||
case EventType.DATA_FOR_INITIALISATION: | ||
return initialiseOwnStateIfNotAlreadyInitialised; | ||
case EventType.DATA_UPDATE: | ||
return ({ setState }) => [setState, [setState]]; | ||
} | ||
} | ||
|
||
export function useAllEventsSubscription<State>(key: string, state: State, setState: SetState<State>) { | ||
const isAlreadyInitialisedRef: MutableRefObject<boolean> = useRef(false); | ||
|
||
const actionOnEventParams: ActionOnEventParams<State> = { key, state, setState, isAlreadyInitialisedRef }; | ||
|
||
allEventsTypes.forEach(messageType => registerActionForMessage(actionOnEventParams, messageType)); | ||
} | ||
|
||
function registerActionForMessage<State>( | ||
actionOnEventParams: ActionOnEventParams<State>, | ||
messageType: EventType | ||
): void { | ||
const [listener, dependencies] = getActionOnEvent<State>(messageType)(actionOnEventParams); | ||
useLayoutEffect(() => { | ||
const registeredListener = addListenerOnStorage(actionOnEventParams.key, messageType, listener); | ||
return () => removeListenerOnStorage(registeredListener); | ||
}, dependencies); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,85 +1,36 @@ | ||
import { Dispatch, SetStateAction, useLayoutEffect, useState } from "react"; | ||
import { notify } from "./messages"; | ||
import { useAllEventsSubscription } from "./useAllEventsSubscription"; | ||
import { EventType } from "./eventsTypes"; | ||
|
||
type Listener<Value> = (value: Value) => void; | ||
type StorageListener = Listener<StorageEvent>; | ||
|
||
export type InitialState<State> = State | (() => State); | ||
export type SetState<State> = Dispatch<SetStateAction<State>>; | ||
export type UseStateReturn<State> = [State, SetState<State>]; | ||
export type InitialState<State> = State | (() => State); | ||
|
||
export function useTabsState<State>(initialState: InitialState<State>, storageKey: string): UseStateReturn<State> { | ||
const [state, setState]: UseStateReturn<State> = useState<State>(initialState); | ||
|
||
useRegisterInitStorageListener<State>(storageKey, state); | ||
|
||
useRegisterStateStorageListener<State>(storageKey, setState); | ||
|
||
useNotifyInitialisationForOtherTabs(storageKey); | ||
export function useTabsState<State>(initialState: InitialState<State>, key: string): UseStateReturn<State> { | ||
const useStateReturn: UseStateReturn<State> = useState<State>(initialState); | ||
const [state, setState]: UseStateReturn<State> = useStateReturn; | ||
|
||
const useStateReturn: UseStateReturn<State> = [state, setState]; | ||
const setStateInStorage = buildSetStateInStorage(useStateReturn, storageKey); | ||
useAllEventsSubscription(key, state, setState); | ||
|
||
return [state, setStateInStorage]; | ||
} | ||
useNotifyInitialisationForOtherTabs(key); | ||
|
||
function useRegisterInitStorageListener<State>(storageKey: string, state: State): void { | ||
useLayoutEffect(() => { | ||
const initStorageKey: string = buildInitStorageKey(storageKey); | ||
const onInitStorageChange = () => { | ||
notifyWithLocalStorage(storageKey, state); | ||
}; | ||
const registeredListener = addListenerOnStorageKey(initStorageKey, onInitStorageChange); | ||
return () => window.removeEventListener("storage", registeredListener); | ||
}, [state]); | ||
} | ||
const setStateAndNotify = buildSetStateAndNotify(useStateReturn, key); | ||
|
||
function useRegisterStateStorageListener<State>(storageKey: string, setState: SetState<State>) { | ||
useLayoutEffect(() => { | ||
const registeredListener = addListenerOnStorageKey(storageKey, setState); | ||
return () => window.removeEventListener("storage", registeredListener); | ||
}, []); | ||
return [state, setStateAndNotify]; | ||
} | ||
|
||
function useNotifyInitialisationForOtherTabs(storageKey: string): void { | ||
function useNotifyInitialisationForOtherTabs(key: string): void { | ||
useLayoutEffect(() => { | ||
const initStorageKey: string = buildInitStorageKey(storageKey); | ||
notifyWithLocalStorage(initStorageKey, "storage initialisation"); | ||
notify(key, EventType.ASK_FOR_INITIALISATION, "storage initialisation"); | ||
}, []); | ||
} | ||
|
||
function buildSetStateInStorage<State>( | ||
[previousState, setState]: UseStateReturn<State>, | ||
storageKey: string | ||
): SetState<State> { | ||
function buildSetStateAndNotify<State>([previousState, setState]: UseStateReturn<State>, key: string): SetState<State> { | ||
return (stateUpdater: SetStateAction<State>) => { | ||
const stateUpdaterIsFunction = stateUpdater instanceof Function; | ||
const newState: State = stateUpdaterIsFunction ? stateUpdater(previousState) : stateUpdater; | ||
notifyWithLocalStorage(storageKey, newState); | ||
notify(key, EventType.DATA_UPDATE, newState); | ||
setState(stateUpdater); | ||
}; | ||
} | ||
|
||
function notifyWithLocalStorage<Value>(storageKey: string, value: Value): void { | ||
localStorage.setItem(storageKey, JSON.stringify(value)); | ||
localStorage.removeItem(storageKey); | ||
} | ||
|
||
function addListenerOnStorageKey<Value>(storageKey: string, listener: Listener<Value>): StorageListener { | ||
const wrappedListener = wrapListenerForKey<Value>(storageKey, listener); | ||
window.addEventListener("storage", wrappedListener); | ||
return wrappedListener; | ||
} | ||
|
||
function wrapListenerForKey<Value>(storageKey: string, listener: Listener<Value>): StorageListener { | ||
return ({ key, newValue }: StorageEvent) => { | ||
const eventIsOnThisState: boolean = key === storageKey && newValue !== null; | ||
if (eventIsOnThisState) { | ||
const newValueParsed: Value = JSON.parse(newValue!); | ||
listener(newValueParsed); | ||
} | ||
}; | ||
} | ||
|
||
function buildInitStorageKey(storageKey: string): string { | ||
return `${storageKey}-init`; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters