Redux, Axios, and Redux Thunk with Expo 37 - react-native

so i am using Redux redux-thunk redux-persist and axios all together here is my setup:
**action.js**
import axios from 'axios';
import * as type from './constants';
export const handleSignup = userDetails => async (dispatch) => {
const {
email, password, username, version,
} = userDetails;
return axios
.post('/users/signup', {
email,
password,
username,
platform: version,
})
.then((res) => {
dispatch({
type: type.USER_SIGNUP_SUCCESS,
payload: res.data,
});
axios.defaults.headers.common.Authorization = `Bearer ${
res.data.access_token
}`;
return res;
});
};
**api.js**
import axios from 'axios';
import configureStore from '../store/configureStore';
const { store } = configureStore();
axios.defaults.baseURL = 'http://baseurl/api/v1';
axios.defaults.headers.common['Content-Type'] = 'application/json';
const accesToken = store.getState().authentication.token;
if (accesToken) {
axios.defaults.headers.common.Authorization = `Bearer ${accesToken}`;
}
axios.defaults.headers.common['Content-Type'] = 'application/json';
axios.interceptors.response.use(
async response => response,
error => Promise.reject(error),
);
**configureStore.js**
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import { persistStore, persistCombineReducers } from 'redux-persist';
import rootReducer from '../reducers';
import storage from 'redux-persist/lib/storage';
const persistConfig = {
key: 'root',
storage,
blacklist: ['name1', 'name2', 'name3'],
};
const middlewares = [thunk];
const enhancer = composeWithDevTools(applyMiddleware(...middlewares));
const persistedReducer = persistCombineReducers(persistConfig, rootReducer);
export default () => {
const store = createStore(persistedReducer, undefined, enhancer);
const persistor = persistStore(store, null, () => {
store.getState();
});
return { store, persistor };
};
and i got this code inside my reducer for the signup success action
case type.USER_SIGNUP_SUCCESS:
return {
...state,
...action.payload.data.user,
email: action.payload.data.user.email,
username: action.payload.data.user.username,
token: action.payload.data.access_token,
user_id: action.payload.data.user.id,
};
and finally, i am calling handleSignUp on a submit button click:
onSignupClicked = () => {
this.setState({
error: false,
errorMessage: [],
loading: true,
});
const { platform } = Constants;
const version = Object.keys(platform)[0];
const {
user: { email, password, username },
} = this.state;
const { handleSignup, navigation } = this.props;
handleSignup({
email,
password,
username,
version,
})
.then(() => {
this.setState({ loading: true });
navigation.navigate(NAV_INTRO);
})
.catch((err) => {
console.log('ERROR : ',err)
});
};
sorry for a long code, so now my problem is that as soon as the user presses signup i am automatically getting ERROR : Network Error message. it doesn't wait for the request to get completed i guess, but the confusing part for me is this same code works on a previous version of the app with Expo 30.0.0, now its running on Expo 37.0.0. and i have double checked the API no problem with that, my question is is there something wrong with his code? is there a reason for it to return Network Error so fast?
i know this is bulky but any suggestion would be nice, Thanks.
in case its important here are my versions:
"react-redux": "5.0.7",
"redux": "4.0.0",
"redux-devtools-extension": "^2.13.8",
"redux-logger": "3.0.6",
"redux-mock-store": "1.5.3",
"redux-persist": "5.10.0",
"redux-thunk": "2.2.0",

Related

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}))
}

React-native redux-saga error: takeLatest$1 requires a saga parameter

I created an App with some components and using the redux-saga in the following component:
// AlphaScreen.js
import ... // react & react-native
import { useSelector, useDispatch } from 'react-redux';
import { getUser } from '../redux/ducks/user';
const AlphScreen = props => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(getUser());
}, [dispatch]);
const users = useSelector((state) => state.user.user);
console.log(users);
return (
<View><Text>Blah</Text></View>
);
}
// redux/ducks/user.js
export const SET_USER = "SET_USER";
export const GET_USER = "GET_USER";
export const setUser = (user) => ({
type: SET_USER,
user // user: user
});
export const getUser = () => ({
tye: GET_USER
});
const initialState = {
user: undefined
};
const userReducer = (state = initialState, action) => {
switch(action.type) {
case SET_USER:
const {user} = action;
return {...state, user:user};
default:
return state;
}
};
export default userReducer;
// redux/defaultStore.js
import {applyMiddleware, combineReducers, createStore} from 'redux'
import createSagaMiddleware from "redux-saga";
import counterReducer from './ducks/counter'
import userReducer from './ducks/user';
import { watcherSaga } from './sagas/saga';
const reducer = combineReducers({
user: userReducer
});
const sagaMiddleware = createSagaMiddleware();
const middlewares = [sagaMiddleware];
const store = createStore(reducer, applyMiddleware(...middlewares));
sagaMiddleware.run(watcherSaga)
export default store;
// redux/sagas/saga.js
import { takeLatest } from 'redux-saga/effects';
import { handleGetUsers } from './handlers/user';
import { GET_USER } from '../ducks/user';
export function* watcherSaga() {
yield takeLatest(GET_USER, handleGetUsers); //<- getting error takeLatest$1 requires a saga parameter
}
// redux/sagas/handlers/user.js
import { call, put } from "redux-saga/effects";
import { setUser } from "../../ducks/user";
import { requestGetUser } from "../requests/user";
export function* handleGetUser(action) {
try {
const response = yield call(requestGetUser);
const { data } = response;
yield put(setUser(data));
} catch (error) {
console.log(error);
}
}
// redux/sagas/request/user.js
import axios from 'axios'
const requestGetUser = () => {
return axios.request({
method: "get",
url:"https://jsonplaceholder.typicode.com/users"
});
}
export { requestGetUser };
But I have following error:
takeLatest$1 requires a saga parameter
at node_modules/#redux-saga/core/dist/io-1d6eccda.js:37:10 in check
at node_modules/#redux-saga/core/dist/redux-saga-effects.dev.cjs.js:386:2 in validateTakeEffect
at node_modules/#redux-saga/core/dist/redux-saga-effects.dev.cjs.js:402:22 in takeLatest$1
at src/redux/sagas/saga.js:6:10 in watcherSaga
at node_modules/#redux-saga/core/dist/redux-saga-core.dev.cjs.js:1161:17 in next
at node_modules/#redux-saga/core/dist/redux-saga-core.dev.cjs.js:1112:6 in proc
at node_modules/#redux-saga/core/dist/redux-saga-core.dev.cjs.js:1371:19 in immediately$argument_0
at node_modules/#redux-saga/core/dist/redux-saga-core.dev.cjs.js:60:15 in immediately
at [native code]:null in runSaga
at src/redux/configureStore.js:16:0 in <global>
at App.js:7:0 in <global>
at node_modules/expo/AppEntry.js:3:0 in <global>
at http://192.168.1.154:19000/node_modules/expo/AppEntry.bundle?platform=android&dev=true&hot=false&minify=false:141908:3 in global code
The above error occurred in task watcherSaga
Any suggestions for fixing the error? Thanks
To me it looks like a typo, handleGetUsers vs handleGetUser :)

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

How can I test actions within a Vuex module?

I want to test a vuex module called user.
Initially, I successfully registered my module to Vuex. Its works as expected.
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
user
}
})
export default store
My user module is defined as follows
store/modules/user.js
const state = {
token: getToken() || '',
}
export const getters = {
token: state => state.token,
}
const mutations = {
[SET_TOKEN]: (state, token) => {
state.token = token
}
}
const actions = {
[LOGIN] ({ commit }, body) {
return new Promise((resolve, reject) => {
login(body).then(response => { //login is an api method, I'm using axios to call it.
const { token } = response.data
setToken(token)
commit(SET_TOKEN, token)
resolve()
}).catch(error => {
reject(error)
})
})
}
}
export default {
state,
getters,
mutations,
actions
}
login api
api/auth.js
import request from '#/utils/request'
export function login (data) {
return request({
url: '/auth/login',
method: 'post',
data
})
}
axios request file
utils/request
import axios from 'axios'
import store from '#/store'
import { getToken } from '#/utils/auth'
const request = axios.create({
baseURL: process.env.VUE_APP_BASE_API_URL,
timeout: 5000
})
request.interceptors.request.use(
config => {
const token = getToken()
if (token) {
config.headers['Authentication'] = token
}
return config
}
)
export default request
When I want to write some test (using Jest), for example login action as shown above.
// user.spec.js
import { createLocalVue } from '#vue/test-utils'
import Vuex from 'vuex'
import actions from '#/store/modules/user'
const localVue = createLocalVue()
localVue.use(Vuex)
test('huhu', () => {
expect(true).toBe(true)
// implementation..
})
How can I write test for my Login action? Thanks. Sorry for my beginner question.
EDIT: SOLVED Thank you Raynhour for showing to me right direction :)
import { LOGIN } from '#/store/action.types'
import { SET_TOKEN } from '#/store/mutation.types'
import { actions } from '#/store/modules/user'
import flushPromises from 'flush-promises'
jest.mock('#/router')
jest.mock('#/api/auth.js', () => {
return {
login: jest.fn().mockResolvedValue({ data: { token: 'token' } })
}
})
describe('actions', () => {
test('login olduktan sonra tokeni başarıyla attı mı?', async () => {
const context = {
commit: jest.fn()
}
const body = {
login: 'login',
password: 'password'
}
actions[LOGIN](context, body)
await flushPromises()
expect(context.commit).toHaveBeenCalledWith(SET_TOKEN, 'token')
})
})
Store it's just a javascript file that will export an object. Not need to use vue test util.
import actions from '../actions'
import flushPromises from 'flush-promises'
jest.mock('../api/auth.js', () => {
return {
login: jest.fn()..mockResolvedValue('token')
}; // mocking API.
describe('actions', () => {
test('login should set token', async () => {
const context = {
commit: jest.fn()
}
const body = {
login: 'login',
password: 'password'
}
actions.login(context, body)
await flushPromises() // Flush all pending resolved promise handlers
expect(context.commit).toHaveBeenCalledWith('set_token', 'token')
})
})
but you need to remember that in unit tests all asynchronous requests must be mocked(with jest.mock or something else)

Updating Only When There's New Data, React Native App, Redux-Socket IO?

Goal: Make API calls only when we have new data rather whenever user navigates to screen.
Let's say the app I'm working on has 2 screens the user can navigate to. Receiving Text Screen and Sending Screen. Each user can add friends/followers as well. On the Sending Screen, the user can send a text item to the followers of their choice. On the Receiving Text Screen, the follower user will see the text item pop up at the top of the screen.
Currently, user on the Receiving Text Screen can only see the text item when we make an API call called fetchTexts. We make this call whenever user navigates to this screen or whenever user pulls down to refresh.
class TextScreen extends Component {
static propTypes = {
fetchTexts: PropTypes.func,
};
constructor(props) {
super(props);
this.state = {
refreshing: false,
};
}
componentDidMount() {
textsGATracker();
this.props.fetchTexts(this.props.userId);
}
_onRefresh() {
this.setState({ refreshing: true });
// when the screen is pulled down, fetchTexts
this.props.fetchTexts(this.props.userId);
this.setState({ refreshing: false });
}
const mapStateToProps = (state) => {
return {
texts: state.texts_reducer.texts,
userId: state.user_reducer.userId,
textsReceived: state.texts_reducer.textsReceived,
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchTexts: (userId) => { dispatch(fetchTexts(userId)); },
};
};
These are the Redux Actions that occur with fetchTexts :
import axios from 'axios';
export const REQUEST_TEXTS_ITEMS = 'REQUEST_TEXTS_ITEMS';
export const RECEIVE_TEXTS_ITEMS = 'RECEIVE_TEXTS_ITEMS';
export const FAILED_TEXTS_ITEMS = 'FAILED_TEXTS_ITEMS';
axios.defaults.baseURL = 'nope.herokuapp.com';
/* ======= Actions to Request Texts by userId ======= */
export const requestTextsItems = userId => ({
type: REQUEST_TEXTS_ITEMS,
userId,
});
export const receiveRecsItems = json => ({
type: RECEIVE_TEXTS_ITEMS,
texts: json,
});
export const failedtextsItems = error => ({
type: FAILED_TEXTS_ITEMS,
infoMsg: `The API request failed - ${error}`,
});
export const fetchtexts = userId => (dispatch, getState) => {
const AUTH_TOKEN = `Bearer ${getState().auth_reducer.bearerToken}`;
axios.defaults.headers.common.Authorization = AUTH_TOKEN;
dispatch(requesttextsItems(userId));
axios.get(`/sharesTexts/${userId}`)
.then((response) => {
dispatch(receivetextsItems(response.data));
}, (error) => {
dispatch(failedtextsItems(error));
});
};
On the Sending Screen, user selects a text item to share with their followers. The redux action used looks like this:
export const usersCreateshareTextId = (textId, description, sharerUserId, recipientUserId) => (dispatch, getState) => {
recipientUserId);
const AUTH_TOKEN = `Bearer ${getState().auth_reducer.bearerToken}`;
axios.defaults.headers.common.Authorization = AUTH_TOKEN;
dispatch(requestUsersCreateshare(description, sharerUserId, recipientUserId));
return axios.post(`/users/createShare/${textId}`, {
description,
sharerUserId,
recipientUserId,
})
.then((response) => {
response.data);
dispatch(receiveUsersCreateshare(response.data));
}, (error) => {
dispatch(failedUsersCreateshare(error));
});
};
So I looked into websockets, and now Redux Socket IO v.1.4.0 seems to be my best bet. It looks like it's mainly used in the store. My store looks like this:
import { createStore, applyMiddleware, compose } from 'redux';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import reducers from '../redux/reducers';
const middleware = [thunk];
const persistConfig = {
key: 'root',
storage,
stateReconciler: autoMergeLevel2,
blacklist: ['network', 'auth_reducer', 'search_reducer', 'follow_reducer', 'following_reducer',
'followers_reducer'],
};
if (process.env.NODE_ENV === 'development') {
middleware.push(logger);
}
const persistedReducer = persistReducer(persistConfig, reducers);
export const store = createStore(
persistedReducer,
compose(applyMiddleware(...middleware)),
);
export const persistor = persistStore(store);
How would I implement Redux Socket IO with what I have so far?
Thank you thank you thank!