light-keeper / react-singleton-hook Goto Github PK
View Code? Open in Web Editor NEWCreate singleton hook from regular react hook
License: MIT License
Create singleton hook from regular react hook
License: MIT License
Hi, i created my own custom hook and use react-singleton-hook to use it.
My App.js code:
const App: FC = () => (
<NotifierWrapper>
<QueryClientProvider client={queryClient}>
<>
<SingletonHooksContainer />
<Routes />
</>
</QueryClientProvider>
</NotifierWrapper>
)
My Routes.js component
const Routes: FC = () => {
const { play, pause, loaded } = useMusic()
const onStateChange = useCallback(() => {
if (loaded && play && pause) {
if (checkSceneHasMusic()) {
play()
} else {
pause()
}
}
}, [loaded, play, pause])
return (
<Router onStateChange={onStateChange}>
<Lightbox>
<Stack key='root'>
<Scene key='Splash' component={Splash} hideNavBar initial />
<Scene key='List' component={List} hideNavBar />
</Stack>
</Lightbox>
</Router>
)
}
export default Routes
My hook (i tried to reduce code to minimal but app crash too)
import { useCallback, useRef, useEffect, useState } from 'react'
import { singletonHook } from 'react-singleton-hook'
import { Player } from '@react-native-community/audio-toolkit'
import { checkSceneHasMusic } from 'utils'
const trackUrl = 'https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_700KB.mp3'
const trackUrl2 = 'https://www.learningcontainer.com/wp-content/uploads/2020/02/Kalimba.mp3'
const mockFunction = () => {
// MOCK
}
const init = { load: mockFunction, play: mockFunction, pause: mockFunction, paused: true, loaded: false }
type ReturnType = {
load: (url: string) => void,
play: () => void,
pause: () => void,
paused: boolean,
loaded: boolean,
}
const useMusic = (): ReturnType => {
const [paused, setPaused] = useState(false)
const [loaded, setLoaded] = useState(false)
useEffect(() => {
if (!loaded) {
setLoaded(true)
}
}, [loaded])
return { load: mockFunction, play: mockFunction, pause: mockFunction, paused, loaded }
}
export default singletonHook(init, useMusic)
If i execute hook on "Routes" component app crash on iOS with this error:
2021-05-10 13:44:22.267632+0200 myapp[20448:251440] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff20421af6 __exceptionPreprocess + 242
1 libobjc.A.dylib 0x00007fff20177e78 objc_exception_throw + 48
2 CoreFoundation 0x00007fff2049e77f _CFThrowFormattedException + 194
3 CoreFoundation 0x00007fff2049c656 -[__NSArrayM insertObject:atIndex:].cold.2 + 0
4 CoreFoundation 0x00007fff2031f678 -[__NSArrayM insertObject:atIndex:] + 1134
5 UIKitCore 0x00007fff23f677ac -[UIViewController _addChildViewController:performHierarchyCheck:notifyWillMove:] + 571
6 myapp 0x000000010d6d220d -[RNSScreenContainerView attachScreen:atIndex:] + 125
7 myapp 0x000000010d6d2bd8 -[RNSScreenContainerView updateContainer] + 2168
8 myapp 0x000000010d6d39f0 __41-[RNSScreenContainerManager markUpdated:]_block_invoke + 352
9 libdispatch.dylib 0x000000011118c7ec _dispatch_call_block_and_release + 12
10 libdispatch.dylib 0x000000011118d9c8 _dispatch_client_callout + 8
11 libdispatch.dylib 0x000000011119be75 _dispatch_main_queue_callback_4CF + 1152
12 CoreFoundation 0x00007fff2038fdbb __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
13 CoreFoundation 0x00007fff2038a63e __CFRunLoopRun + 2685
14 CoreFoundation 0x00007fff203896d6 CFRunLoopRunSpecific + 567
15 GraphicsServices 0x00007fff2c257db3 GSEventRunModal + 139
16 UIKitCore 0x00007fff24696cf7 -[UIApplication _run] + 912
17 UIKitCore 0x00007fff2469bba8 UIApplicationMain + 101
18 myapp 0x000000010d11b630 main + 112
19 libdyld.dylib 0x00007fff2025a3e9 start + 1
20 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
terminating with uncaught exception of type NSException
CoreSimulator 732.18.6 - Device: iPhone 12 (7E7D3327-1ACA-482F-B440-1302588C9173) - Runtime: iOS 14.4 (18D46) - DeviceType: iPhone 12
If i use on Scene components it works fine. And on iOS release mode only fail if i call some hook function (with same error that show above)
On Android not crash app but reload "Routes" if i call hook function, and music player not works if i used on "Routes". In Scenes components all work fines like on iOS
My versions
"react": "17.0.1",
"react-native": "0.64.0",
"react-singleton-hook": "^3.1.1"
I have a utilty function that creates a Context.Provider
, and a useContext
.
For my specific case, I'd like to use use-context-selector
library to improve the performance of my application.
The API for the use-context-selector
is: const myContextData = useContextSelector(Context, (state) => state[0].data);
In my function, I create an abstraction for the useContextSelector, like this: (contextSelectorFn) => useContextSelector(Context, contextSelectorFn)
.
This way, I can use my custom hook directly like: const myUserId = useUserData((state) => state[0].id)
;.
I'm sort of having a hard time understand how could I simply adhere to this singleton pattern for this use case.
Here's the code for my utility function as of now:
import type { Dispatch, ReactElement, ReactNode, SetStateAction } from 'react';
import { useState } from 'react';
import { createContext, useContextSelector } from 'use-context-selector';
type GlobalState<State> = [State, Dispatch<SetStateAction<State>>];
type Provider = (props: { children: ReactNode }) => ReactElement;
type GlobalContext<State> = <Selected>(
selector: (value: GlobalState<State>) => Selected,
) => Selected;
function createGlobalStateContext<State>(
initialState: State,
): [GlobalContext<State>, Provider] {
const Context = createContext<GlobalState<State>>([initialState, () => {}]);
const ProviderComponent: Provider = ({ children }) => {
const [state, setState] = useState<State>(initialState);
const contextState: GlobalState<State> = [state, setState];
return <Context.Provider value={contextState}>{children}</Context.Provider>;
};
return [
(contextSelectorFn) => useContextSelector(Context, contextSelectorFn),
ProviderComponent,
];
}
I am using react-singleton-hook for some fairly complex behaviour and would like to have some jest tests. Unfortunately the global nature of the hook makes having multiple tests for it impossible.
When I have more than one test with the hook in it, I get this warning
"SingletonHooksContainer is mounted second time. You should mount SingletonHooksContainer before any other component and never unmount it.Alternatively, dont use SingletonHooksContainer it at all, we will handle that for you."
The second test also fails. Each test passes independently, so I suspect this global state is causing the problem.
In the tests for the container there is a function called resetLocalStateForTests that resets this global state that gets run after each test. Exposing this function for users of the hook would solve the problem as I would be able to run it after each test.
Can not mount SingletonHooksContainer with react native.Please mount SingletonHooksContainer into your components tree manually.
at VideoPlayer (http://localhost:8081/index.bundle//&platform=android&dev=true&minify=false&app=com.example&modulesOnly=false&runModule=true:123441:22)
at RCTView
at View (http://localhost:8081/index.bundle//&platform=android&dev=true&minify=false&app=com.example&modulesOnly=false&runModule=true:59610:43)
at App
at RCTView
at View (http://localhost:8081/index.bundle//&platform=android&dev=true&minify=false&app=com.example&modulesOnly=false&runModule=true:59610:43)
at RCTView
at View (http://localhost:8081/index.bundle//&platform=android&dev=true&minify=false&app=com.example&modulesOnly=false&runModule=true:59610:43)
at AppContainer (http://localhost:8081/index.bundle//&platform=android&dev=true&minify=false&app=com.example&modulesOnly=false&runModule=true:59454:36)
at example(RootComponent) (http://localhost:8081/index.bundle//&platform=android&dev=true&minify=false&app=com.example&modulesOnly=false&runModule=true:108811:28)
I have this waring but i don't know why
i use React Native 0.72.4
System:
OS: Linux 6.2 Ubuntu 22.04.3 LTS 22.04.3 LTS (Jammy Jellyfish)
CPU: (8) x64 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz
Memory: 5.07 GB / 15.32 GB
Shell:
version: 5.8.1
path: /usr/bin/zsh
Binaries:
Node:
version: 18.17.0
path: ~/.nvm/versions/node/v18.17.0/bin/node
Yarn:
version: 1.22.19
path: ~/.nvm/versions/node/v18.17.0/bin/yarn
npm:
version: 9.6.7
path: ~/.nvm/versions/node/v18.17.0/bin/npm
Watchman: Not Found
SDKs:
Android SDK: Not Found
IDEs:
Android Studio: AI-223.8836.35.2231.10671973
Languages:
Java:
version: 11.0.20.1
path: /usr/bin/javac
Ruby: Not Found
npmPackages:
"@react-native-community/cli": Not Found
react:
installed: 18.2.0
wanted: 18.2.0
react-native:
installed: 0.72.4
wanted: 0.72.4
npmGlobalPackages:
"*react-native*": Not Found
Android:
hermesEnabled: true
newArchEnabled: false
iOS:
hermesEnabled: Not found
newArchEnabled: Not found
When trying to use react-singleton-hook with React 18 this error message comes up in the browser console:
react_devtools_backend.js:4026 Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot
overrideMethod @ react_devtools_backend.js:4026
printWarning @ react-dom.development.js:86
error @ react-dom.development.js:60
render @ react-dom.development.js:29572
mount @ env.js:14
addHook @ SingletonHooksContainer.js:59
(anonymous) @ singletonHook.js:44
commitHookEffectListMount @ react-dom.development.js:23049
commitPassiveMountOnFiber @ react-dom.development.js:24816
commitPassiveMountEffects_complete @ react-dom.development.js:24781
commitPassiveMountEffects_begin @ react-dom.development.js:24768
commitPassiveMountEffects @ react-dom.development.js:24756
flushPassiveEffectsImpl @ react-dom.development.js:26990
flushPassiveEffects @ react-dom.development.js:26935
performSyncWorkOnRoot @ react-dom.development.js:26032
flushSyncCallbacks @ react-dom.development.js:12009
commitRootImpl @ react-dom.development.js:26910
commitRoot @ react-dom.development.js:26638
finishConcurrentRender @ react-dom.development.js:25937
performConcurrentWorkOnRoot @ react-dom.development.js:25765
workLoop @ scheduler.development.js:266
flushWork @ scheduler.development.js:239
performWorkUntilDeadline @ scheduler.development.js:533
This is caused by this code segment in https://github.com/Light-Keeper/react-singleton-hook/blob/main/src/utils/env.js, specifically the render()
call:
// ...
import { unstable_batchedUpdates, render } from 'react-dom';
// ...
export const mount = C => {
if (globalObject.document && globalObject.document.createElement) {
render(<C/>, globalObject.document.createElement('div'));
} else {
// ...
}
};
I'm attempting to use the dark/light mode example in my react app exactly how it is written in the examples: https://github.com/Light-Keeper/react-singleton-hook#darklight-mode-switch
If there is at least one reference in a react component to useDarkMode, then calling setDarkMode will cause the app to freeze. I'm not sure if its due to issues with this module or something else. I've tried versions 3.2.1 and then the latest 3.2.3
Newbie question: Sometimes I need a hook that receive arguments in its declaration, something like:
const whatever = useMyCustomHook(withMyArgument)
How can I do it with react-singleton-hook?
I'm using version 3.4.0
We use singleton hook to store data fetched through rest API and then updated via websocket. From performance perspective it will be better to unmount hood and unsubscribe from topic if there is no consumer, and create new hook instance if some component that uses hook will mount again.
Using a singleton hook seems like a good solution for our particular need, but is causing some issues with testing. It appears that the singleton hook persists between tests, which is not ideal. I'd really like a way to destroy the hook upon test completion. There doesn't appear to be a way to do this?
import { useEffect, useState } from 'react';
import { singletonHook } from 'react-singleton-hook';
const api = { async getMe(userId) { return { id: userId, name: 'test' }; } };
const init = { loading: true };
const useUserProfileImpl = (userId) => {
const [profile, setProfile] = useState(init);
useEffect(() => {
api.getMe(userId)
.then(profile => setProfile({ profile }))
.catch(error => setProfile({ error }));
}, []);
return profile;
};
export const useUserProfile = singletonHook(init, useUserProfileImpl(userId));
Currently the consumed hook from singletonHook
is just a pointer to the hook itself. I tried wrapping my hook around a function so that the parameter is curried, but it's no longer treated as a singletonHook
.
Thanks!
Hello! We have a strange bug, where functions created inside a singleton-hook appear to be outdated. We have proved that the singleton-hook recognizes a Redux state update AND re-renders. But then, when one of the singleton-hook's functions is called by a consumer, it does not appear to be calling the most up-to-date version of the function.
I'm wondering if you can help? Do singleton-hooks memoize their outputs behind the scenes; or what else could be causing the singleton-hook to return a stale function?
We have confirmed that making this hook to NOT be a singleton-hook solves the problem. (see console.logs below)
Attached below is some example code (contains some pseudocode for simplicity)
const initialReduxState = {
currentRound: undefined,
};
// ---
const useDriversRoundsPermissionsImpl = () => {
const currentRound = useSelector(selectCurrentRound);
const hasPermission = listenToIndexedDbChanges('hasPermission', currentRound?.id);
useEffect(() => {
if (currentRound) console.log(currentRound);
// OUTPUT: { roundId: 309823, roundName: "Round 1" }
// Prints 1st, suggesting a re-render isrequestPermissionForRound() should contain the updated state.
}, [currentRound]);
const requestPermissionForRound = async () => {
const test = selectCurrentRound(store.getState());
console.log(currentRound === undefined, test === undefined);
// OUTPUT: true, false
// When fetched with store.getState() the currentRound is NOT undefined.
// OUTPUT WHEN NOT A SINGLETON-HOOK: false, false
};
return { hasPermission, requestPermissionForRound };
};
export const useDriversRoundsPermissions = singletonHook(
{},
useDriversRoundsPermissionsImpl
);
// ---
function RoundRequestManager() {
const dispatch = useDispatch();
const currentRound = useSelector(selectCurrentRound);
const { hasPermission, requestPermissionForRound } = useDriversRoundsPermissions();
useEffect(() => {
if (currentRound && !hasPermission) {
requestPermissionForRound();
// We can say with certainty that currentRound is NOT undefined when this is called
}
}, [hasPermission, currentRound]);
return (
<>
<button
onClick={() =>
dispatch(setCurrentRound({ roundId: 309823, roundName: "Round 1" }))
}
>
Join Round 1
</button>
<button
onClick={() =>
dispatch(setCurrentRound({ roundId: 981726, roundName: "Round 2" }))
}
>
Join Round 2
</button>
</>
);
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.