I`ve just started to learn redux/toolkit. And I encounter the problem. I read in the documentation of redux that async logic can be processed in midlewares which are created with createAsyncThunk.
So I see two ways how the data can be fetched and pushed to the server. First, you can create reducers that are making changes to the state and fetch or push requests directly from the react component calling dispatch the action and just passing payload that is processed by reducers.
and the second way to use thunks and extra reduces. But when I use them I don't have the logic to put to reducers. I wonder which option to use?
const postsSlice = createSlice({
name: 'posts',
initialState: {
postsList: [],
status: 'idle',
error: null
},
reducers:{
addPost: (state, action) => {
return state.postsList.push(action.payload);
},
deletePost: (state, action) => {
return state.postsList.filter((item, i) => i!== action.id);
}
},
extraReducers: {
[fetchPosts.pending]: state => {
state.status = 'loading';
},
[fetchPosts.fulfilled]: (state, action) => {
state.status = 'succeeded';
state.postsList = action.payload;
},
[fetchPosts.rejected]: (state, action) => {
state.status = 'failed';
state.error = action.error.message;
},
}
});
export default postsSlice.reducer;
Related
I created a Todo application in React.js. I have an API to store my todos and their states.
In my application I can drag and drop the todos in the state I want and I can also CRUD the states.
For the state part I use React-Redux.
My problem happens when adding a new state, I would like to get the id that is provided by the API but in my reducer I get this error: A non-serializable value was detected in the state, in the path: stateReducer.
If I don't get the id, I can't delete the state cause I delete by id, I have to reload the page.
To initialize my state list I use a middleware to do the asymmetric part but I don't understand how to do it when I add a new object.
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import Api from "../api/Api.ts";
const api = new Api("http://localhost:3333/api/state/")
const initialState = {
status:'idle',
list:[],
error: null
}
export const StateSlice = createSlice({
name:'status',
initialState,
reducers: {
add: async (state, action) => {
let item
await api.post(action.payload).then(state => item = state) // PROBLEME
state.list.push(item)
},
remove:(state, action) => {
state.list.splice(state.list.indexOf(action.payload),1)
api.del(action.payload)
},
update:(state, action) => {
api.update(action.payload)
},
get: async (state,action) => {
state.list = action.payload
},
},
extraReducers(builder) {
builder
.addCase(fetchPosts.pending, (state, action) => {
state.status = 'loading'
})
.addCase(fetchPosts.fulfilled, (state, action) => {
state.status = 'succeeded'
state.list = state.list.concat(action.payload)
})
.addCase(fetchPosts.rejected, (state, action) => {
state.status = 'failed'
state.error = action.error.message
})
}
})
const getPosts = async () => {
const states = []
let posts = await api.get()
posts.forEach(p => {
states.push({id:p.id,name:p.name})
});
return states
}
export const fetchPosts = createAsyncThunk('state/fetchPosts', async () => {
let response = await getPosts()
return response
})
export const {add, remove, update, get} = StateSlice.actions
export default StateSlice.reducer;
Do I need to create another middleware for this and how call it ? I am new to this technology, can you give me a hint please?
I am working on a application which uses redux for state management. There, at a some condition, I want to update the state.
My initial state and reducer function looks like this:
import { createSlice } from '#reduxjs/toolkit';
const filterDataTemplate = {
programId: '',
year: '',
};
const initialState = {
//some other state
filterData: { ...filterDataTemplate },
};
const slice = createSlice({
name: 'editFilterSlice',
initialState: initialState,
reducers: {
updateFilterProgramId: (state, action) => {
state.filterData.programId = action.payload;
},
updateFilterYear: (state, action) => {
state.filterData.year = action.payload;
},
},
});
export const {
updateFilterYear,
updateFilterProgramId,
} = slice.actions;
export default slice.reducer;
So filter details containg year and programId is obtained with the help of this code:
const filterDetails = useAppSelector(
(state) => state.locationsFilter.filterData
);
Let's say I have filter data initially:
filterDetails: {year:2021, programId: "Ameria"}
And i want to have my new filter data to be
filterDetails: {year: "", programId: "Ameria"}
So for this what I am doing:
const handleDelete = (e) => {
e.preventDefault();
if (//some condition) {
console.log("delete is called");
dispatch(updateFilterYear(''));
} else {
dispatch(updateFilterProgramId(''));
}
}
handleDelete function is getting called properly when I am clicking a button because I am getting value inside console.
But after running this code my filter data is not updating. I am not sure what I am doing wrong.
Please help with this.
Action.payload is of object type. So You should reference action.payload.year.
I hope this example will be of any use
setTodoDate: {
reducer: (state, action: PayloadAction<TodoDate>) => {
state.currentDate = action!.payload.date;
},
prepare: (value) => ({
payload: { ...value },
}),
}
I think the issue is because you are trying to mutate your state directly. This is bad practice, and Redux state (and more generally react) is intended to be immutable. Reducers should return a copy of the state, along with the updated values. Documentation linked below.
Redux Documentation
Try writing your reducers like the following
updateFilterYear: (state, action) => {
return {
...state,
filterData: {
...state.filterData,
year: action.payload
}
},
updateFilterProgramId: (state, action) => {
return {
...state,
filterData: {
...state.filterData,
programId: action.payload
}
},
I'm learning Redux-Saga and everything works well with { configureStore, getDefaultMiddleware, createAction, createReducer }. However, I cannot successfully implement createSlice.
My actions seem to be dispatched just fine (though I'm not sure since I have multiple Redux stores and placing console.log inside createSlice doesn't seem to work...). I just cannot get the store values - after dispatched action the relevant state value (initially '') becomes undefined. I did wrap my component inside Provider and all. Can someone enlighten me how does createSlice work? Thanks.
RESOLVED I had a bug somewhere else in my code, that's why the reducers weren't working proberly. BUT what I was asking about and what was causing my problems is this: actions passed to createSlice must be 'pure' functions, meaning: (state, action) -> state, nothing fancy. That's why I had to remove my fetching functions (getData1 and getData2) from this createSlice.
ComponentWrapper returns this
<Provider store={toolkitCreateSliceStore}>
<ReduxToolkitCreateSliceComponent />
</Provider>
Component (Buttons just dispatch actions)
class ReduxToolkitCreateSliceComponent extends React.Component {
render () {
return (
<>
<h2>
{this.props.data1}
{(this.props.data1!=='' && this.props.data2!=='') ? ', ' : ''}
{this.props.data2}
</h2><br/>
<h3>{this.props.message}</h3>
<Button1 />
<Button2 />
<Button3 />
</>
);
}
}
function mapStateToProps(state) {
return {
data1: state.toolkitCreateSliceReducer.data1,
data2: state.toolkitCreateSliceReducer.data2,
message: state.toolkitCreateSliceReducer.message
};
}
export default connect(mapStateToProps)(ReduxToolkitCreateSliceComponent);
Redux Toolkit slice
import { createSlice } from "#reduxjs/toolkit";
import axios from "axios";
const initialSliceState = {
data1: '',
data2: '',
message: ''
};
const slice = createSlice({
name: "slice",
initialState: initialSliceState,
reducers: {
getData1: (state, action) => {
return dispatch => {
dispatch(loading1());
return axios.get('http://localhost:8081/data1')
.then(function (response) {
if (response.status === 200) {
dispatch(setResponse1(response.data));
}
}).catch(error => dispatch(displayError1(error)));
};
},
getData2: (state, action) => {
return dispatch => {
dispatch(loading2());
return axios.get('http://localhost:8081/data2')
.then(function (response) {
if (response.status === 200) {
dispatch(setResponse2(response.data));
}
}).catch(error => dispatch(displayError2(error)));
};
},
setResponse1: (state, action) => {
state.data1 = action.payload;
state.message = 'success';
},
setResponse2: (state, action) => {
state.data2 = action.payload;
state.message = 'success';
},
reset: (state, action) => {
state.data1 = '';
state.data2 = '';
state.message = 'reset';
},
loading1: (state, action) => {
state.message = 'loading';
},
loading2: (state, action) => {
state.message = 'loading';
},
displayError1: (state, action) => {
state.message = action.payload;;
},
displayError2: (state, action) => {
state.message = action.payload;;
}
}
});
export const toolkitCreateSliceReducer = slice.reducer;
const { getData1, getData2, setResponse1, setResponse2, reset, loading1, loading2,
displayError1, displayError2} = slice.actions;
export default slice;
Redux Toolkit store
const middleware = [
...getDefaultMiddleware()
];
const toolkitCreateSliceStore = configureStore({
reducer: {
toolkitCreateSliceReducer
},
middleware
});
export default toolkitCreateSliceStore;
Your "reducers" are very wrong.
A reducer must never have any side effects like AJAX calls.
You've written some Redux "thunk" functions where your reducers should be:
getData1: (state, action) => {
return dispatch => {
dispatch(loading1());
return axios.get('http://localhost:8081/data1')
.then(function (response) {
if (response.status === 200) {
dispatch(setResponse1(response.data));
}
}).catch(error => dispatch(displayError1(error)));
};
},
This is a thunk, not a reducer.
A reducer would be something like:
getData(state, action) {
return action.payload;
}
I'd specifically recommend reading through our brand-new "Redux Essentials" core docs tutorial, which teaches beginners "how to use Redux, the right way", using our latest recommended tools and practices like Redux Toolkit. It specifically covers how reducers should work, how to write reducers with createSlice, and how to write and use thunks alongside createSlice:
https://redux.js.org/tutorials/essentials/part-1-overview-concepts
If you're interested in creating async actions, let me recommend you an npm package that I created and use. It is saga-toolkit that allows async functions to get resolved by sagas.
slice.js
import { createSlice } from '#reduxjs/toolkit'
import { createSagaAction } from 'saga-toolkit'
const name = 'example'
const initialState = {
result: null,
loading: false,
error: null,
}
export const fetchThings = createSagaAction(`${name}/fetchThings`)
const slice = createSlice({
name,
initialState,
extraReducers: {
[fetchThings.pending]: () => ({
loading: true,
}),
[fetchThings.fulfilled]: ({ payload }) => ({
result: payload,
loading: false,
}),
[fetchThings.rejected]: ({ error }) => ({
error,
loading: false,
}),
},
})
export default slice.reducer
sagas.js
import { call } from 'redux-saga/effects'
import { takeLatestAsync } from 'saga-toolkit'
import API from 'hyper-super-api'
import * as actions from './slice'
function* fetchThings() {
const result = yield call(() => API.get('/things'))
return result
}
export default [
takeLatestAsync(actions.fetchThings.type, fetchThings),
]
i started to study the react. here is my problem. I have some reducers
let reducers = combineReducers({
user: userReducer,
index_page: indexReducer,
notifications: notificationsReducer
});
Notifications Reducer has its own state of notifications for their renderingand in indexReducer there is. An axios request which, after the response, should draw a notification to the user - change state in notificationsReducer.
I do not quite understand how to do this.
This is my code:
notificationsReducer
let initialState = [
{id: 3, text: 'test_msg', state: 'error'}
];
export const createNotificationActionCreator = (msg_text, msg_state) => {
return {
type: 'SHOW_NOTIFY',
msg_text: msg_text,
msg_state: msg_state
}
}
const notificationsReducer = (state = initialState, action) => {
switch (action.type) {
case SHOW_NOTIFY:
let msg = {
text: action.msg_text,
msg_state: action.msg_state
};
state.push(msg);
break;
}
return state;
}
indexReducer
const indexReducer = (state = initialState, action) => {
switch (action.type) {
case CREATE_NEW_BET:
let bet_data = new Object();
bet_data.bet = state.betAmount;
bet_data.color = action.color;
axios.get('http://localhost/createbet', {
params: {
bet_data
}
}).then(function (response) {
// CHANGE STATE IN notificationsReducer
});
break;
}
return state;
}
To update state in another reducer, I would suggest dispatching the SHOW_NOTIFY action right after dispatching the CREATE_NEW_BET. This can be done using Redux Thunks.
Also read this Stack Overflow answer on suggestions to update state managed by another reducer: Updating state managed by another reducer
With redux-thunk setup, this is what your thunk would look like:
const createBetAndNotify = () => (dispatch) => {
return dispatch({ type: "CREATE_NEW_BET" }).then(() => {
dispatch({ type: "SHOW_NOTIFY" })
})
}
Then inside your React component, you would dispatch the above thunk:
dispatch(createBetAndNotify());
I'm using Redux Toolkit to connect to an API with Axios.
I'm using the following code:
const products = createSlice({
name: "products",
initialState: {
products[]
},
reducers: {
reducer2: state => {
axios
.get('myurl')
.then(response => {
//console.log(response.data.products);
state.products.concat(response.data.products);
})
}
}
});
axios is connecting to the API because the commented line to print to the console is showing me the data. However, the state.products.concat(response.data.products); is throwing the following error:
TypeError: Cannot perform 'get' on a proxy that has been revoked
Is there any way to fix this problem?
I would prefer to use createAsyncThunk for API Data with extraReducers
Let assume this page name is productSlice.js
import { createSlice,createSelector,PayloadAction,createAsyncThunk,} from "#reduxjs/toolkit";
export const fetchProducts = createAsyncThunk(
"products/fetchProducts", async (_, thunkAPI) => {
try {
//const response = await fetch(`url`); //where you want to fetch data
//Your Axios code part.
const response = await axios.get(`url`);//where you want to fetch data
return await response.json();
} catch (error) {
return thunkAPI.rejectWithValue({ error: error.message });
}
});
const productsSlice = createSlice({
name: "products",
initialState: {
products: [],
loading: "idle",
error: "",
},
reducers: {},
extraReducers: (builder) => {
builder.addCase(fetchProducts.pending, (state) => {
state. products = [];
state.loading = "loading";
});
builder.addCase(
fetchProducts.fulfilled, (state, { payload }) => {
state. products = payload;
state.loading = "loaded";
});
builder.addCase(
fetchProducts.rejected,(state, action) => {
state.loading = "error";
state.error = action.error.message;
});
}
});
export const selectProducts = createSelector(
(state) => ({
products: state.products,
loading: state.products.loading,
}), (state) => state
);
export default productsSlice;
In your store.js add productsSlice: productsSlice.reducer in out store reducer.
Then for using in component add those code ... I'm also prefer to use hook
import { useSelector, useDispatch } from "react-redux";
import {
fetchProducts,
selectProducts,
} from "path/productSlice.js";
Then Last part calling those method inside your competent like this
const dispatch = useDispatch();
const { products } = useSelector(selectProducts);
React.useEffect(() => {
dispatch(fetchProducts());
}, [dispatch]);
And Finally, you can access data as products in your component.
It is happening because your reducer function is not a pure function, it should not be having any asynchronous calls.
something like this should work. it will fix the error you are getting
const products = createSlice({
name: "products",
initialState: {
products: []
},
reducers: {
reducer2: (state, { payload }) => {
return { products: [...state.products, ...payload]}
})
}
}
});
and api should be called outside
const fetchProducts = () => {
axios.get('myurl')
.then(response => {
//console.log(response.data.products);
store.dispatch(products.actions.reducer2(response.data.products))
})
};
PS: haven't tried running above code, you may have to make modifications as per your need.