I have useReducer and I have action type for fetching data inside useEffect and dispatch it like this.
function reducer(state, action) {
switch (action.type) {
case 'FETCH_REQUEST':
return { ...state, loading: true, error: '' };
case 'FETCH_SUCCESS':
return { ...state, loading: false, orders: action.payload, error: '' };
case 'FETCH_FAIL':
return { ...state, loading: false, error: action.payload };
default:
return state;
}
}
export default function OrderHistoryScreen() {
const [{ loading, error, orders }, dispatch] = useReducer(reducer, {
loading: true,
orders: [],
error: '',
});
useEffect(() => {
const fetchOrders = async () => {
try {
dispatch({ type: 'FETCH_REQUEST' });
const { data } = await axios.get(`/api/orders/history`);
dispatch({ type: 'FETCH_SUCCESS', payload: data });
} catch (err) {
dispatch({ type: 'FETCH_FAIL', payload: getError(err) });
}
};
fetchOrders();
}, []);
how can I use SWR for this condition SWR cant be called inside useEffect and I want to use SWR to dispatch it to avoid fetching with useEffect
Related
I am using the useEffect hook in a react a component to fire a redux action. This action makes a call to my api, but before this call happens I update the redux state, after the api call I again update the redux state. With the api call removed it works fine but with it included it causes an infinite loop
React component
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { allEntries } from "../../redux/actions/allEntries";
export const EmployeeProfileTime = () => {
const dispatch = useDispatch();
const userId = useSelector((state) => state.userInfoReducer.userId);
const isAdmin = useSelector((state) => state.userInfoReducer.isAdmin);
useEffect(() => {
const userInfo = {
userId,
typeOfId: isAdmin ? "admin_id" : "user_id",
};
dispatch(allEntries(userInfo));
}, [userId, isAdmin, dispatch]);
return <div></div>;
};
import axios from "axios";
export const allEntries = (userInfo) => async (dispatch) => {
try {
dispatch({
type: "SET_USER_INFO_REQUEST",
});
await axios({
method: "get",
url: `userInfo is passed as param`,
}).then((response) => {
console.log(response);
});
dispatch({
type: "SET_USER_INFO_SUCCESS",
});
} catch (error) {
console.log(error);
}
};
Redux reducer
const intitialState = {
firstName: "",
lastName: "",
email: "",
image: "",
userId: "",
companyNumber: "",
companyName: "",
isAdmin: "",
date: "",
weeklyTimeEntries: [],
entriesToDisplay: [],
allEntries: [],
weeklyTime: "",
jobs: [],
isLoading: false,
error: null,
};
const userInfoReducer = (state = intitialState, action) => {
switch (action.type) {
case "SET_USER_INFO":
return {
...state,
firstName: action.payload.firstName,
lastName: action.payload.lastName,
email: action.payload.email,
image: action.payload.image,
userId: action.payload.userId,
companyNumber: action.payload.companyNumber,
isAdmin: action.payload.isAdmin,
date: action.payload.date,
};
case "SET_USER_INFO_REQUEST":
return {
...state,
isLoading: true,
};
case "SET_USER_INFO_SUCCESS":
return {
...state,
isLoading: false,
};
case "SET_USER_INFO_ERROR":
return {
...state,
error: action.error,
};
case "SET_USER_COMPANY_NUMBER":
return {
...state,
companyNumber: action.payload,
};
case "SET_USER_COMPANY_NAME":
return {
...state,
companyName: action.payload,
};
case "SET_USER_IS_ADMIN":
return {
...state,
isAdmin: 1,
};
case "SET_USER_IS_NOT_ADMIN":
return {
...state,
isAdmin: 0,
};
case "SET_USER_IMAGE":
return {
...state,
image: action.payload,
};
case "SET_USER_WEEKLY_TIME":
return {
...state,
weeklyTimeEntries: action.payload,
};
case "SET_USER_TOTAL_TIME":
return {
...state,
weeklyTime: action.payload,
};
case "SET_USER_DISPLAY_ENTRIES":
return {
...state,
entriesToDisplay: action.payload,
};
case "SET_USER_JOBS":
return {
...state,
jobs: action.payload,
};
case "SET_USER_ALL_ENTRIES":
return {
...state,
allEntries: action.payload,
};
default:
return state;
}
};
export default userInfoReducer;
My component shows incorrect value from store.
The request was successful and after request I've got fetching: false and contacts list in Redux state. But component doesn't update, console.log(fetching) shows true and UI shows "Loading".
In Redux dev tools there's only 'GET_CONTACTS_SUCCESS' action.
Why component doesn't update, where can be the problem? Seems like I do everything as in this answer https://stackoverflow.com/a/64614396/12994741
Component:
const { contacts, fetching } = useSelector(state => state.contact);
useEffect(() => {
dispatch({ type: 'GET_CONTACTS_REQUEST' });
}, [dispatch])
console.log(fetching);
return <div>
{fetching
? <p> Loading </p>
: (contacts.map(contact => <div key={contact.id}>{contact.id}</div>)}
</div>
Saga:
function* getContactsSaga() {
try {
const response = yield call('...');
if (response && response.data) {
yield put({
type: 'GET_CONTACTS_SUCCESS',
items: response.data,
})
} else {
yield put({
type: 'GET_CONTACTS_FAILED',
message: 'Data Access Error. Please Try again later',
});
}
} catch (e) {
yield put({
type: 'GET_CONTACTS_FAILED',
message: e.response.statusText,
});
}
}
function* contactSaga() {
yield takeLatest('GET_CONTACTS_REQUEST', getContactsSaga);
}
Reducer:
const initialState = {
error: '',
fetching: false,
contacts: [],
};
const ContactReducer = (state = initialState, action) => {
switch (action.type) {
case 'GET_CONTACTS_REQUEST':
return {
...state,
fetching: true,
};
case 'GET_CONTACTS_SUCCESS':
return {
...state,
error: '',
fetching: false,
contacts: action.items,
};
case 'GET_CONTACTS_FAILED':
return {
...state,
fetching: false,
error: action.message,
};
default:
return state;
}
}
I'm struggling with react-redux variable for hours...hope someone can help.
The conditional returns to me that the variable order.name is not defined, although everything goes as it should in the reducer and action.
When isLoading === true, it continues rendering {order.name} and I know it is not defined at that point because it takes some time. At that time i set Loader to do it's job..
So it’s not clear to me why he continues to render even though there’s a conditional one that shouldn’t allow it... until isLoading === false.
Here is console.log of orderDetails
import { getOrderDetailsAction } from "../actions/orderAction";
const OrderScreen = ({ match }) => {
const orderId = match.params.id;
const dispatch = useDispatch();
useEffect(() => {
dispatch(getOrderDetailsAction(orderId));
}, [dispatch, orderId]);
const orderDetails = useSelector((state) => state.orderDetails);
const { order, isLoading } = orderDetails;
return isLoading ? <Loader /> : <>{order.name}</>;
};
export default OrderScreen;
Reducer
export const orderDetailsReducers = (
state = { isLoading: true, orderItems: [], shippingAddress: {} },
action
) => {
switch (action.type) {
case ORDER_DETAILS_REQUEST:
return {
...state,
isLoading: true,
};
case ORDER_DETAILS_SUCCESS:
return {
isLoading: false,
order: action.payload,
};
case ORDER_DETAILS_FAILED:
return {
isLoading: false,
error: action.payload,
};
default:
return { state };
}
};
Action
export const getOrderDetailsAction = (id) => async (dispatch, getState) => {
try {
dispatch({
type: ORDER_DETAILS_REQUEST,
});
//Getting TOKEN
const {
userLogin: { userInfo },
} = getState();
//Passing TOKEN
const config = {
headers: {
"auth-token": `${userInfo.token}`,
},
};
const { data } = await axios.get(`/api/orders/${id}`, config);
dispatch({
type: ORDER_DETAILS_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: ORDER_DETAILS_FAILED,
payload: error.response.data.msg,
});
}
};
Check redux action for how isLoading state is changed from redux dev tools
Check reducer name -> state.orderDetails (does this exists ?)
const orderDetails = useSelector((state) => state.orderDetails);
Also, we can correct this
state = { isLoading: true, orderItems: [], shippingAddress: {}, order: {} }
// return entire state and not just isLoading and order
case ORDER_DETAILS_SUCCESS:
return {
...state, <--------
isLoading: false,
order: action.payload,
};
case ORDER_DETAILS_FAILED:
return {
...state, <---------
isLoading: false,
error: action.payload,
};
The problem is that when on of my reducer updates its own state, it also updates the state of another reducer.
//authActions.js
export const authActions = {
login: (props) => dispatch => {
// This will make sure the loading spinner will appear.
dispatch({
type: POST_LOGIN_PENDING,
payload: null
})
// make request to login user
axios.post(LOGIN_ENDPOINT, {
email: props.email,
password: props.password
}).then(res => dispatch({
type: POST_LOGIN_FULFILLED,
payload: res.data
})
).catch( () => dispatch({
type: POST_LOGIN_REJECTED,
payload: null
}))
},
logout: () => dispatch => {
dispatch({
type: LOGOUT,
payload: null
})
},
// authReducer.js
export const initialState = {
token: "",
userRole: "",
isLoading: false,
loginFailed: false,
isAuthenticated: false,
}
export function authReducer(state = initialState, action) {
switch (action.type) {
case POST_LOGIN_PENDING:
return {
...state,
isLoading: true,
}
case POST_LOGIN_FULFILLED:
return {
...state,
token: action.payload.token,
userRole: action.payload.userRole,
loginFailed: false,
isAuthenticated: true,
isLoading: false,
}
case POST_LOGIN_REJECTED:
return {
...state,
loginFailed: true,
isLoading: false,
}
// studentActions.js
export const studentActions = {
getAllStudents: props => dispatch => {
dispatch({
type: GET_ALL_STUDENTS_PENDING,
payload: null,
})
axios.get(STUDENTS_ENDPOINT, {
headers: {
'Authorization': `Bearer ${props.token}`
}
})
.then(res =>
dispatch({
type: GET_ALL_STUDENTS_FULFILLED,
payload: res.data
}))
.catch(err => dispatch({
type: GET_ALL_STUDENTS_FULFILLED,
payload: err
}))
},
// studentReducer.js
export const initialState = {
students: [],
err: "",
isLoading: false,
}
export function studentReducer(state = initialState, action) {
switch (action.type) {
case GET_ALL_STUDENTS_PENDING:
return {
...state,
isLoading: true,
}
case GET_ALL_STUDENTS_FULFILLED:
return {
...state,
students: action.payload,
isLoading: false,
}
case GET_ALL_STUDENTS_REJECTED:
return {
...state,
err: action.payload,
isLoading: false,
}
case DELETE_STUDENT_BY_ID_FULFILLED:
return state
default:
return state
}
}
When a user logs in and the POST_LOGIN_FULFILLED applies. I would expect only the initialstate of the authReducer to be updated, but when inspect with the redux devtools I can see that that the array "studens" which is part of the initialstate of the studentReducer also is updated. From what I understand this should not be possible.
After the user has logged in the students array is filled: (From redux devtools)
student: {
students: [] => {....some stuff}
isLoading: true => false
}
By reading you comments it looks like that GET_ALL_STUDENTS_FULFILLED refers to POST_LOGIN_FULFILLED . This must be the reason why your students array is updated. Change
export const GET_ALL_STUDENTS_PENDING = 'POST_LOGIN_PENDING';
export const GET_ALL_STUDENTS_REJECTED = 'POST_LOGIN_REJECTED';
export const GET_ALL_STUDENTS_FULFILLED = 'POST_LOGIN_FULFILLED';
to
export const GET_ALL_STUDENTS_PENDING = 'GET_ALL_STUDENTS_PENDING ';
export const GET_ALL_STUDENTS_REJECTED = 'GET_ALL_STUDENTS_REJECTED ';
export const GET_ALL_STUDENTS_FULFILLED = 'GET_ALL_STUDENTS_FULFILLED ';
Action types should be unique or else it might get triggered by some other action
In the below jhipster generated code, how the pending, success and failure of actions are being triggered? For each action type we use, it is being appended with _PENDING or _SUCCESS or _FAILURE and I'm not able to figure out where and how it happens.
As I see pending, success and failure states are being handled by the reducer I don't understand when and where those actions are being triggered.
For example in the code below, the first action has type ACTION_TYPES.FETCH_MEDICINE_LIST = 'medicine/FETCH_MEDICINE_LIST'.
The actions that actually gets triggered are medicine/FETCH_MEDICINE_LIST_PENDING, medicine/FETCH_MEDICINE_LIST_SUCCESS, medicine/FETCH_MEDICINE_LIST_FAILURE when medicine/FETCH_MEDICINE_LIST action gets trigger. Where and how the Api state actions are being triggered?
import { ICrudGetAction, ICrudGetAllAction, ICrudPutAction, ICrudDeleteAction } from 'react-jhipster';
import { cleanEntity } from 'app/shared/util/entity-utils';
import { REQUEST, SUCCESS, FAILURE } from 'app/shared/reducers/action-type.util';
import { IMedicine, defaultValue } from 'app/shared/model/medicine.model';
export const ACTION_TYPES = {
FETCH_MEDICINE_LIST: 'medicine/FETCH_MEDICINE_LIST',
FETCH_MEDICINE: 'medicine/FETCH_MEDICINE',
CREATE_MEDICINE: 'medicine/CREATE_MEDICINE',
UPDATE_MEDICINE: 'medicine/UPDATE_MEDICINE',
DELETE_MEDICINE: 'medicine/DELETE_MEDICINE',
RESET: 'medicine/RESET'
};
const initialState = {
loading: false,
errorMessage: null,
entities: [] as ReadonlyArray<IMedicine>,
entity: defaultValue,
updating: false,
updateSuccess: false
};
export type MedicineState = Readonly<typeof initialState>;
// Reducer
export default (state: MedicineState = initialState, action): MedicineState => {
switch (action.type) {
case REQUEST(ACTION_TYPES.FETCH_MEDICINE_LIST):
case REQUEST(ACTION_TYPES.FETCH_MEDICINE):
return {
...state,
errorMessage: null,
updateSuccess: false,
loading: true
};
case REQUEST(ACTION_TYPES.CREATE_MEDICINE):
case REQUEST(ACTION_TYPES.UPDATE_MEDICINE):
case REQUEST(ACTION_TYPES.DELETE_MEDICINE):
return {
...state,
errorMessage: null,
updateSuccess: false,
updating: true
};
case FAILURE(ACTION_TYPES.FETCH_MEDICINE_LIST):
case FAILURE(ACTION_TYPES.FETCH_MEDICINE):
case FAILURE(ACTION_TYPES.CREATE_MEDICINE):
case FAILURE(ACTION_TYPES.UPDATE_MEDICINE):
case FAILURE(ACTION_TYPES.DELETE_MEDICINE):
return {
...state,
loading: false,
updating: false,
updateSuccess: false,
errorMessage: action.payload
};
case SUCCESS(ACTION_TYPES.FETCH_MEDICINE_LIST):
return {
...state,
loading: false,
entities: action.payload.data
};
case SUCCESS(ACTION_TYPES.FETCH_MEDICINE):
return {
...state,
loading: false,
entity: action.payload.data
};
case SUCCESS(ACTION_TYPES.CREATE_MEDICINE):
case SUCCESS(ACTION_TYPES.UPDATE_MEDICINE):
return {
...state,
updating: false,
updateSuccess: true,
entity: action.payload.data
};
case SUCCESS(ACTION_TYPES.DELETE_MEDICINE):
return {
...state,
updating: false,
updateSuccess: true,
entity: {}
};
case ACTION_TYPES.RESET:
return {
...initialState
};
default:
return state;
}
};
const apiUrl = 'api/medicines';
// Actions
export const getEntities: ICrudGetAllAction<IMedicine> = (page, size, sort) => ({
type: ACTION_TYPES.FETCH_MEDICINE_LIST,
payload: axios.get<IMedicine>(`${apiUrl}?cacheBuster=${new Date().getTime()}`)
});
export const getEntity: ICrudGetAction<IMedicine> = id => {
const requestUrl = `${apiUrl}/${id}`;
return {
type: ACTION_TYPES.FETCH_MEDICINE,
payload: axios.get<IMedicine>(requestUrl)
};
};
export const createEntity: ICrudPutAction<IMedicine> = entity => async dispatch => {
const result = await dispatch({
type: ACTION_TYPES.CREATE_MEDICINE,
payload: axios.post(apiUrl, cleanEntity(entity))
});
dispatch(getEntities());
return result;
};
export const updateEntity: ICrudPutAction<IMedicine> = entity => async dispatch => {
const result = await dispatch({
type: ACTION_TYPES.UPDATE_MEDICINE,
payload: axios.put(apiUrl, cleanEntity(entity))
});
dispatch(getEntities());
return result;
};
export const deleteEntity: ICrudDeleteAction<IMedicine> = id => async dispatch => {
const requestUrl = `${apiUrl}/${id}`;
const result = await dispatch({
type: ACTION_TYPES.DELETE_MEDICINE,
payload: axios.delete(requestUrl)
});
dispatch(getEntities());
return result;
};
export const reset = () => ({
type: ACTION_TYPES.RESET
});
The actions are triggered by redux-promise-middleware.
For an action FOO with an asynchronous payload, redux-promise-middleware will dispatch 3 actions:
FOO_PENDING, immediately
FOO_FULFILLED, once the promise is settled
FOO_REJECTED, if the promise is rejected
REQUEST, SUCCESS and FAILURE are just 3 simple functions in JHispter to facilitate the use of redux-promise-middleware.
export const REQUEST = actionType => `${actionType}_PENDING`;
export const SUCCESS = actionType => `${actionType}_FULFILLED`;
export const FAILURE = actionType => `${actionType}_REJECTED`;