Programming is weird, if you think not then check this case 🤣, I'm using createSlices as Redux and I have two slices with their own states.
First one is orderSlice:
export const orderSlice = createSlice({
name: 'order',
initialState: {
order: null,
message: null,
isLoading: true,
}
})
While the second slice is ordersSlice:
export const orderSlice = createSlice({
name: 'orders',
initialState: {
orders: null,
message: null,
isLoading: true,
}
})
And I have this method to fetch the order and the fulfilled phase where the state is set from:
Fetching the order:
export const fetchOrder = createAsyncThunk('', async ({ token, id }) => {
const requestOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token,
},
};
try {
const response = await fetch(`${api}/orders/view/${id}`, requestOptions);
const data = await response.json();
return data;
} catch (error) {
console.log(error);
}
});
Filling the order state:
extraReducers: {
[fetchOrder.fulfilled]: (state, action) => {
state.order = action.payload.data;
state.message = 'Succesfully fetched the Order.';
state.isLoading = false;
}
}
While here is method for fetching the orders:
export const fetchAllOrders = createAsyncThunk('', async (token) => {
const requestOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token,
},
};
try {
const response = await fetch(`${api}/orders/all`, requestOptions);
const data = await response.json();
return data;
} catch (error) {
console.log(error);
}
});
And here updating the orders state:
extraReducers: {
[fetchAllOrders.fulfilled]: (state, action) => {
state.orders = action.payload.data;
state.message = 'Succesfully fetched all Orders.';
state.isLoading = false;
}
}
So the case is that I'm calling the fetchAllOrders in the Order page with UseEffect, here is how:
import { fetchAllOrders } from '../redux/ordersSlice';
useEffect(() => dispatch(fetchAllOrders(user.token)), [user]);
So this is how i run the method to fetch orders with dispatch and it works. But the problem is that when I run this function beside the orders state that is filled with the same data, also the order state is filled with the same data and this is impossible as I've cheked all the cases where I could misstyped a user,users typo but there is none I found, and I don't know.
And here is the store:
import orderSlice from './redux/orderSlice';
import ordersSlice from './redux/ordersSlice';
const store = configureStore({
reducer: {
order: orderSlice,
orders: ordersSlice
},
});
You have to give your thunks an unique name: If you name both '' they will be handled interchangably.
Also, you should be using the builder notation for extraReducers. We will deprecate the object notation you are using soon.
Related
i'm stuck while converting an old project to redux tool kit getting an "Unhandled Rejection (TypeError): state.push is not a function" error. i haven't got to grips with the action/thunk and reducer immutability yet. The alerts are working but then the error msg.
import axios from 'axios';
import { setAlert } from '../alerts/alertSlice';
const slice = createSlice({
name: 'auth',
initialState: {
token: localStorage.getItem('token'),
isAuthenticated: null,
loading: true,
user: null,
},
reducers: {
registerSuccess: (state, action) => {
const { payload } = action.payload;
state.push({
payload,
isAuthenticated: true,
loading: false,
});
},
registerFail: (state, action) => {
localStorage.removeItem('token');
state.push({
token: null,
isAuthenticated: false,
loading: false,
user: null,
});
},
},
});
const { registerSuccess, registerFail } = slice.actions;
export default slice.reducer;
// Register User
export const register =
({ name, email, password }) =>
async (dispatch) => {
const config = {
headers: {
'comtent-Type': 'application/json',
},
};
const body = JSON.stringify({ name, email, password });
try {
const res = await axios.post('/api/users', body, config);
dispatch({
type: registerSuccess,
payload: res.data,
});
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach((error) => dispatch(setAlert(error.msg, 'danger')));
}
dispatch(registerFail());
}
};
.push is an array function to add a new item at the end of an array - your state is not an array.
You probably wanted to do something along the lines of
state.token = null
state.isAuthenticated = false
state.loading = false
state.user = null
I have two createAsyncThunk - signIn and signUp is it possible to use one fulfilled reducer both? The reason is that fulfilled reducer same for signIn and signUp. Example:
extraReducers: (builder) => {
builder.addCase(signInUser.fulfilled, (state, { payload }) => {
state.isPending = false;
state.email = payload.username;
state.username = payload.username;
state.first_name = payload.first_name;
state.last_name = payload.last_name;
state.title = payload.title;
state.organization = payload.organization;
state.isNew = payload.isNew;
state.isPremium = payload.isPremium;
state.id = payload.id;
state.error = '';
state.access_token = payload.access_token;
localStorage.setItem(REFRESH_TOKEN, payload.refresh_token);
});
}
Yes, have utils for this purpose: watch this
There is an obvious solution that I found after refactoring, you can pass state and payload to another function and it can set state as it would in yourAsyncThunk.fulfilled. Example:
builder.addCase(signInUser.fulfilled, (state, { payload }) => {
assignStateWithUser(state, payload);
});
builder.addCase(signUpUser.fulfilled, (state, { payload }) => {
assignStateWithUser(state, payload);
});
builder.addCase(loadUser.fulfilled, (state, { payload }) => {
assignStateWithUser(state, payload);
});
const assignStateWithUser = (state: initialStateUser, payload: User) => {
state.email = payload.username;
state.username = payload.username;
state.first_name = payload.first_name;
state.last_name = payload.last_name;
state.title = payload.title;
state.organization = payload.organization;
};
Types for state and payload:
type initialStateUser = RequestPending & { error: string } & User;
export interface User {
id: number;
username: string;
first_name: string;
last_name: string;
email: string;
title: string;
organization: string;
};
I am trying to pass data to my redux store with the help of redux toolkit, but I'm unable to do so. I double checked that there is no problem with my data. Here is my code:
//USER DETAILS
export const userDetailsSlice = createSlice({
name: 'userDetails',
initialState: { loading: 'idle', user: {} },
reducers: {
userDetailsLoading(state) {
if (state.loading === 'idle') {
state.loading = 'pending';
}
},
userDetailsReceived(state, action) {
if (state.loading === 'pending') {
state.loading = 'idle';
state.user = action.payload;
}
},
userDetailsError(state, action) {
if (state.loading === 'pending') {
state.loading = 'idle';
state.error = action.payload;
}
},
},
});
export const {
userDetailsLoading,
userDetailsReceived,
userDetailsError,
} = userDetailsSlice.actions;
export const getUserDetails = (id) => async (dispatch, getState) => {
dispatch(userRegisterLoading());
try {
const {
userLogin: { userInfo },
} = getState();
const config = {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${userInfo.token}`,
},
};
const { data } = await axios.get(`/api/users/${id}`, config);
console.log(data);
dispatch(userDetailsReceived(data));
} catch (error) {
dispatch(userDetailsError(error.toString()));
}
};
I console logged my data right before the dispatch action: so the data is definitely there. Therefore I think there's something wrong with my code. Here is my code for my redux store:
import { configureStore } from '#reduxjs/toolkit';
import { photoListSlice, photoCreateSlice } from './photo';
import { userRegisterSlice, userLoginSlice, userDetailsSlice } from './user';
const reducer = {
userRegister: userRegisterSlice.reducer,
userLogin: userLoginSlice.reducer,
userDetails: userDetailsSlice.reducer,
};
const store = configureStore({ reducer });
export default store;
As seen from this photo: the action userDetailsReceived was called but the state for user did not change.
Any help or hint would be greatly appreciated!!
I made a typo on the first dispatch call of getUserDetails function, I dispatched userRegisterLoading instead of userDetailsLoading... Now it works fine after the change.
export const getUserDetails = (id) => async (dispatch, getState) => {
dispatch(userDetailsLoading());
try {
const {
userLogin: { userInfo },
} = getState();
const config = {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${userInfo.token}`,
},
};
const { data } = await axios.get(`/api/users/${id}`, config);
console.log(data);
dispatch(userDetailsReceived(data));
} catch (error) {
dispatch(userDetailsError(error.toString()));
}
};
I am trying to post data from a redux-form to an api server using axios.My action creator for posting the data looks like this:
//Action creator for submitting edited post
export function submitEditedPost(values, callback) {
const request = axios.post(`${API}/posts`, {headers}, {values});
return dispatch => {
return request.then((data) => {
callback();
console.log(data)
dispatch({
type:SUBMIT_POST,
payload: data
})
})
}
}
My onSubmit() methos of the form where the action-creator is called looks like this:
onSubmit(values) {
var id = Math.random().toString(36).substr(-8);
var d = new Date().toLocaleTimeString();
const formData = {};
for (const field in this.refs) {
formData[field] = this.refs[field].value;
}
formData.id = id;
formData.timestamp = d;
console.log('-->', formData);
this.props.submitEditedPost(formData, () => {
this.props.history.push('/');
});
}
When I try to console.log the edited values,I can see it correctly,but I am not able to post the edited values and update the api. The error message is shown in the screenshot below:
How do I proceed? Can someone please help me with this issue?
EDIT 1: My whole action file:
import axios from 'axios';
export const FETCH_POSTS = 'fetch_posts';
export const CREATE_POST = 'create_post';
export const FETCH_POST = 'fetch_post';
export const DELETE_POST ='delete_post';
export const EDIT_POST = 'edit_post';
export const SUBMIT_POST = 'submit_post';
let token ;
if(!token)
token = localStorage.token = Math.random().toString(36).substr(-8)
const API = 'http://localhost:3001';
const headers = {
'Accept' : 'application/json',
'Authorization' :'token'
}
//Action creator for fetching posts from the API server
export function fetchPosts() {
const URL = `${API}/posts`;
const request = axios.get(URL,{headers});
return dispatch => {
return request.then(({data}) => {
console.log(data);
dispatch({
type : FETCH_POSTS,
payload : data
})
})
}
}
//Action Creator for creating posts
export function createPosts(values, callback) {
return dispatch => {
return axios.post(`${API}/posts`,values,{headers})
.then((data) => {
callback();
console.log(data)
dispatch({
type: CREATE_POST,
payload: data
})
})
}
}
//Action Creator for displaying a selected post
export function fetchPost(id) {
const request = axios.get(`${API}/posts/${id}`,{headers});
return dispatch => {
return request.then(({data}) => {
console.log(data);
dispatch({
type: FETCH_POST,
payload: data
})
})
}
}
//Action creator for deleting post
export function deletePost(id, callback) {
const request = axios.delete(`${API}/posts/${id}`, {headers})
.then(() => callback());
return {
type: DELETE_POST,
payload: id
}
}
//Action creator for editing post
export function editPost(id, callback) {
const request = axios.get(`${API}/posts/${id}`,{headers});
return dispatch => {
return request.then((data) => {
callback();
console.log(data);
dispatch({
type: EDIT_POST,
payload: data
})
})
}
}
//Action creator for submitting edited post
export function submitEditedPost(id, values, callback) {
console.log(values, 'values')
console.log(id, 'id')
const request = axios.put(`${API}/posts/${id}`, {values}, {headers});
return dispatch => {
return request.then((res) => {
callback();
console.log("response", res)
dispatch({
type:SUBMIT_POST,
payload: res
})
})
}
}
In my react/redux/thunk application I use actions like:
function catsRequested() {
return {
type: CATS_REQUESTED,
payload: {},
};
}
function catsReceived(landings) {
return {
type: CATS_RECEIVED,
payload: landings,
};
}
function catsFailed(error) {
return {
type: CATS_FAILED,
payload: { error },
};
}
export const fetchCats = () => ((dispatch, getState) => {
dispatch(catsRequested());
return catsAPI.loadCats()
.then((cats) => {
dispatch(catsReceived(cats));
}, (e) => {
dispatch(catsFailed(e.message));
});
});
To deal with some data (simplified). Everything works but i have a lot of code for every data entity (and constants too).
I mean same functions for dogs, tigers, birds etc...
I see there are similar requested/received/failed action/constant for every entity.
What is right way to minify code in terms of redux-thunk?
You can keep your code DRY by creating a types and a thunk creators:
Type:
const createTypes = (type) => ({
request: `${type}_REQUESTED`,
received: `${type}_RECEIVED`,
failed: `${type}_FAILED`,
});
Thunk:
const thunkCreator = (apiCall, callTypes) => ((dispatch, getState) => {
dispatch({ type: callTypes.request });
return apiCall
.then((payload) => {
dispatch({ type: callTypes.received, payload }));
}, (e) => {
dispatch({ type: callTypes.failed, payload: e.message }));
});
});
Now you can create a fetch method with 2 lines of code:
export const fetchCatsTypes = createTypes('CATS'); // create and export the constants
export const fetchCats = (catsAPI.loadCats, fetchCatsTypes); // create and export the thunk
export const fetchDogsTypes = createTypes('DOGS'); // create and export the constants
export const fetchDogs = (dogsAPI.loadDogs, fetchDogsTypes ); // create and export the thunk
Note: you'll also use the types constant (fetchDogsTypes) in the reducers.