I’m building a modular system consisting of multiple npm packages for React and React Native projects. Here’s the setup:
-
replyke-core
:- Contains shared logic for React and React Native.
- Includes a conditional
getAsyncStorage
function to dynamically import@react-native-async-storage/async-storage
only when used in React Native.
-
replyke-rn
:- A React Native-specific package that consumes
replyke-core
.
- A React Native-specific package that consumes
-
Final consuming project:
- A React Native app consuming
replyke-rn
.
- A React Native app consuming
Problem
In the final consuming project, calling AsyncStorage.getItem
directly works without issue, but when I use it indirectly through replyke-core
, it throws the following error:
(NOBRIDGE) ERROR Cannot read property 'getItem' of undefined
Implementation
In replyke-core
, I have the following refreshToken
function in my authentication context:
const refreshToken = useCallback(async () => {
try {
const path = `/auth/refresh`;
let refreshToken = null;
// Use helper function to dynamically load AsyncStorage
if (isReactNative()) {
const AsyncStorage = await getAsyncStorage();
console.log("Using AsyncStorage:", AsyncStorage);
if (!AsyncStorage) {
console.error("Failed to retrieve AsyncStorage, skipping refresh.");
return;
}
console.log("Checking getItem type:", typeof AsyncStorage.getItem);
if (typeof AsyncStorage.getItem !== "function") {
throw new Error("AsyncStorage.getItem is not a function.");
}
refreshToken = await AsyncStorage.getItem("refreshToken");
console.log("Retrieved refreshToken:", refreshToken);
if (!refreshToken) {
console.log("No refresh token found.");
return;
}
}
const response = await axios.post(
path,
isReactNative()
? { projectId, refreshToken }
: { projectId },
{ withCredentials: !isReactNative() }
);
const { accessToken: newAccessToken, user: newUser } = response.data;
setAccessToken(newAccessToken);
setUser(newUser);
return newAccessToken;
} catch (err) {
handleError(err, "Refresh error: ");
}
}, [projectId]);
useEffect(() => {
const fetchInitial = async () => {
await refreshToken();
setLoadingInitial(false);
};
fetchInitial();
}, [projectId]);
This function uses a helper function to dynamically load AsyncStorage
:
export async function getAsyncStorage() {
try {
const module = await import("@react-native-async-storage/async-storage");
const AsyncStorage = module.default;
if (!AsyncStorage) {
throw new Error("AsyncStorage is not defined.");
}
if (typeof AsyncStorage.getItem !== "function") {
throw new Error("AsyncStorage.getItem is not a function.");
}
return AsyncStorage;
} catch (err) {
console.error("Error loading AsyncStorage:", err);
return null;
}
}
The @react-native-async-storage/async-storage
package is declared as a peerDependency
in both replyke-core
and replyke-rn
:
"peerDependencies": { "@react-native-async-storage/async-storage": "1.x" }
And it is installed in the final consuming project:
npm install @react-native-async-storage/[email protected]
Observations
- Direct Usage in the Final Project Works: When I test
AsyncStorage
directly in the final consuming project, it works as expected:
useEffect(() => {
const test = async () => {
const value = await AsyncStorage.getItem("my-key");
console.log({ value }); // Logs: { value: null }
};
test();
}, []);
- Dynamic Import Logs: When debugging the logs show that
AsyncStorage
is resolved correctly andgetItem
is a function (identical when using dynamic or static imports):
(NOBRIDGE) LOG Using AsyncStorage: { clear: [Function clear], getItem: [Function getItem], ... }
(NOBRIDGE) LOG Checking getItem type: function
But the call to getItem
still fails:
(NOBRIDGE) ERROR Cannot read property 'getItem' of undefined
- Static Import Fails Too: To rule out dynamic imports as the issue, I replaced
getAsyncStorage
with a static import inreplyke-core
:
import AsyncStorage from "@react-native-async-storage/async-storage";
refreshToken = await AsyncStorage.getItem("refreshToken");
The same error occurs.
- When I Remove the
refreshToken
Function Call: Removing theuseEffect
that callsrefreshToken
stops the error.
What I’ve Tried
- Used .then/.catch Instead of await:
I refactored the refreshToken function to use a .then/.catch chain for handling the getAsyncStorage promise, as it could potentially affect scoping or execution context. The AsyncStorage object resolved correctly, and getItem was still confirmed to be a function, but the same error (Cannot read property ‘getItem’ of undefined) occurred when calling getItem. - Declaring
@react-native-async-storage/async-storage
as apeerDependency
in bothreplyke-core
andreplyke-rn
. - Verifying that the final consuming project has the dependency installed (
1.23.1
). - Using both dynamic and static imports in
replyke-core
. - Running
npm dedupe
to ensure no duplicate versions of@react-native-async-storage/async-storage
exist. - Adding extensive logging to confirm that
AsyncStorage
and its methods are resolved correctly.
Environment
- React Native:
0.76.2
- Expo:
52.0.7
- AsyncStorage:
1.23.1
Question
Why does AsyncStorage.getItem
throw Cannot read property 'getItem' of undefined
when used through the replyke-core
package, but works correctly when used directly in the consuming project? How can I resolve this issue?