I'm trying to get data in nextjs with redux-saga. But my data wont show, then i debug in every file, and finally found that the data is successfully fetched but, the state wont updated. I've been follow the docs and dont know which part that caused the issue. Thanks for any help
Default State
{
status: 'init',
data: [],
error: null,
}
Saga
export function* getCars() {
try {
const options = {
url: `http://localhost/some-endpoint`,
method: 'GET',
}
yield put({
type: ACTION_TYPES.CARS.LOAD,
});
const response = call(axios, options);
if (response.data) {
const { data, status } = response;
yield put({
type: ACTION_TYPES.CARS.RES,
payload: { data, status },
});
} else {
yield put({
type: ACTION_TYPES.CARS.ERR,
error: response.status,
});
}
} catch (err) {
yield put({
type: ACTION_TYPES.CARS.ERR,
error: err,
});
}
}
export default function* combineSaga() {
yield takeEvery(ACTION_TYPES.CARS.GET, getCars);
}
Reducer
const defaultState = DEFAULT_STATE.CARS;
function reducer(state = defaultState, action) {
switch (action.type) {
case ACTION_TYPES.CARS.LOAD:
return {
...state,
status: 'loading',
};
case ACTION_TYPES.CARS.RES:
return {
...state,
data: action.payload.data,
status: 'success',
};
case ACTION_TYPES.CARS.ERR:
return {
...state,
status: 'error',
error: action.error,
};
default:
return state;
}
}
export default reducer;
Store Configuration
const makeStore = () => {
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
rootReducer,
applyMiddleware(sagaMiddleware),
)
store.sagaTask = sagaMiddleware.run(rootSaga)
return store
}
export default createWrapper(makeStore);
App.js
function MyApp({ Component, pageProps }) {
return (
<div>
<Component {...pageProps} />
</div>
);
}
MyApp.getInitialProps = async appContext => {
const { Component, ctx } = appContext;
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return { pageProps };
}
export default wrapper.withRedux(withReduxSaga(MyApp));
Index.js
function Home({ storeCars }) {
useEffect(() => {
//always show default state
console.log(storeCars);
}, [storeCars.status]);
return <div>Home</div>;
}
Home.getInitialProps = async props => {
const { store } = props;
store.dispatch({
type: ACTION_TYPES.CARS.GET,
payload: {},
});
return {};
}
export default connect(state => state)(Home);
you need 2 actions
getDataFromServer to be used in Home component
setDataToRedux to be used in saga and action type in reducer
Related
Redux Saga does not work after any error. It totally stops working and need to refresh the page. Working well when does not generate any error.
I want whether any error occurred the page should running if click the process again and fetch data from API.
If anyone gives any suggestion/solution, please.
My axios call as like below,
export async function getWithPagination(pageNumber, pageSize, searchObject) {
try {
const response = await axios.get(
uri.ApiBaseUrl + "Voucher/GetWithPagination",
{
params: {
...searchObject,
pageNumber,
pageSize,
},
}
);
return { response };
} catch (error) {
return { error };
}
}
export async function createVoucher(voucher) {
console.log("createVoucher service=> ", voucher);
try {
const response = await axios.post(uri.ApiBaseUrl + "Voucher", voucher, {});
return { response };
} catch (error) {
return { error };
}
}
The reducer, action, saga like below
import { put, takeLatest, call } from "redux-saga/effects";
import {
getWithPagination,
createVoucher
} from "../../services-crud/accounting/vouchersCrud";
export const reducer = (state = voucherState, action) => {
case actionTypes.VoucherGetRequested: {
return {
...state,
voucherGridObj: {
paging: {},
links: {},
lists: [],
},
isGridLoading: true,
};
}
case actionTypes.VoucherGetSucceed: {
return { ...state, voucherGridObj: action.data, isGridLoading: false };
}
case actionTypes.VoucherGetFailed: {
return { ...state, isGridLoading: false };
}
case actionTypes.VoucherCreateRequested: {
return { ...state };
}
case actionTypes.VoucherCreateSucceed: {
return {
...state,
voucherObj: { ...voucherInit },
voucherDetailsList: [],
voucherGridObj: {
...state.voucherGridObj,
lists: [{ ...action.data.voucher }, ...state.voucherGridObj.lists],
},
isSuccess: false,
isDataSyncNeeded: true,
};
}
case actionTypes.VoucherCreateFailed: {
return { ...state, isSuccess: false, isDataSyncNeeded: false };
}
}
export const actions = {
voucherGetRequested: (pageNumber, pageSize, searchObject) => ({
type: actionTypes.VoucherGetRequested,
pageNumber: pageNumber,
pageSize: pageSize,
searchObject: searchObject,
}),
voucherGetSucceed: (data) => ({
type: actionTypes.VoucherGetSucceed,
data: data,
}),
voucherGetFailed: () => ({ type: actionTypes.VoucherGetFailed }),
voucherCreate: (voucherObj, voucherImage, gurdianImage) => ({
type: actionTypes.VoucherCreate,
voucherObj: voucherObj,
voucherImage: voucherImage,
gurdianImage: gurdianImage,
}),
voucherCreateRequested: () => ({
type: actionTypes.VoucherCreateRequested,
}),
voucherCreateSucceed: (data) => ({
type: actionTypes.VoucherCreateSucceed,
data: data,
}),
voucherCreateFailed: () => ({
type: actionTypes.VoucherCreateFailed,
}),
};
export function* saga() {
try {
yield takeLatest(
actionTypes.VoucherGetRequested,
function* voucherRequested(action) {
const { response, error } = yield call(() =>
getWithPagination(
action.pageNumber,
action.pageSize,
action.searchObject
)
);
if (response) {
yield put(actions.voucherGetSucceed(response.data));
} else {
NotificationMessage(errorResponseProcess(error.response));
yield put(actions.voucherGetFailed(error));
}
}
);
} catch (error) {
NotificationMessage(errorResponseProcess(error.response));
yield put(actions.voucherGetFailed(error));
}
try {
yield takeLatest(
actionTypes.VoucherCreate,
function* createsvoucher(action) {
yield put(actions.voucherCreateRequested());
const { response, error } = yield call(() =>
createVoucher(
action.voucherObj,
action.voucherImage,
action.gurdianImage
)
);
if (response) {
NotificationMessage(response.data.notification);
yield put(actions.voucherCreateSucceed(response.data.data));
yield put(actions.voucherResetFlag());
} else {
NotificationMessage(errorResponseProcess(error.response));
yield put(actions.voucherCreateFailed(error));
}
}
);
} catch (error) {
NotificationMessage(errorResponseProcess(error.response));
yield put(actions.voucherCreateFailed(error));
}
}
The root reducer & store is below
import { all } from "redux-saga/effects";
import createSagaMiddleware from "redux-saga";
import { combineReducers, applyMiddleware, createStore } from "redux";
import * as accountsReportCrud from "./accounts-chart-duck/accountsReportCrudDuck";
import * as vouchersCrud from "./voucher-duck/voucherCrudDuck";
export const rootReducer = combineReducers({
accountsCrud: accountsCrud.reducer,
accountsReportCrud: accountsReportCrud.reducer,
});
export function* rootSaga() {
yield all([
accountsCrud.saga(),
accountsReportCrud.saga(),
...
]);
}
const sagaMiddleware = createSagaMiddleware();
const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(rootSaga);
export default store;
In your root saga, you can spawn the tasks instead of all/fork so that one saga's error doesn't crash all the sagas.
Redux-saga docs on detached fork (spawn):
Uncaught errors from spawned tasks are not bubbled up to the parent.
I think this formation of sagas will does the trick
function* function_name({ payload }){
//code here
}
export function* function_name_sagas() {
yield takeEvery(action_type, function_name);
}
export default function* rootSaga() {
yield all([
fork(function_name_sagas),
]);
}
I am using Redux for state management and saga as a middleware. For some reason my app is in some infinite loop state of calling API endpoint.
This is my actions:
export const GET_USERS = "GET_USERS";
export const getUsers = () => ({
type: GET_USERS,
});
export const GET_USERS_SUCCESS = `${GET_USERS}_SUCCESS`;
export const getUsersSuccess = (data) => ({
type: GET_USERS_SUCCESS,
payload: data,
});
export const GET_USERS_FAIL = `${GET_USERS}_FAIL`;
export const getUsersFail = (error) => ({
type: GET_USERS_FAIL,
payload: error,
});
This is saga:
export function* getUsers$() {
try {
const users = yield getUsersAPI();
yield put(actions.getUsersSuccess(users.data));
} catch (error) {
yield put(actions.getUsersFail(error));
}
}
export default function* () {
yield all([takeLatest(actions.getUsers, getUsers$)]);
}
This is a reducer:
export default (state = initialState(), action) => {
const { type, payload } = action;
switch (type) {
case actions.GET_USERS:
return {
...state,
users: {
...state.users,
inProgress: true,
},
};
case actions.GET_USERS_SUCCESS:
return {
...state,
users: {
inProgress: false,
data: payload,
},
};
case actions.GET_USERS_FAIL:
return {
...state,
users: {
...state.users,
inProgress: false,
error: payload,
},
};
default:
return state;
}
};
And this is a component connected with redux:
const Home = (props) => {
useEffect(() => {
props.getUsers();
console.log('props', props.data);
}, []);
return(
<h1>Title</h1>
);
}
const mapStateToProps = ({
users: {
users: {
data
}
}
}) => ({data})
export default connect(mapStateToProps, {getUsers})(Home);
Why is this happening?
This is due to the fact that you misused the sagas in your example. As with any other effect creator as the first parameter must pass a pattern, which can be read in more detail in the documentation. The first parameter can also be passed a function, but in a slightly different way. View documentation (block take(pattern)).
In your case, you are passing a function there that will return an object
{
type: 'SOME_TYPE',
payload: 'some_payload',
}
Because of this, your worker will react to ALL events that you dispatch.
As a result, you receive data from the server, dispatch a new action to save data from the store. And besides the reducer, your getUsers saga will be called for this action too. And so on ad infinitum.
Solution
To solve this problem, just use the string constant actions.GET_USERS that you defined in your actions.
And your sagas will look like this:
export function* getUsers$() {
try {
const users = yield getUsersAPI();
yield put(actions.getUsersSuccess(users.data));
} catch (error) {
yield put(actions.getUsersFail(error));
}
}
export default function* () {
yield all([takeLatest(actions.GET_USERS, getUsers$)]);
}
This should fix your problem.
isAuthenticated is undefined when i run this code. how can is use isAuthenticated with mapStateProps. if i am use Token `(Token '5302f4340a76cd80a855286c6d9e0e48d2f519cb'} like this then it's working fine but i want Authorized it with props.isAuthenticated anybody know how can i solve this issue?
authAction.js
import axios from 'axios';
import * as actionTypes from './actionTypes';
export const authStart = () => {
return {
type: actionTypes.AUTH_START
}
}
export const authSuccess = token => {
return {
type: actionTypes.AUTH_SUCCESS,
token: token
}
}
export const authFail = error => {
return {
type: actionTypes.AUTH_FAIL,
error: error
}
}
export const logout = () => {
localStorage.removeItem('token');
return {
type: actionTypes.AUTH_LOGOUT
};
}
export const authLogin = (userData) => {
return dispatch => {
dispatch(authStart());
axios.post('http://localhost:8000/rest-auth/login/', userData)
.then(res => {
const token = res.data.key;
localStorage.setItem('token', token);
dispatch(authSuccess(token));
})
.catch(err => {
dispatch(authFail(err))
})
}
}
authReducer.js
import * as actionTypes from '../actions/actionTypes';
import { updateObject } from '../utility';
const initialState = {
isAuthenticated: null,
token: null,
error: null,
loading: false
}
const authStart = (state, action) => {
return updateObject(state, {
isAuthenticated: false,
error: null,
loading: true
});
}
const authSuccess = (state, action) => {
return updateObject(state, {
isAuthenticated: true,
token: action.token,
error: null,
loading: false
});
}
const authFail = (state, action) => {
return updateObject(state, {
error: action.error,
loading: false
});
}
const authLogout = (state, action) => {
return updateObject(state, {
token: null
});
}
export default function (state = initialState, action) {
switch (action.type) {
case actionTypes.AUTH_START: return authStart(state, action);
case actionTypes.AUTH_SUCCESS: return authSuccess(state, action);
case actionTypes.AUTH_FAIL: return authFail(state, action);
case actionTypes.AUTH_LOGOUT: return authLogout(state, action);
default:
return state;
}
}
index.js
import { combineReducers } from 'redux';
import auth from './authReducer'
export default combineReducers({
auth: auth
});
articleList.js
const NewsList = (props) => {
// ...
const fetchItems = async () => {
try {
const config = {
headers: {
'Content-Type': 'application/json',
Authorization: `Token ${props.isAuthenticated}`
}
}
const res = await axios.get(`${process.env.REACT_APP_API_URL}/api/`, config);
setItems(res.data)
setLoading(false);
}
catch (err) {
console.log(`😱 Axios request failed: ${err}`);
}
}
fetchItems()
})
}, [items]);
// ...
}
const mapStateToProps = (state) => {
return {
isAuthenticated: state.auth.token
}
}
export default connect(mapStateToProps)(NewsList)
You need to debug your code. Start by connecting the dots: The output tells you that props.isAuthenticated is undefined. You pass this in from state.auth.token in mapStateToProps():
const mapStateToProps = (state) => {
return {
isAuthenticated: state.auth.token
}
}
So state.auth.token must be undefined also. That's as far as I can get from what you have shown me. You will need to debug further to figure out why. You can use the React Dev Tools to inspect props of your components. You can use Redux Dev Tools to inspect and manipulate the redux state. Check what the value of auth.token is in state. Look where it is supposed to be set and find out why it isn't getting set to a valid value.
Be sure to check this article for tips on how to debug your code.
I'm using redux-saga to fetch an endpoint and want to present it on first page load using useEffect(). But mine is not fetching anything. The screen is blank and reduxDevTools is also not showing anything. I can't understand what did I miss.
My saga:
export function* watcherSaga() {
yield takeLatest("FETCH_TOP_NEWS_REQUEST", workerSaga);}
function fetchTopNews() {
return axios({
method: 'get',
url: 'https://newsapi.org/v2/top-headlines?country=us&apiKey=API_KEY'
});}
function* workerSaga() {
try{
const response = yield call(fetchTopNews);
const news = response.data.articles;
yield put({ type: "FETCH_TOP_NEWS_SUCCESS", news });
}
catch (error) {
yield put({ type: "FETCH_TOP_NEWS_ERROR", error });
}
}
I defined 3 actions:
const initialState = {
fetching: false,
error: null,
news: []
};
const NewsReducer = (state=initialState, action) => {
switch(action.type){
case types.fetchTopNewsRequest:
return { ...state, fetching: true, error: null };
case types.fetchTopNewsSuccess:
return { ...state, fetching: false, news: action.news[0] };
case types.fetchTopNewsError:
return { ...state, fetching: false, news: null, error: action.error };
default:
return state;
}
}
export default NewsReducer;
At last my component, I imported the fetchTopNewsRequest() action here:
const TopHeadline = (props) => {
const { news, getTopNews } = props;
useEffect(() => {
getTopNews();
}, [getTopNews]);
return (
<div className="newsItem">
<h1>Title: {news.title}</h1>
</div>
);}
const mapStateToProps= (state) => {
return {
news: state.news,
};
};
const mapDispatchToProps = (dispatch) => {
return {
getTopNews: () => dispatch( fetchTopNewsRequest() )
};
};
export default connect(mapStateToProps, mapDispatchToProps)(TopHeadline);
I'm trying to fetch only the articles.title.
DevTools shows it's successfully fetching the data:
Buy my states are not updating:
I am using multiple reducers in my project and then combining them with combineReducers() function and have all actions in single file. when i dispatch the action, it is returning me state values to undefined. I think It can't find out because of multiple reducerse. But when i use single reducer file. It is working fine. Can anyone please tell me what the issue.It is how i am combining the reducers.
const rootReducer = combineReducers({
isMobileReducer,
imageSliderReducer
})
and now passing to store, like below:
let store = createStore(rootReducer,applyMiddleware(thunk))
and in frontend how i am accessing state
const mapStateToProps = (state) => ({
images: state.images,
isMobile: state && state.isMobile
})
imageSliderReducer.js
import {
FETCH_IMAGES_BEGIN,
FETCH_IMAGES_SUCCESS,
FETCH_IMAGES_FAILURE
} from '../actions/actionTypes'
const initialState = {
images:[],
error:null
}
const imageSliderReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_IMAGES_BEGIN:
return {...state,error:null}
case FETCH_IMAGES_SUCCESS:
return {...state,images:action.payload.images}
case FETCH_IMAGES_FAILURE:
return {...state,error:action.payload.error,images:[]}
default:
return state
}
}
export default imageSliderReducer;
isMobileReducer.js
import {
OPEN_MENU,
CLOSE_MENU,
SET_DEVICE_TYPE,
} from '../actions/actionTypes'
const initialState = {
isMenuOpen: null,
isMobile: false
}
const isMobileReducer = (state = initialState, action) => {
switch (action.type) {
case OPEN_MENU:
return {...state, isMenuOpen: true}
case CLOSE_MENU:
return {...state, isMenuOpen: false}
case SET_DEVICE_TYPE:
return {...state, isMobile: action.isMobile}
default:
return state
}
}
export default isMobileReducer;
actionCreator.js
import {
OPEN_MENU,
CLOSE_MENU,
SET_DEVICE_TYPE,
FETCH_IMAGES_BEGIN,
FETCH_IMAGES_SUCCESS,
FETCH_IMAGES_FAILURE
} from './actionTypes'
export function openMenu(isMobile) {
return {
type: OPEN_MENU
}
}
export function closeMenu(isMobile) {
return {
type: CLOSE_MENU
}
}
export function setDeviceType (isMobile) {
return {
type: SET_DEVICE_TYPE,
isMobile: isMobile
}
}
export function fetchImages() {
return dispatch => {
dispatch(fetchImagesBegin());
return fetch("https://7344.rio.com/wp-json/customapi/homeslider")
.then(handleErrors)
.then(res => res.json())
.then(json => {
dispatch(fetchImagesSuccess(json.posts));
return json.posts;
})
.catch(error => dispatch(fetchImagesFailure(error)));
};
}
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
export const fetchImagesBegin = () => ({
type: FETCH_IMAGES_BEGIN
});
export const fetchImagesSuccess = images => ({
type: FETCH_IMAGES_SUCCESS,
payload: { images }
});
export const fetchImagesFailure = error => ({
type: FETCH_IMAGES_FAILURE,
payload: { error }
});
Try using this:
const mapStateToProps = (state) => ({
images: state.imageSliderReducer.images,
isMobile: state.isMobileReducer.isMobile
})