Redux Saga action async action called once but fired twice - react-native

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

Related

redux toolkit and reselect

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 ?
................................................................................

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-thunk: actions must be plain objects

I have been trying to use redux and redux-thunk to help get a json file from a api and have been getting a warning stating that action must be a plain object. I am really confused as to where the issue is in the code. i have tried following many other stackoverflow posts and a couple of guides online and have not really got a good grasp of where I am going wrong. I understand that this is a problem with how I am referencing async and dispatch but do not know how to fix it.
This is the function that causes the warning to appear in the simulator
export const fetchData = url => {
console.log("Should enter async dispatch");
return async (dispatch) => {
dispatch(fetchingRequest());
fetch("https://randomuser.me/api/?results=10")
.then(response => {
if (response.ok) {
let json = response.json();
dispatch(fetchingSuccess(json));
console.log("JSON", json);
}
})
.catch(error => {
dispatch(fetchingFailure(error));
console.log("Error", error);
});
};
};
Here is the output in the console
Possible Unhandled Promise Rejection (id: 0):
Error: Actions must be plain objects. Use custom middleware for async actions.
Error: Actions must be plain objects. Use custom middleware for async actions.
Edit: including setup of middleware
I have the middleware setup in the index.js file of my app
index.js
import { AppRegistry } from "react-native";
import App from "./App";
import { name as appName } from "./app.json";
import { Provider } from "react-redux";
import React, { Components } from "react";
import { createStore, applyMiddleware } from "redux";
import appReducer from "./src/data/redux/reducers/appReducer";
import thunk from "redux-thunk";
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const store = createStoreWithMiddleware(appReducer);
console.log("Store", store.getState());
const AppContainer = () => (
<Provider store = {store}>
<App />
</Provider>
);
AppRegistry.registerComponent(appName, () => AppContainer);
I learned this implementation of store from a Youtube Tutorial.
Edit 2: Adding in the fetchData call
I call fetchData in a _onPress function like this
_onPress = () => {
const {fetchData} = this.props;
let url = "https://randomuser.me/api/?results=10";
fetchData(url);
console.log("should have fetched");
};
this is how my app has been connected to redux
const mapStateToProps = state => {
return { response: state };
};
const mapStateToDispatch = dispatch => ({
fetchData: url => dispatch(fetchData(url)),
});
export default connect(
mapStateToProps,
mapStateToDispatch
)(SearchScreen);
these are the action in my app
export const fetchingRequest = () => {
{
type: FETCHING_REQUEST;
}
};
export const fetchingSuccess = json => {
{
type: FETCHING_SUCCESS;
payload: json;
}
};
export const fetchingFailure = error => {
{
type: FETCHING_FAILURE;
payload: error;
}
};
I was able to figure out the problem thanks to working through the steps in the comments thanks to Michael Cheng. I ended up finding that the problem was that i had actions with plain objects but they were not returning anything.
The original actions were
export const fetchingRequest = () => {
{
type: FETCHING_REQUEST;
}
};
export const fetchingSuccess = json => {
{
type: FETCHING_SUCCESS;
payload: json;
}
};
export const fetchingFailure = error => {
{
type: FETCHING_FAILURE;
payload: error;
}
};
to this
export const fetchingRequest = () => {
return {
type: FETCHING_REQUEST
}
};
export const fetchingSuccess = json => {
return {
type: FETCHING_SUCCESS,
payload: json
}
};
export const fetchingFailure = error => {
return {
type: FETCHING_FAILURE,
payload: error
};
};
with including the return for each action

Fetching data with redux doesn't work for me, initialState don't change

I started my RN project without redux and when I fetch data (using formData) it works fine without redux, but I've decided to inculude Redux in my project. that's what I did:
I created two new folder , one for actions (constants and ations files) and the other for store witch contains a folder for reducer
after that I created 3 actions like this: getScoreList, getScoreListSuccess, getScoreListFailure
function getScoreListSuccess(data) {
return {
type: FETCHING_SCORE_LIST_SUCCESS,
data: data
}
}
in the same file I created function for fetching data using async await and formData:
import FormData from 'FormData';
const formData = new FormData()
formData.append('userId', '1');
formData.append('key', 'XXXXXXXXX');
export const getScoreFromAPI = () => {
return async dispatch => {
dispatch(getScoreList());
try {
let res = await fetch('https://www.***.com/***/***/***.php', {
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data',
},
body: formData,
})
let json = await res.json();
dispatch(getScoreListSuccess(json));
} catch (err) {
console.log("the error is: " + error)
}
};
};
Then I set my reducer for 3 cases:
import {FETCHING_SCORE_LIST, FETCHING_SCORE_LIST_SUCCESS, FETCHING_SCORE_LIST_FAILURE} from '../../actions/constants'
const initialState = {
scoreList: [],
isFetching: false,
error: false,
}
export default function getScoreList(state = initialState, action) {
switch (action.type) {
case FETCHING_SCORE_LIST:
return {
...state,
isFetching: true,
scoreList: [],
};
case FETCHING_SCORE_LIST_SUCCESS:
return {
...state,
isFetching: false,
scoreList: action.data,
};
case FETCHING_SCORE_LIST_SUCCESS:
return {
...state,
isFetching: false,
error: true,
};
default:
return state;
}
};
and in another file (index.js) that's what I did:
import {combineReducers} from 'redux'
import getScoreList from './scoreListReducer'
const rootReducer = combineReducers({
getScoreListS: getScoreList,
})
export default rootReducer
then I created my store with thunk middleware in configStore.js file
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './Reducers/index'
export default function configureStore() {
let store = createStore(rootReducer, applyMiddleware(thunk))
return store
}
app.js file:
...
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './Store/Reducers/index'
const createStoreWithMW = applyMiddleware(thunk)(createStore);
const store = createStoreWithMW(rootReducer);
export default class App extends Component<Props> {
render() {
return (
<Provider store={store}>
<Navigation/>
</Provider>
);
}
}
And finally I connected my component with redux :
...
import { connect } from 'react-redux';
import {getScoreFromAPI} from '../../actions/actions';
import { bindActionCreators } from 'redux';
...
function mapStateToProps(state) {
return{
scoreListState: state.getScoreListS,
};
}
const mapDispatchToProps = dispatch => bindActionCreators({
fetchScoreList: getScoreFromAPI
}, dispatch)
export default connect(
mapStateToProps,
mapDispatchToProps
)(ScoreList);
to test it I console.warn scoreListState:
componentDidMount() {
console.warn(this.props.scoreListState);
}
this is what I have
I hope it was clear enough even if it's a little bit long

Asyncstorage in Redux action

I'm trying to use my access token (stored in Asyncstorage) in a Redux action. This is my code:
export function fetchData() {
const endpoint = 'someEndpoint.com';
let accessToken = '';
myAccessToken().then((token) => {
accessToken = token;
});
return (dispatch) => {
dispatch(getData());
axios.get(endpoint, { headers: { 'access-token': accessToken } })
.then(response => {
dispatch(getDataSuccess(response.data));
})
.catch(error => {
dispatch(getDataFailure(error));
});
};
}
const myAccessToken = async () => {
try {
const retrievedItem = await AsyncStorage.getItem('accessToken');
return retrievedItem;
} catch (error) {
return null;
}
};
But fetching of the key is obviously async, I'm not sure on how to use the accessToken in the API call. I'm not allowed to do something like this:
export function fetchData() {
const endpoint = 'someEndpoint.com';
myAccessToken().then((token) => {
return (dispatch) => {
dispatch(getData());
axios.get(endpoint, { headers: { 'access-token': token } })
.then(response => {
dispatch(getDataSuccess(response.data));
})
.catch(error => {
dispatch(getDataFailure(error));
});
};
});
}
My Store:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import app from './reducers';
export default function configureStore() {
return createStore(app, applyMiddleware(thunk));
}
Update
In the end I did it a little bit different, In my componentDidMount:
componentDidMount() {
AsyncStorage.getItem('accessToken').then((accessToken) => {
this.setState({ accessToken });
this.props.fetchData(accessToken);
});
}
Thanks, Kevin.
I think you should use redux-thunk library for asynchronous updates of the redux state. It's easy to configure in the store.js file:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
// Note: this API requires redux#>=3.1.0
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
Then, I would implement it like this:
export function fetchData() {
return async function(dispatch) {
const endpoint = 'someEndpoint.com';
const accessToken = await myAccessToken();
try {
const response = await axios.get(endpoint, { headers: { 'access-token': accessToken } });
return dispatch(getDataSuccess(response.data));
} catch (error) {
return dispatch(getDataFailure(error));
}
}
}