Reputation: 46
I am a novice developer, please tell me why an error occurs when calling initPayment: "A non-serializable value was detected in an action, in the path: payload. Value: [Error: Invalid hook call. Hooks can only be called inside of the body of a function component]"
import { useSQLiteContext } from "expo-sqlite";
const paymentSlice = createSliceWithThunks({
name: "payments",
initialState,
reducers: (create) => ({
initPayment: create.asyncThunk(
async function (_, { rejectWithValue }) {
try {
const db = useSQLiteContext();
const payments = await db.getAllAsync<Payments>("SELECT * FROM payments");
return payments;
} catch (err) {
return rejectWithValue(err);
}
},
{
fulfilled: (state, action: PayloadAction<Payments[]>) => {
state.list = action.payload;
},
rejected: (state) => {
console.error("Error");
},
}
),
}),
});
export default function Main() {
const { theme } = useContext(ThemeContext);
return (
<SQLiteProvider
databaseName="payments.db"
assetSource={{ assetId: require("../assets/payments.db") }}
>
<Provider store={store}>
<NavigationContainer theme={theme}>
<PaperProvider>
<StatusBar style="auto" />
<Stack.Navigator
screenOptions={{
contentStyle: {
backgroundColor: theme.colors.background,
padding: 20,
},
headerShadowVisible: false,
headerStyle: {
backgroundColor: theme.colors.background,
},
}}
>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Payment" component={Payment} />
</Stack.Navigator>
</PaperProvider>
</NavigationContainer>
</Provider>
</SQLiteProvider>
);
}
Upvotes: 1
Views: 111
Reputation: 202836
You cannot call React hooks from anything other than React functions and custom React hooks, so calling the useSQLiteContext
in the initPayment
action creator is invalid.
You can however call useSQLiteContext
in the React component that is dispatching initPayment
and pass the DB instance to the Thunk action.
Example:
const paymentSlice = createSliceWithThunks({
name: "payments",
initialState,
reducers: (create) => ({
initPayment: create.asyncThunk(
async function ({ db }, { rejectWithValue }) {
try {
return db.getAllAsync<Payments>("SELECT * FROM payments");
} catch (err) {
return rejectWithValue(err);
}
},
{
fulfilled: (state, action: PayloadAction<Payments[]>) => {
state.list = action.payload;
},
rejected: (state) => {
console.error("Error");
},
}
),
}),
});
import { useSQLiteContext } from "expo-sqlite";
...
const SomeComponent = () => {
const dispatch = useDispatch();
const db = useSQLiteContext();
...
const someHandler = async () => {
...
const payments = await dispatch(initPayment({ db })).unwrap();
...
};
...
};
db
to specific Thunk actions that use itCreate an intermediate wrapper component that can call useSQLiteContext
hook and pass db
in the Thunk middlware's extraArgument
.
See getDefaultMiddleware API reference for details.
Example:
import { useSQLiteContext } from "expo-sqlite";
import { configureStore } from "@reduxjs/toolkit";
import rootReducer from "../path/to/rootReducer";
const Wrapper = ({ children }) => {
const db = useSQLiteContext();
// Create stable/memoized Redux store instance reference
const store = useMemo(() => {
return configureStore({
reducer: rootReducer,
middleware: getDefaultMiddleware =>
getDefaultMiddleware({
thunk: {
extraArgument: {
db, // <-- pass db to Thunk middleware
}
}
}),
});
}, [db]);
return (
<Provider store={store}>
{children}
</Provider>
);
};
Render Wrapper
in Redux Provider
component's place in Main
.
export default function Main() {
const { theme } = useContext(ThemeContext);
return (
<SQLiteProvider
databaseName="payments.db"
assetSource={{ assetId: require("../assets/payments.db") }}
>
<Wrapper>
<NavigationContainer theme={theme}>
<PaperProvider>
<StatusBar style="auto" />
<Stack.Navigator screenOptions={{ ... }}>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Payment" component={Payment} />
</Stack.Navigator>
</PaperProvider>
</NavigationContainer>
</Wrapper>
</SQLiteProvider>
);
}
Access the extra
ThunkApi property to get the passed db
instance.
const paymentSlice = createSliceWithThunks({
name: "payments",
initialState,
reducers: (create) => ({
initPayment: create.asyncThunk(
async function (_, { extra, rejectWithValue }) {
try {
const { db } = extra; // <-- access db from extraArgument
return db.getAllAsync<Payments>("SELECT * FROM payments");
} catch (err) {
return rejectWithValue(err);
}
},
{
fulfilled: (state, action: PayloadAction<Payments[]>) => {
state.list = action.payload;
},
rejected: (state) => {
console.error("Error");
},
}
),
}),
});
Upvotes: 1