Skip to content

Commit ae6e7b7

Browse files
authored
react 18 changes - getting registerStateContainer from context (#3)
* react 18 changes - getting registerStateContainer from context * Remove console.log statements in useSelector * Update package.json version and key-value-state-container dependency * Update key-value-state-container version in package.json * Update package.json version to 1.0.0
1 parent 2567f7b commit ae6e7b7

File tree

6 files changed

+114
-52
lines changed

6 files changed

+114
-52
lines changed

package-lock.json

Lines changed: 8 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ContainerRoot/ContainerRoot.tsx

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,12 @@ import React, { PropsWithChildren, useEffect, useRef, useState } from "react";
2626
import {
2727
Action,
2828
getUniqueId,
29-
registerStateContainer,
29+
registerStateContainer as libRegisterStateContainer,
3030
unregisterStateContainer,
3131
} from "key-value-state-container";
3232

3333
import { ContainerRootContext } from "./context";
3434
import { ContainerRootProps } from "./props";
35-
import { reRegister } from "./re-register";
3635

3736
export const ContainerRoot = <TState extends Object, TAction extends Action>(
3837
props: PropsWithChildren<ContainerRootProps<TState, TAction>>
@@ -48,10 +47,9 @@ export const ContainerRoot = <TState extends Object, TAction extends Action>(
4847
persistence,
4948
reducer,
5049
} = props;
51-
const runningFirstTimeRef = useRef<boolean>(true);
52-
if (runningFirstTimeRef.current) {
53-
runningFirstTimeRef.current = false;
54-
registerStateContainer({
50+
51+
const registerStateContainer = () => {
52+
libRegisterStateContainer({
5553
autoActions,
5654
autoState,
5755
config,
@@ -60,36 +58,51 @@ export const ContainerRoot = <TState extends Object, TAction extends Action>(
6058
persistence,
6159
reducer,
6260
});
63-
}
61+
};
6462

65-
useEffect(() => {
63+
const runningFirstTimeRef = useRef<boolean>(true);
64+
if (runningFirstTimeRef.current) {
65+
runningFirstTimeRef.current = false;
6666
/**
67-
* Checking for unregistered container
68-
* Container might be unregistered due to `useEffect` return function
69-
* called twice in React 18 Strict Mode
67+
* We need to register the container here, with the first render.
68+
*
69+
* Assuming the `console.log()` invocations are printed out for
70+
* mount/unmount component lifecycle events (by `useEffect()`),
71+
* the printout for React 18 Strict Mode would look as follows:
72+
*
73+
* useSelector mounted ⭐️
74+
* ContainerRoot mounted ⭐️⭐️
75+
* useSelector unmounted
76+
* ContainerRoot unmounted
77+
* useSelector mounted ⭐️⭐️⭐️
78+
* ContainerRoot mounted
79+
*
80+
* Please note is is way too late to register the container in the ⭐️⭐️
81+
* (as there was a `useSelector` call in ⭐️, that wanted to register
82+
* callback listeners with an unregistered container!)
83+
*
84+
* The same story goes with `useSelector`, marked by ⭐️⭐️⭐️,
85+
* that needs a container to be registered.
86+
*
87+
* Keep in mind the biggest possible memory issues for a container
88+
* are the callback listeners mentioned above,
89+
* as these can easily allocate GBs of memory
90+
* (btw. this problem is handled by the `useSelector` code)
7091
*/
71-
if (reRegister({ containerId })) {
72-
registerStateContainer({
73-
autoActions,
74-
autoState,
75-
config,
76-
containerId,
77-
initialState,
78-
persistence,
79-
reducer,
80-
});
81-
}
92+
registerStateContainer();
93+
}
94+
95+
useEffect(() => {
8296
return () => {
83-
unregisterStateContainer({
84-
containerId,
85-
});
97+
unregisterStateContainer({ containerId });
8698
};
8799
}, []);
88100

89101
return (
90102
<ContainerRootContext.Provider
91103
value={{
92104
containerId,
105+
registerStateContainer,
93106
}}
94107
>
95108
{children}

src/ContainerRoot/context.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@ import { getUniqueId } from "key-value-state-container";
2727

2828
export interface ContainerRootContextProps {
2929
containerId: string;
30+
registerStateContainer: () => void;
3031
}
3132

3233
export const ContainerRootContext = React.createContext<
3334
ContainerRootContextProps
3435
>({
3536
containerId: getUniqueId(),
37+
registerStateContainer: () => { },
3638
});

src/ContainerRoot/re-register.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.

src/use-get-context.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
The MIT License (MIT)
3+
4+
Copyright Tomasz Szatkowski and WealthArc https://www.wealtharc.com (c) 2023
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
SOFTWARE.
23+
*/
24+
25+
import { useContext } from "react";
26+
27+
import {
28+
ContainerRootContext,
29+
ContainerRootContextProps,
30+
} from "./ContainerRoot/context";
31+
32+
type Result = {
33+
containerIdFromContext: string;
34+
registerStateContainer: () => void;
35+
}
36+
37+
export const useGetContext = (): Result => {
38+
const { containerId, registerStateContainer } =
39+
useContext<ContainerRootContextProps>(ContainerRootContext);
40+
return { containerIdFromContext: containerId, registerStateContainer };
41+
};

src/use-selector.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import { useEffect, useRef, useState } from "react";
22
import {
33
Action,
44
ClientNotificationCallbackArgs,
5+
containerRegistered,
56
getContainer,
67
getUniqueId,
78
registerStateChangedCallback,
89
TKnownStatePath,
910
unregisterStateChangedCallback,
1011
} from "key-value-state-container";
11-
import { useGetContainerId } from "./use-get-container-id";
1212
import { RendersWithContainerId } from "./types/contracts";
13+
import { useGetContext } from "./use-get-context";
1314

1415
interface Args<TState extends Object, TAction extends Action = Action>
1516
extends Partial<RendersWithContainerId> {
@@ -67,8 +68,8 @@ export const useSelector = <
6768
`${listenerTag ? `${listenerTag}:` : ""}${getUniqueId()}`
6869
);
6970
const unmountedRef = useRef<boolean>(false);
70-
const containerFromHook = useGetContainerId();
71-
const containerId = containerIdFromProps || containerFromHook;
71+
const { containerIdFromContext, registerStateContainer } = useGetContext();
72+
const containerId = containerIdFromProps || containerIdFromContext;
7273
const initialState =
7374
getContainer<TState>({
7475
containerId,
@@ -78,15 +79,24 @@ export const useSelector = <
7879
const lateInvoke =
7980
typeof lateInvokeFromProps === "boolean" ? lateInvokeFromProps : true;
8081
const [currentState, setCurrentState] = useState(initialState);
82+
8183
useEffect(() => {
82-
return () => {
83-
unmountedRef.current = true;
84-
};
84+
/**
85+
* Component gets mounted
86+
*/
87+
unmountedRef.current = false;
8588
}, []);
89+
8690
useEffect(() => {
8791
if (switchOff) {
8892
return;
8993
}
94+
/**
95+
* Awkward, but necessary logic, explained in ContextRoot.tsx component.
96+
*/
97+
if (!containerRegistered({ containerId })) {
98+
registerStateContainer();
99+
}
90100
const statePaths =
91101
typeof statePath === "string"
92102
? [statePath]
@@ -138,5 +148,14 @@ export const useSelector = <
138148
};
139149
}, [setCurrentState]);
140150

151+
useEffect(() => {
152+
return () => {
153+
/**
154+
* component gets unmounted
155+
*/
156+
unmountedRef.current = true;
157+
};
158+
}, []);
159+
141160
return currentState;
142161
};

0 commit comments

Comments
 (0)