redux toolkit and reselect - react-native

I want to use reselect but I do not understand it correctly yet.
If I want to filter anything then I can do this:
const selectNumCompletedTodos = createSelector(
(state) => state.todos,
(todos) => todos.filter((todo) => todo.completed).length
)
But if I fetch, how does it look then? (I use useSelector and not mapToProps)
My Code:
Login:
imports
...
const Login = ({ navigation, route }) => {
const dispatch = useDispatch();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [passwordShown, setPasswordShown] = useState(true);
const handleGoBack = () => {
navigation.goBack();
};
const handleLogin = async () => {
const payload = {
email,
password
};
dispatch(request(payload));
};
return (
<View>
<TouchableOpacity onPress={handleLogin}>
<Text>click to fetch!</Text>
</TouchableOpacity>
</View>
)
authSaga.js
import { all, call, put, take, takeEvery, takeLatest } from 'redux-saga/effects';
import { request, authSuccess, authFailure } from '../slice/authSlice';
import authAPI from '../../api/auth';
import * as SecureStore from 'expo-secure-store';
function* auth({ payload }) {
const data = yield call(authAPI, payload);
yield put(authSuccess(data.user));
}
function* watcher() {
yield takeEvery(request.type, auth);
}
export default function* () {
yield all([watcher()]);
}
reducers:
import { combineReducers } from "redux";
import authReducer from './slice/authSlice';
const rootReducer = combineReducers({
user: authReducer,
});
export default rootReducer;
Slice:
import { createSlice } from '#reduxjs/toolkit';
const authSlice = createSlice({
name: 'user',
initialState: {
loading: false,
data: [],
error: null
},
reducers: {
request(state) {
state.loading = true;
},
authSuccess(state, action) {
state.loading = false;
state.data = action.payload;
},
authFailure(state, action) {
state.loading = false;
state.error = action.payload;
}
}
});
export const { request, authSuccess, authFailure } = authSlice.actions;
export default authSlice.reducer;
can anyone help me ?
................................................................................

Related

How to test a component that renders asynchronously after a call

Suppose I have a component that loads its content when an asynchronous call returns succesfuly:
const MyScreen = () => {
let userData: userDataResponse;
const [email, setEmail] = useState("");
const [firstTime, setFirstTime] = useState(true);
async function localGetUserData() {
userData = await getUserData();
setEmail(userData.email);
setFirstTime(false);
}
useEffect(() => {
localGetUserData();
}, []);
if (firstTime) {
return <Text>Cargando...</Text>;
}
return (
<SafeAreaView style={styles.formStyling}>
When the data is available, it sets a state variable so the real content renders
If I want to test it, I think I should mock the getUserData so the mocked function returns a mocked email, say {email: a#b.c}
What would be a good approach to achieve this?
Assuming following component setup (as I cannot see whole component):
myScreenUtils.js
export const getUserData = async () => {
return Promise.resolve('original implementation')
}
MyScreen.jsx
import { useState, useEffect } from "react";
import { getUserData } from './myScreenUtils.js'
const MyScreen = () => {
let userData;
const [email, setEmail] = useState("");
const [firstTime, setFirstTime] = useState(true);
async function localGetUserData() {
userData = await getUserData();
setEmail(userData.email);
setFirstTime(false);
}
useEffect(() => {
localGetUserData();
}, []);
if (firstTime) {
return <div>Cargando...</div>;
}
return (
<div>{email}</div>
)
};
export default MyScreen;
You can write following tests:
import { screen, render, waitFor, waitForElementToBeRemoved } from '#testing-library/react';
import MyScreen from "../MyScreen";
import * as utils from '../myScreenUtils';
describe('MyScreen', () => {
it('the text is displayed and then removed', async () => {
jest.spyOn(utils, 'getUserData').mockResolvedValue({ email: 'mocked value' });
render(<MyScreen />);
expect(screen.getByText('Cargando...')).toBeInTheDocument();
await waitForElementToBeRemoved(() => screen.queryByText('Cargando...'))
})
it('the text email is fetched and displayed', async () => {
jest.spyOn(utils, 'getUserData').mockResolvedValue({ email: 'mocked value' });
render(<MyScreen />);
await waitFor(() => {
expect(screen.getByText('mocked value')).toBeInTheDocument()
})
})
})

Redux-Toolkit: createAsyncThunk axios request is not sent

I am using react native for frontend and django for backend of my application, but I can't make an api request with axios to login authentication.
I would like to know why the request is not made to the backend but is rejected.
The code fails during the request, up to which point it is executed.
userSlice:
import axios from "axios";
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
const initialState = {
isLoading:false,
isSuccess:false,
isError:false,
message:"",
userInfo: null
}
export const login = createAsyncThunk(
'user/login',
async (userData, thunkApi) => {
try{
const config = {
headers:{
'Content-type':'application/json'
}
}
const response = await axios.post(
'http://127.0.0.1:8000/api/users/login/',
userData,
config
)
return response.data
}catch(error){
return thunkApi.rejectWithValue(error.response?.data)
}
})
const userSlice = createSlice({
name:"user",
initialState,
reducers:{
},
extraReducers: (builder) => {
builder.addCase(login.pending, (state) => {
state.isLoading = true
})
builder.addCase(login.fulfilled, (state, action) => {
state.isLoading = false
state.isSuccess = true
state.userInfo = action.payload
})
builder.addCase(login.rejected, (state, action) => {
state.isLoading = false
state.isSuccess = true
state.userInfo = null
state.message = action.payload
})
}
})
export default userSlice.reducer
store:
import {configureStore, combineReducers} from '#reduxjs/toolkit'
import userReducer from './userSlice'
const rootReducer = combineReducers({
user: userReducer
})
export const store = configureStore({
reducer: rootReducer
})
export default store
login dispatch:
const onPressHandler = () =>{
dispatch(login({username, password}))
}

TypeError: undefined is not an object (evaluating '_useSelector.attendance')

Any idea why I am getting the error TypeError: undefined is not an object (evaluating '_useSelector.attendance') with redux.. Everything seems to be working fine but I just done understand why it keeps coming back to this.
Reducers.js
import { GET_ATTENDANCE, ADD_TO_ATTENDANCE_LIST } from "./actions";
const initialState = () => ({
attendance: [],
attendancebook: [],
});
function attendanceReducer(state = initialState, action) {
switch (action.type) {
case GET_ATTENDANCE:
return { ...state, attendance: action.payload };
case ADD_TO_ATTENDANCE_LIST:
return {
...state,
attendancebook: [...state.attendancebook, action.payload],
};
default:
return state;
}
}
export default attendanceReducer;
AttendanceScreen.js
function AttendanceScreen({ route }) {
const navigation = useNavigation();
const listing = route.params;
const dispatch = useDispatch();
const { attendance, attendancebook } = useSelector(
(state) => state.attendanceReducer
);
const getAttendance = () => {
try {
dispatch({
type: GET_ATTENDANCE,
payload: attendancelist,
});
} catch (error) {
console.log(error);
}
};
const fetchAttendance = () => dispatch(getAttendance());
const addToAttendanceList = (data) => dispatch(addAttendance(data));
useEffect(() => {
fetchAttendance();
}, []);
store.js
import attendanceReducer from "./reducers";
const persistConfig = {
key: "root",
storage: AsyncStorage,
whitelist: ["attendancebook"],
};
const rootReducer = combineReducers({
attendanceReducer: persistReducer(persistConfig, attendanceReducer),
});
export const store = createStore(rootReducer, applyMiddleware(thunk));
export const persistor = persistStore(store);
actions.js
export const GET_ATTENDANCE = "GET_ATTENDANCE";
export const ADD_TO_ATTENDANCE_LIST = "ADD_TO_ATTENDANCE_LIST";
export const addAttendance = (data) => (dispatch) => {
dispatch({
type: ADD_TO_ATTENDANCE_LIST,
payload: data,
});
};
Please any help will be appreciated.

How to setup redux persist for offline storage in React Native?

I used redux persist with asyncstorage for offline data, but after i clear my app (like swipe right to delete it from the processes in my android phone), my data is gone the next time i open the app again.
Here is the relevant code:
// src/reduxActions/store.ts
import { configureStore } from '#reduxjs/toolkit'
import { combineReducers, Store } from 'redux';
import {
Persistor,
persistStore,
persistReducer,
} from 'redux-persist'
import AsyncStorage from '#react-native-async-storage/async-storage';
import AuthReducer from './auth/reducer';
import ItemReducer from './item/reducer';
import autoMergeLevel1 from 'redux-persist/lib/stateReconciler/autoMergeLevel1';
const rootReducer = combineReducers({
auth: AuthReducer,
item: ItemReducer,
});
const persistConfig = {
key: 'root',
storage: AsyncStorage,
whitelist: [],
storageReconciler: autoMergeLevel1,
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
export type RootState = ReturnType<typeof rootReducer>;
let store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
}),
});
export function getStore(): Store<RootState> {
return store;
}
export const persistor = persistStore(store);
export type AppDispatch = typeof store.dispatch;
// src/reduxActions/item/reducer.ts
import { createSlice } from '#reduxjs/toolkit';
import { Item } from 'models/item';
import { createItem, getItems, updateItem, deleteItem } from './actions';
interface ItemState {
items: {
[index: string]: Item,
}
itemIds: string[],
offlineItems: {
[index: string]: Item,
},
lastOfflineItemId: string,
}
const initialState: ItemState = {
items: {},
itemIds: [],
offlineItems: {},
lastOfflineItemId: '0',
}
const itemSlice = createSlice({
name: 'item',
initialState,
reducers: {
createOfflineItem(state, action) {
const id = (parseInt(state.lastOfflineItemId)+1).toString();
const item = {
"id": id,
"createdAt": Date.now(),
}
state.offlineItems[id] = { ...item, ...action.payload };
state.lastOfflineItemId = id;
},
toggleOfflineIsFavorite(state, action) {
state.offlineItems[action.payload.id].isFavorite = action.payload.isFavorite;
},
editOfflineItem(state, action) {
state.offlineItems[action.payload.id] = {
...state.offlineItems[action.payload.id],
...action.payload,
};
},
deleteOfflineItem(state, action) {
delete state.offlineItems[action.payload];
}
},
extraReducers: (builder) => {
builder.addCase(createItem.fulfilled, (state, action) => {
state.items[action.payload.id] = action.payload;
state.itemIds.unshift(action.payload.id);
})
builder.addCase(getItems.fulfilled, (state, action) => {
state.items = action.payload.results.reduce(function(map: { [index: string]: Item }, item: Item) {
map[item.id] = item;
return map;
}, {});
state.itemIds = action.payload.results.map((item: Item) => item.id);
})
builder.addCase(updateItem.fulfilled, (state, action) => {
state.items[action.payload.id] = action.payload;
})
builder.addCase(deleteItem.fulfilled, (state, action) => {
state.itemIds = state.itemIds.filter(id => id != action.payload);
delete state.items[action.payload];
})
builder.addDefaultCase((state) => {
return state;
})
},
})
const { actions, reducer } = itemSlice;
export const { createOfflineItem, toggleOfflineIsFavorite, editOfflineItem, deleteOfflineItem } = actions;
export default reducer;
Here is the complete code: https://www.github.com/roumanite/wmi-mobile
I want to be able to store offline data that will never go away, not with a phone restart and can also be backup & synced. I guess it will go away only if uninstalled & cleared completely
How can I fix this? thanks
You can update const persistConfig object:
const persistConfig = {
key: 'root',
storage: AsyncStorage,
whitelist: ['auth', 'item'],
storageReconciler: autoMergeLevel1,
};

Redux Saga action async action called once but fired twice

So I dispatch my redux-saga action once from my react-native app and it makes two API calls. I'm trying to figure out why this is, and how to only have it send one.
App.js
const initFetch = async () => {
const userToken = await AsyncStorage.getItem("userToken");
dispatch(fetchLiked({ page: 0, search: "", userToken }));
};
useEffect(() => {
initFetch();
}, []);
configureStore.js
import { createStore, combineReducers, applyMiddleware } from "redux";
import { persistStore, persistReducer } from "redux-persist";
import AsyncStorage from "#react-native-community/async-storage";
import likedReducer from "./reducers/liked";
import createSagaMiddleware from "redux-saga";
import rootSaga from "./sagas/rootSaga";
const rootReducer = combineReducers({
liked: likedReducer,
});
const persistConfig = {
key: "primary",
storage: AsyncStorage,
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
const sagaMiddleware = createSagaMiddleware();
export default () => {
let store = createStore(persistedReducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(rootSaga);
let persistor = persistStore(store);
return { store, persistor };
};
rootSaga.js
import { take, call, all } from "redux-saga/effects";
import { watchFetchLikedSaga } from "./likedSaga";
export default function* rootSaga() {
yield all([watchFetchLikedSaga()]);
}
likedSaga.js
import { takeLatest, call, put } from "redux-saga/effects";
import Server from "../../utils/Server";
import { fetchLikedSuccess } from "./../actions/liked";
import { types } from "../actions/types";
function* asyncFetchLiked(data) {
console.log("sending async fetch");
const { page, search, userToken } = data.payload;
try {
const response = yield call(() =>
Server.get("/api/titles/getliked", {
headers: { "auth-token": userToken },
params: { page: page, search: search },
})
);
yield put(fetchLikedSuccess(response.data));
} catch (e) {
console.log(e);
}
}
export function* watchFetchLikedSaga() {
yield takeLatest(types.SEND_REQUEST, asyncFetchLiked);
}
export const fetchLiked = (data) => {
return {
type: types.SEND_REQUEST,
payload: data,
};
};
actions/liked.js
export const fetchLiked = (data) => {
console.log("fetchLiked");
return {
type: types.SEND_REQUEST,
payload: data,
};
};
export const fetchLikedSuccess = (data) => {
console.log("fetchLikedSuccess");
return {
type: types.SEND_REQUEST_SUCCESS,
payload: data,
};
};
export const fetchLikedFailure = (error) => {
return {
type: types.SEND_REQUEST_FAILURE,
payload: {},
error: error,
};
};
My console.log output looks like this. You can see the action is only being dispatched once, but it is sending two async requests and calling the reducer success action twice.
fetchLiked
sending async fetch
sending async fetch
fetchLikedSuccess
fetchLikedSuccess