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
Related
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
I'm trying to update the component after a user inputs new data.
Currently on componetDidMount() I call my reducer to fetch data from an API and return it to the component. That works. But when the user updates add a new form and it gets saved in the API, I call the API and the store updates (both redux and console log confirmed this) but the component does not update.
I'm think this could be an aysnc problem but I'm not certain.
Store:
type KnownAction = RecievedInvoicesAction | RequestInvoicesAction | RefreshInvoices;
export const actionCreators = {
requestInvoices: (): AppThunkAction<KnownAction> => (dispatch, getState) => {
const appState = getState();
if (appState && appState.invoices && appState.invoices.isLoading) {
fetch('https://localhost:44304/api/invoices')
.then((response) => response.json())
.then((data) => {
dispatch({
type: 'RECIEVED_INVOICES',
invoices: data,
isLoading: false,
});
toast.success('Invoices loaded đź‘Ť', {
position: "bottom-right",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
})
});
dispatch({ type: 'REQUEST_INVOICES', isLoading: true});
}
},
refreshInvoices: (): AppThunkAction<KnownAction> => (dispatch) => {
fetch('https://localhost:44304/api/invoices')
.then((response) => response.json())
.then((data) => {
console.log(data);
dispatch({
type: 'REFRESH_INVOICES',
invoices: data,
isLoading: false,
});
});
dispatch({ type: 'REQUEST_INVOICES', isLoading: true});
}
};
// REDUCER
const unloadedState: InvoiceState = { isLoading: true, invoices: [] };
export const reducer: Reducer<InvoiceState> = (
state: InvoiceState | undefined,
incomingAction: Action
): InvoiceState => {
if (state === undefined) {
return unloadedState;
}
const action = incomingAction as KnownAction;
switch (action.type) {
case 'REQUEST_INVOICES' :
return Object.assign({}, state, {
isLoading: action.isLoading
})
case 'RECIEVED_INVOICES':
return Object.assign({}, state, {
invoices: action.invoices,
isLoading: action.isLoading
})
case 'REFRESH_INVOICES':
return Object.assign({}, state, {
invoices: action.invoices,
isLoading: action.isLoading
})
default:
return state;
}
};
Main Component:
class Home extends React.Component<HomeProps, State> {
constructor(SearchInvoiceProps : HomeProps) {
super(SearchInvoiceProps);
this.state = {
queryText : '',
filterBy : 'all',
orderBy : 'asc',
order : 'invoiceDate',
error : '',
invoicesArr : []
}
}
componentDidMount() {
this.ensureDataFetched();
this.setState({
invoicesArr : this.props.invoices
})
}
ensureDataFetched = () => {
this.props.requestInvoices();
}
...
}
export default connect(
(state: ApplicationState) => state.invoices,
InvoiceStore.actionCreators
)(Home as any);
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,
};
I received state nested in state and it has many states rendered.
You see from my photo below.
Here is my LoginReducer
import {
LOGIN_SUCCESS,
LOGIN_FAIL,
USER_LOADED,
AUTH_ERROR
} from '../actions/typeName';
const initialState = {
token: localStorage.getItem('token'),
isAuthenticated: null,
loading: true,
user: null
}
const loginReducer = (state = initialState, action) => {
const {
type,
payload
} = action;
switch (type) {
case USER_LOADED:
return {
...state,
isAuthenticated: true,
loading: true,
user: payload
};
case LOGIN_SUCCESS:
localStorage.setItem('token', payload.token)
return {
...state,
isAuthenticated: true,
loading: false
}
case AUTH_ERROR:
case LOGIN_FAIL:
localStorage.removeItem('token')
return {
...state,
token: null,
isAuthenticated: false,
loading: true
}
default:
return {
state
}
}
}
export default loginReducer;
And here is myaction.
export const LoginAction = (email, password) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
}
const body = JSON.stringify({email, password});
try {
const res = await axios.post('/api/admin/login', body, config);
dispatch({
type: LOGIN_SUCCESS,
payload: res.data
});
dispatch(loadUser());
}catch(err){
const errors = err.response.data.errors;
dispatch({
type: LOGIN_FAIL,
});
if(errors) {
errors.forEach(error => {
dispatch(NotificationAction(error.msg, 'error'));
})
}
}
}
If it is not normal and how to prevent this? Thank you so much.
The default case is not handled correctly in your reducer. Use
switch (type) {
...
default:
return state;
}
instead. If you return { state } the current state will be wrapped in a new object with an attribute state, that's what you see in the inspector.
Its not normal, usually we will get state and the reducers defined in it. In your case
state with the following reducers
loginReducer
notificationReducer
listUsersReducer and so on.
Can u please provide the respective action file. I think the way u are passing inside the switch() should be action.type instead of type itself.