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);
}
Related
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()
I am using Redux and Redux Saga for my project of an online shopping website, so this is how I have done:
actions/products.js
export const Types = {
GET_PRODUCTS_REQUEST: 'products/get_products_request',
GET_PRODUCTS_SUCCESS: 'products/get_products_success',
CREATE_PRODUCT_REQUEST: 'products/create_product_request',
};
export const getProductRequest = () => ({
type: Types.GET_PRODUCTS_REQUEST,
});
export const getProductSuccess = ({products}) => ({
type: Types.GET_PRODUCTS_SUCCESS,
payload: {
products,
},
});
export const createProductRequest = ({
name,
price,
description,
productImage,
}) => ({
type: Types.CREATE_PRODUCT_REQUEST,
payload: {
name,
price,
description,
productImage,
},
});
reducers/products.js
import {Types} from '../actions/products';
const INTIAL_STATE = {
products: [],
error: '',
};
export default function products(state = INTIAL_STATE, action) {
switch (action.type) {
case Types.GET_PRODUCTS_SUCCESS: {
return {
...state,
products: action.payload.products,
};
}
default: {
return state;
}
}
}
reducers/index.js
import {combineReducers} from 'redux';
import ProductsReducer from './products';
import OrderReducer from './orders';
export default combineReducers({
products: ProductsReducer,
orders: OrderReducer
});
sagas/products.js
import {takeEvery, call, fork, put, takeLatest} from 'redux-saga/effects';
import * as actions from '../actions/products';
import * as api from '../api/products';
function* getProducts() {
try {
const products = yield call(api.getProducts);
// console.log(products);
yield put(
actions.getProductSuccess({
products,
})
);
} catch (e) {}
}
function* watchGetProductRequest() {
yield takeEvery(actions.Types.GET_PRODUCTS_REQUEST, getProducts);
}
function* createProduct({name, price, description, productImage}) {
try {
yield call(api.createProduct, {
name,
price,
description,
productImage,
});
yield call(getProducts);
console.log('create products');
} catch (e) {
console.log(e, 'create products');
}
}
function* watchCreateNewProductRequest() {
yield takeLatest(actions.Types.CREATE_USER_REQUEST, createProduct);
}
const userSagas = [
fork(watchGetProductRequest),
fork(watchCreateNewProductRequest),
];
export default userSagas;
sagas/index.js
import {combineReducers} from 'redux';
import ProductsReducer from './products';
import OrderReducer from './orders';
export default combineReducers({
products: ProductsReducer,
orders: OrderReducer,
});
api/products
const baseUrl = 'https://shop-test-api.herokuapp.com';
export const getProducts = async () => {
return await (await (await fetch(`${baseUrl}/products`)).json()).docs;
};
export const createProduct = async ({
name,
price,
description,
productImage,
}) => {
return await fetch(`${baseUrl}/products`, {
method: 'POST',
body: {
name,
price,
description,
productImage,
},
headers: {
'content-type': 'application/json; charset=UTF-8',
},
}).json();
};
even I have not import createNewProductRequest in the actions/products.js to the component I want to use, the console log out this error:
The error occurs in the function createProduct of sagas/products.js, I use takeLastest because I use many dispatches (create new product then take the updated product list after new product was created)
and it show me that error
please help me to tackle with it, it means a lot to me.
This is the code repo: https://gitlab.com/Khangithub/redux-saga-multiple-reducers-and-requests
Once again. thank you so much and have a good day
Well, for me my action type was undefined, I changed the original file where I defined the types and forgot to effect the changes in my saga, hence that error. So that might be the same issue with your code. Just check your types to make sure its defined and there's no typos.
Got the same error because I had a duplicate name across sagas
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
Tell me please why i can't to get local data from json in axios.
db.json is at the root of the project, but in the getEvents function it throws error 404.
Help me please
operation.js
import FetchClient from 'app/utils/FetchClient';
import IdsAndByIds from 'app/utils/IdsAndByIds';
import { eventsFetch, setEvents } from './actions';
export const getEvents = () => async (dispatch) => {
try {
const { data } = await FetchClient.get('./db.json');
dispatch(setEvents(IdsAndByIds(data)));
dispatch(eventsFetch(false));
} catch (error) {
console.log(error);
}
};
FetchClient.js
import axios from 'axios';
import { URL_API } from 'app/config'; //localhost:3009
const FetchClient = () => {
const defaultOptions = {
baseURL: URL_API,
method: 'get',
headers: {
'Content-Type': 'application/json'
}
};
const instance = axios.create(defaultOptions);
return instance;
};
export default FetchClient();
actions.js
import * as types from './types';
export const eventsFetch = value => ({
type: types.FETCHING_EVENTS,
payload: value
});
export const setEvents = ({ objById, arrayIds }) => ({
type: types.SET_EVENTS,
payload: {
eventById: objById,
eventsOrder: arrayIds
}
});
I'm working with Open Weather API, but when i send an request for a city data, i get network error.
Here is the action which does the job of getting the resource.
actions/index.js
import axios from "axios";
const ROOT_URL = `https://samples.openweathermap.org/data/2.5/forecast?appid=${API_KEY}`;
export const FETCH_WEATHER = "FETCH_WEATHER";
export function fetchWeather(city) {
const url = `${ROOT_URL}&q=${city},us`;
const request = axios.get(url);
console.log("request:", request);
return {
type: FETCH_WEATHER,
payload: request
};
}
When i press the submit button then i can see error in the mozilla firefox console.
something like this...Error:network error
but i want the city name under the city header...
just like this...
axios.get(url) is returning promise,
conventional way is,
export function fetchWeather(city) {
const url = `${ROOT_URL}&q=${city},us`;
const res = axios.get(url).then(function(res){
console.log("response:", res);
return {
type: FETCH_WEATHER,
payload: res
};
}).catch(err){
console.log(err)
}
}
OR,
Use async/await to get required result.
export async function fetchWeather(city) {
try{
const url = `${ROOT_URL}&q=${city},us`;
const res = await axios.get(url);
console.log("request:", res);
return {
type: FETCH_WEATHER,
payload: res
};
}catch(err){
console.log(err)
}
}
In your actions/index.js put this:
import axios from "axios";
const API_KEY = "YOUR API KEY GOES HERE ...";
const ROOT_URL = `https://api.openweathermap.org/data/2.5/forecast?appid=${API_KEY}&units=metric`;
export const FETCH_WEATHER = 'FETCH_WEATHER';
export function fetchWeather(city) {
const url = `${ROOT_URL}&q=${city}`;
const request = axios.get(url);
return {
type: FETCH_WEATHER,
payload: request,
};
}
In your reducers/reducer_weather.js put this:
import { FETCH_WEATHER } from "../actions/index";
export default function (state = [], action) {
if (action.error) {
return state;
}
switch (action.type) {
case FETCH_WEATHER:
return [action.payload.data, ...state];
}
return state;
}
Additionally, make sure to include your weather reducer inside of you root reducer, for example:
reducers/index.js
import { combineReducers } from "redux";
import WeatherReducer from "./reducer_weather";
const rootReducer = combineReducers({
weather: WeatherReducer
});
export default rootReducer;
You have built your URL wrong way, and you can not query your initial URL, it is just a sample ready for download.
import axios from 'axios';
const ROOT_URL = 'http://api.openweathermap.org/data/2.5/forecast';
export const FETCH_WEATHER = 'FETCH_WEATHER';
export function fetchWeather(city) {
const url = `${ROOT_URL}?q=${city},us&APPID=${API_KEY}`;
const request = axios.get(url);
console.log('request:', request);
return {
type: FETCH_WEATHER,
payload: request
};
}
Regards.