Axios request sent twice when using React Saga - javascript

I am trying to send a GET request using axios and React Saga.
The request fired twice
This is my component file where I call the getBusinessHoursList action:
import { getBusinessHoursList } from "../../../../redux/actions";
...
...
componentDidMount() {
this.props.getBusinessHoursList(this.state.id);
}
...
...
const mapStateToProps = ({ settings }) => {
return {
settings
};
};
export default connect(
mapStateToProps,
{
getBusinessHoursList,
}
)(injectIntl(BusinessHours));
This is my service file where I use axios to get my business hours list:
setting-service.js:
import axios from '../util/api';
import { configureStore } from '../redux/store';
export const settingsService =
{
getBusinessHours,
};
function getBusinessHours() {
const store = configureStore({});
const user = JSON.parse(store.getState().authUser.user)
return axios.get("business/" + user.business.id + "/businesshours")
.then(response => {
return response.data.data
}).catch(error => {
return error.response.data
})
}
This is actions file where I define the actions
actions.js:
import {
CHANGE_LOCALE,
SETTING_GET_BUSINESS_HOURS_FAIL,
SETTING_GET_BUSINESS_HOURS_SUCCESS,
SETTING_GET_BUSINESS_HOURS,
} from '../actions';
export const getBusinessHoursList = (data) => ({
type: SETTING_GET_BUSINESS_HOURS,
payload: data
});
export const getBusinessHoursSuccess = (items) => ({
type: SETTING_GET_BUSINESS_HOURS_SUCCESS,
payload: items
});
export const getBusinessHoursFail = (error) => ({
type: SETTING_GET_BUSINESS_HOURS_FAIL,
payload: error
});
reducer.js
import {
CHANGE_LOCALE,
SETTING_GET_BUSINESS_HOURS,
SETTING_GET_BUSINESS_HOURS_FAIL,
SETTING_GET_BUSINESS_HOURS_SUCCESS
} from '../actions';
const INIT_STATE = {
errors: '',
loadingBH: false,
businessHoursItems: null
};
export default (state = INIT_STATE, action) => {
switch (action.type) {
case SETTING_GET_BUSINESS_HOURS:
return { ...state, loadingBH: false };
case SETTING_GET_BUSINESS_HOURS_SUCCESS:
return { ...state, loadingBH: true, businessHoursItems: action.payload};
case SETTING_GET_BUSINESS_HOURS_FAIL:
return { ...state, loadingBH: true, errors: action.payload };
default: return { ...state };
}
}
saga.js:
import { all, call, fork, put, takeEvery, takeLatest, take } from "redux-saga/effects";
import { getDateWithFormat } from "../../helpers/Utils";
import { settingsService } from '../../services/settings-service'
import {
SETTING_GET_BUSINESS_HOURS,
SETTING_UPDATE_BUSINESS_HOURS,
} from "../actions";
import axios from "../../util/api";
import { NotificationManager } from "../../components/common/react-notifications";
import {
getBusinessHoursSuccess,
getBusinessHoursFail,
} from "./actions";
const getServiceListRequest = async () =>
await settingsService.getBusinessHours()
.then(authUser => authUser)
.catch(error => error);
function* getBusinessHours() {
console.log('test')
try {
const response = yield call(getServiceListRequest);
yield put(getBusinessHoursSuccess(response));
} catch (error) {
yield put(getBusinessHoursFail(error));
}
}
export function* watchGetBusinessHours() {
yield takeLatest(SETTING_GET_BUSINESS_HOURS, getBusinessHours);
}
export default function* rootSaga() {
yield all([
fork(watchGetBusinessHours),
]);
}
Global sagas file : sagas.js:
import { all } from 'redux-saga/effects';
import authSagas from './auth/saga';
import settingsSagas from './settings/saga';
export default function* rootSaga(getState) {
yield all([
authSagas(),
settingsSagas(),
]);
}
The request fired successfully and I get Business hours list but the request fired twice
I tried to use takeLatest in place of takeEvery
This is the network tab

Related

fetch function doesn't called with react-redux

I want to call a rest api with react-redux but my fetch doesn't called at all.
My actions:
restActions.js file:
export const customerbyidGetAction = (data) => ({ type: types.customerbyidGetAction, data });
My reducer:
restReducer.js file:
import { Map, fromJS } from 'immutable';
import * as types from '../constants/restConstants';
const initialState = {};
const initialImmutableState = fromJS(initialState);
export default function reducer(state = initialImmutableState, action) {
switch(action.type) {
case types.customerbyidGetAction:
return {
...state,
data: action.payload
}
break;
default:
// the dispatched action is not in this reducer, return the state unchanged
return state;
}
}
restApi.js file:
import {customerbyidGetAction} from '../../redux/actions/restActions'
const URL = "https://***"
export default function customerbyidGet() {
return dispatch => {
console.log("not called")
dispatch(customerbyidGetAction());
fetch(URL + 'customer/byid/Get',{
method:'POST',
headers:{
'Accept': 'application/json',
'Content-Type': 'application/json',
'token':1234
},
body: JSON.stringify({'customerId': 1})
})
.then(res => {
const r = res.json();
return r;
})
.then(res => {
if(res.error) {
throw(res.error);
}
dispatch(customerbyidGetAction(res));
return res;
})
.catch(error => {
dispatch(customerbyidGetAction(error));
})
}
}
export default apis;
inside my Component:
import React from 'react';
import { Helmet } from 'react-helmet';
import Grid from '#material-ui/core/Grid';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { withStyles } from '#material-ui/core/styles';
import PropTypes from 'prop-types';
import {
Help, InsertDriveFile,
MonetizationOn,
Person,
PersonPin,
RemoveRedEye
} from '#material-ui/icons';
import { Link } from 'react-router-dom';
import CounterWidget from '../../../components/Counter/CounterWidget';
import colorfull from '../../../api/palette/colorfull';
import styles from '../../../components/Widget/widget-jss';
import PapperBlock from '../../../components/PapperBlock/PapperBlock';
import {customerbyidGetAction} from '../../../redux/actions/restActions';
class Panelclass extends React.Component {
componentDidMount(){
const {customerbyidGet} = this.props;
customerbyidGet()
}
render() {
const { classes } = this.props;
return (
<div>
Hi
</div>
);
}
}
Panelclass.propTypes = {
classes: PropTypes.object.isRequired,
customerbyidGet: PropTypes.func.isRequired,
};
// const mapStateToProps = state => ({
// customers: customerbyidGet(state),
// })
const mapDispatchToProps = dispatch => bindActionCreators({
customerbyidGet: customerbyidGetAction
}, dispatch)
const Panel = connect(
//mapStateToProps,
null,
mapDispatchToProps
)(Panelclass);
export default withStyles(styles)(Panel);
Your reducer expects payload property in action:
function reducer(state = initialImmutableState, action) {
switch (action.type) {
case types.customerbyidGetAction:
return {
...state,
data: action.payload, // Here
};
}
}
So, you need to have that property in action creator:
const customerbyidGetAction = (data) => ({
type: types.customerbyidGetAction,
payload: data, // Here
});
Also, you need to fix the correct action import in component:
import { customerbyidGet } from "../path-to/restApi";
const mapDispatchToProps = (dispatch) =>
bindActionCreators(
{
customerbyidGet,
},
dispatch
);
PS: Today, you should use the Redux Toolkit library which reduces lots of boilerplate code.
First of all define your rest api function like below
export default function customerbyidGet(dispatch) {
return () => {
console.log("not called")
dispatch(customerbyidGetAction());
fetch(URL + 'customer/byid/Get',{
method:'POST',
headers:{
'Accept': 'application/json',
'Content-Type': 'application/json',
'token':1234
},
body: JSON.stringify({'customerId': 1})
})
.then(res => {
const r = res.json();
return r;
})
.then(res => {
if(res.error) {
throw(res.error);
}
dispatch(customerbyidGetAction(res));
return res;
})
.catch(error => {
dispatch(customerbyidGetAction(error));
})
}
}
Then pass it to your mapDispatchToProps function
const mapDispatchToProps = dispatch => bindActionCreators({
customerbyidGetData: customerbyidGet(dispatch)
}, dispatch)
And finally`invoke it in mapDispatchToProps
componentDidMount(){
const {customerbyidGetData} = this.props;
customerbyidGetData()
}
The other two answers are pointing out good points, but missing something very crucial: That ; there is a typo.
const {customerbyidGet} = this.props;
customerbyidGet()
should be
const {customerbyidGet} = this.props.customerbyidGet()

Redux-Saga and createAction from Redux-Toolkit does not run

I have been trying to introduce redux-sagas and redux-toolkit to my project. It seems when I dispatch(fetchTweets(term)) I can see the actions firing off in the Redux DevTools. But when it gets to the saga nothing happens. Not sure how to fix it. Let me know if you have any ideas. Here is the error I am getting. Here is the link to github
file - configureAppStore.js
import { configureStore, getDefaultMiddleware } from '#reduxjs/toolkit';
import reducer from './reducer';
import toast from './middleware/toast.js';
import websocket from './middleware/websocket.js';
import createSagaMiddleware from 'redux-saga';
import tweetSagas from '../saga/tweet.js';
const configureAppStore = () => {
const sagaMiddleware = createSagaMiddleware();
const middlewares = [sagaMiddleware, websocket, toast];
const middleware = [
...getDefaultMiddleware({ thunk: false }),
...middlewares,
];
const store = configureStore({
reducer: reducer,
middleware: middleware,
});
sagaMiddleware.run(tweetSagas);
return store;
};
export default configureAppStore;
file - saga/tweet.js
import { takeEvery, call, put, fork } from 'redux-saga/effects';
import axios from 'axios';
import * as actions from '../store/action/saga.js';
const port = process.env.REACT_APP_PORT;
const hostname = process.env.REACT_APP_LOCALHOST;
const baseURL = `http://${hostname}:${port}`;
function api({ dispatch }) {
return function (next) {
return function* (action) {
if (action.type !== actions.sagaApiCallBegan.type) return next(action);
next(action); // 'sagaApiCallBegan' to show in redux dev tools
const { url, method, onSuccess, onError } = action.payload;
try {
const response = yield call(
async () =>
await axios.request({
baseURL: baseURL,
url,
method,
})
);
if (onSuccess) yield put({ type: onSuccess, payload: response.data });
} catch (error) {
if (onError) yield put({ type: onError, payload: error });
}
};
};
}
function* watchApi() {
yield takeEvery(actions.sagaApiCallBegan.type, api);
}
export default function* tweetSagas() {
yield fork(watchApi);
}
file- store/tweets.js
import { createSlice } from '#reduxjs/toolkit';
import {
sagaApiCallBegan,
sagaApiCallSuccess,
sagaApiCallFailed,
} from './action/saga';
import { webSocketCallBegan, webSocketCallFailed } from './action/websocket.js';
import { normalize } from 'normalizr';
import { tweetSchema } from '../store/Schema/tweet.js';
const initialState = () => ({
byTweetId: {},
byUserId: {},
allTweetIds: [],
});
// action, actionTypes and reducer
const slice = createSlice({
name: 'tweets',
initialState: initialState(),
// reducers
reducers: {
tweetAdded: (state, action) => {
const { entities, result } = normalize(action.payload, tweetSchema);
Object.assign(state.byTweetId, entities.byTweetId);
Object.assign(state.byUserId, entities.byUserId);
state.allTweetIds.push(result);
},
tweetStoreReseted: (state) => initialState(),
},
});
export const { tweetAdded, tweetStoreReseted } = slice.actions;
export default slice.reducer;
// Action creators
export const fetchTweets = (term) =>
sagaApiCallBegan({
url: `/setsearchterm/${term}`,
method: 'get',
onSuccess: sagaApiCallSuccess.type,
onError: sagaApiCallFailed.type,
});
export const fetchTweetsPause = () =>
sagaApiCallBegan({
url: '/pause',
method: 'GET',
onSuccess: sagaApiCallSuccess.type,
onError: sagaApiCallFailed.type,
});
export const getTweet = (message) =>
webSocketCallBegan({
message: message,
onSuccess: tweetAdded.type,
onError: webSocketCallFailed.type,
});
file - action/saga.js
import { createAction } from '#reduxjs/toolkit';
export const sagaApiCallBegan = createAction('saga/apiCallBegan');
export const sagaApiCallSuccess = createAction('saga/apiCallSuccess');
export const sagaApiCallFailed = createAction('saga/apiCallFailed');
Here is the solution
file - saga/tweet.js
import { takeEvery, call, put, fork } from 'redux-saga/effects';
import axios from 'axios';
import * as actions from '../store/action/saga.js';
const port = process.env.REACT_APP_PORT;
const hostname = process.env.REACT_APP_LOCALHOST;
const baseURL = `http://${hostname}:${port}`;
const fetchApi = async ({ baseURL, url, method }) =>
await axios.request({
baseURL: baseURL,
url: url,
method: method,
});
function* api(action) {
const { url, method, onSuccess, onError } = action.payload;
const options = {
baseURL: baseURL,
url: url,
method: method,
};
try {
const response = yield call(fetchApi, options);
if (onSuccess)
yield put({
type: onSuccess,
payload: response.data,
});
} catch (error) {
if (onError) yield put({ type: onError, payload: error });
}
}
function* watchApi() {
yield takeEvery(actions.sagaApiCallBegan.type, api);
}
export default function* tweetSagas() {
yield fork(watchApi);
}

Redux state from actions and reducers does not show in mapStateToProps

I have an actions file called menu that uses axios to get JSON data from a backend API. I then have reducers which are called in a MainMenu.js file which gets the data. All of the data is shown in the Redux devtools. However I am only able to access the data I have in intitialState. There should be an array of three items but it only shows the first one in intitialState. The data from the reducers does not show up when I try to console.log it. So obviously I would not be able to render it to the DOM.
actions/menu.js
import { ADD_PRODUCT, GET_PRODUCTS, GET_PRODUCT } from './types';
import axios from 'axios';
export const getProducts = () => (dispatch) => {
axios
.get('http://localhost:8080/menu')
.then((response) => {
// console.log(response);
const data = response.data;
dispatch({
type: GET_PRODUCTS,
payload: { data },
});
})
.catch((err) => {
console.log(err);
});
};
export const getProduct = (product) => (dispatch) => {
axios
.get(`http://localhost:8080/menu/${product}`)
.then((response) => {
// console.log(response);
const data = response.data;
dispatch({
type: GET_PRODUCT,
payload: { data },
});
})
.catch((err) => {
console.log(err);
});
};
reducers/index.js
import { combineReducers } from 'redux';
import menu from './menu';
export default combineReducers({ menu });
reducers/menu.js
import { GET_PRODUCTS, GET_PRODUCT } from '../actions/types';
const intitialState = [
{
test: 'Test',
},
];
export default function (state = intitialState, action) {
const { type, payload } = action;
switch (type) {
case GET_PRODUCTS:
return [...state, payload];
case GET_PRODUCT:
return [...state, payload];
default:
return [...state];
}
}
components/MainMenu.js
import React, { Fragment, useEffect } from 'react';
import { connect } from 'react-redux';
import { getProducts, getProduct } from '../actions/menu';
const MainMenu = ({ getProducts, getProduct, menu }) => {
useEffect(() => {
getProducts();
getProduct('b4bc2e28-21a3-47ea-ba3b-6bad40b35504');
console.log(menu);
}, []);
return <Fragment></Fragment>;
};
const mapStateToProps = (state) => ({
menu: state,
});
export default connect(mapStateToProps, { getProducts, getProduct })(MainMenu);
I created a working solution using the ternary operator. First a check is made to see if all of the elements in the array have loaded. If they have not all loaded then a data not loaded div is displayed. Otherwise the data is rendered to the DOM.
import React, { Fragment, useEffect } from 'react';
import { connect } from 'react-redux';
import { getProducts, getProduct } from '../actions/menu';
const MainMenu = ({ getProducts, getProduct, menu }) => {
useEffect(() => {
getProducts();
getProduct('b4bc2e28-21a3-47ea-ba3b-6bad40b35504');
}, []);
if (menu.length <= 2) {
console.log('Data Not Loaded');
} else {
console.log('Data', menu[2].data.name);
}
return (
<Fragment>
<div>{menu.length <= 2 ? <div>Data not loaded</div> : <div>{menu[2].data.name}</div>}</div>
<div>
{menu.length <= 2 ? (
<div>Data not loaded</div>
) : (
<div>
{menu[1].data.map((food) => (
<div>{food.name}</div>
))}
</div>
)}
</div>
</Fragment>
);
};
const mapStateToProps = (state) => ({
menu: state.menu,
});
export default connect(mapStateToProps, { getProducts, getProduct })(MainMenu);

I am getting TypeError: this.props.function is not a function while fetching from an api

I am trying to fetch data from an api, but I am getting an error: TypeError: this.props.getPeople is not a function, while everything looks good through the code, as below:
people-component.js
import React, { Component } from 'react';
import './people-component-styles.css';
import { Container, Row, Col, Card } from 'react-bootstrap';
import { connect } from 'react-redux';
import { getPeople } from '../../actions/index'
import 'react-lazy-load-image-component/src/effects/blur.css';
import 'animate.css/animate.min.css';
export class People extends Component {
componentDidMount() {
// console.log(this.props);
this.props.getPeople();
}
render() {
// console.log(this.props);
return (
<Row className='main'>
hello!
</Row>
);
}
}
const mapStateToProps = state => ({
people: state.people
})
const mapDispatchToProps = dispatch => ({
getPeople: () => dispatch(getPeople())
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(People);
actions/index.js
export const getPeople = () => {
console.log("yes");
return async dispatch => {
const response = await fetch('https://dma.com.eg/api.php?action=getPeople', {
method: 'GET'
})
const json = await response.json();
dispatch({ type: "GET_PEOPLE", payload: json });
}
}
reducers/index.js
const INITIAL_STATE = {
people: []
}
const rootReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case "GET_PEOPLE":
return ({
...state,
people: state.people.concat(action.payload)
})
default:
return state;
}
};
export default rootReducer
I was importing People component as name while it's exported as default, thanks to #Brian Thompson .. it's fixed.

Not able to access response object via this.props | React/ Redux

When I try to access to the response object in my component it doesnt throw me an error but it wont either print. I do get access to the response in the component but thats it, i cant actually print something.
Actions File
import axios from 'axios';
import { FETCH_USERS, FETCH_USER } from './types';
const BASE_URL = "http://API_URL/endpoint/"
export function fetchUsers(id,first_name, last_name, dob) {
const request = axios.post(`${BASE_URL}member-search?patientId=${id}&firstName=${first_name}&lastName=${last_name}&dateOfBirth=${dob}&length=10`).then(response => { return response; })
return {
type: FETCH_USERS,
payload: request
};
}
export function fetchUser(id) {
const request = axios.get(`${BASE_URL}members/${id}/summary/demographics`).then(response => { return response; })
return{
type: FETCH_USER,
payload: request
};
}
My reducer file
import _ from 'lodash';
import {
FETCH_USERS, FETCH_USER
} from '../actions/types';
export default function(state = [], action) {
switch (action.type) {
case FETCH_USER:
return { ...state, [action.payload.data.member.id]: action.payload.data.member };
// return [ action.payload.data.member, ...state ];
case FETCH_USERS:
return _.mapKeys(action.payload.data.searchResults, 'id');
}
return state;
}
And finally my component where Im trying to render some results of the response.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { fetchUser } from '../actions';
class PatientWrapper extends Component{
componentDidMount() {
const { id } = this.props.match.params;
this.props.fetchUser(id);
}
render(){
const { user } = this.props;
console.log('this.props response: ',user);
if(!user){
return <div>loading...</div>;
}
return(
<div>
Name: {user.firstName}
Last Name: {user.lastName}
</div>
)
}
}
function mapStateToProps({ users }, ownProps) {
// return { users };
return { user: users[ownProps.match.params.id] };
}
export default connect (mapStateToProps, { fetchUser })(PatientWrapper);
I uploaded a Screenshot img of the response : http://prntscr.com/fbs531
What is wrong with my code?
The issue is that in fetchUser action you use a Promise and return it in payload field. This promise does not contain any information you need like response data. So to fix the issue you need to dispatch action only when response is retrieved (e.g. in then success callback).
To implement it you need to pass mapDispatchToProps in the second argument in connect function for your component and pass dispatch function to your action:
function mapDispatchToProps(dispatch) {
return {
fetchUser: id => fetchUser(id, dispatch)
}
}
Then in the action just do the following
function fetchUser(id, dispatch) {
const request = axios.get(`${BASE_URL}/${id}`)
.then(response => dispatch({
type:FETCH_USER,
payload: response
}));
}
For complete example see JSFiddle

Categories

Resources