Can someone please help me to find what exact problem is with below code, as i am new in redux.
API is not called
data is not coming to state products[] array
product-slice.js
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";<br/>
import axios from "axios";<br/>
const initialState = {<br/>
products: [],<br/>
sample: "Hello World",<br/>
};<br/>
<br/>
const productSlice = createSlice({<br/>
name: "productSlice",<br/>
initialState,<br/>
reducers: {<br/>
getProducts(state, action) {<br/>
const p = action.payload;<br/>
console.log(p);<br/>
},<br/>
},<br/>
extraReducers: (builder) => {<br/>
builder.addCase(fetchProductData.fulfilled, (state, action) => {<br/>
state.products.push(action.payload);<br/>
});<br/>
},<br/>
});<br/>
export const fetchProductData = createAsyncThunk(<br/>
"products/fetchProducts",<br/>
async (_, thunkAPI) => {<br/>
try {<br/>
const response = await axios.get("http://localhost:8080/products");<br/>
return await response.data;<br/>
} catch (error) {<br/>
return error;<br/>
}<br/>
}<br/>
);<br/>
export const productActions = productSlice.actions;<br/>
export default productSlice;<br/>
ProductList.js(Component)<br/>
const ProductList = (props) => {<br/>
const history = useHistory();<br/>
const hello = useSelector((state) => state.products.sample);<br/>
const dispatch = useDispatch();<br/>
<br/>
useEffect(() => {<br/>
dispatch(fetchProductData);<br/>
}, [dispatch]);<br/>
You should call an action as a function inside dispatch() like dispatch(fetchProductData()) and implement redux/toolkit with thunk middleware like below (example):
as you can see in reduxToolkitCreateAsyncThunk
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit'
import { userAPI } from './userAPI'
// First, create the thunk
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId, thunkAPI) => {
const response = await userAPI.fetchById(userId)
return response.data
}
)
// Then, handle actions in your reducers:
const usersSlice = createSlice({
name: 'users',
initialState: { entities: [], loading: 'idle' },
reducers: {
// standard reducer logic, with auto-generated action types per reducer
},
extraReducers: (builder) => {
// Add reducers for additional action types here, and handle loading state as needed
builder.addCase(fetchUserById.fulfilled, (state, action) => {
// Add user to the state array
state.entities.push(action.payload)
})
},
})
// Later, dispatch the thunk as needed in the app
dispatch(fetchUserById(123))
Related
I am basically trying to save the state of like/dislike on list of products which I am trying to fetch from an API using axios, but I'm getting the below error.
A non-serializable value was detected in the state, in the path:
'products. Value: {"_x":0"_y",_z":null,"_A":null}
To save the state, I am trying to use Redux Toolkit. Below I have codes for productSlice component and ProductListScreen component.
productSlice.js
import { createSlice } from "#reduxjs/toolkit";
import axios from "axios";
const productsSlice = createSlice({
name: "products",
initialState: { items: [], likes: [] },
reducers: {
fetchProducts: async (state) => {
const response = await axios.get("https://dummyjson.com/products");
state.items = response.data;
state.likes = new Array(state.items.length).fill(false);
},
toggleLike: (state, action) => {
state.likes[action.payload.index] = action.payload.isLiked;
},
},
});
export const { fetchProducts, toggleLike } = productsSlice.actions;
export default productsSlice.reducer;
productListScreen.js
import { useEffect, useState } from "react";
import { FlatList, Text, View } from "react-native";
import { useDispatch, useSelector } from "react-redux";
import { fetchProducts, toggleLike } from "./productsSlice";
const ProductListScreen = () => {
const products = useSelector((state) => state.products.items);
const likes = useSelector((state) => state.products.likes);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchProducts());
}, []);
const handleLike = (index, isLiked) => {
dispatch(toggleLike({ index, isLiked }));
};
const renderItem = ({ item, index }) => (
<View>
<Text>{item.title}</Text>
<LikeDislikeButton
productId={item.id}
liked={likes[index]}
onPress={() => handleLike(index, !likes[index])}
/>
</View>
);
return (
<View>
<FlatList
data={products}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>
</View>
);
};
export default ProductListScreen;
Reducer functions are supposed to be pure synchronous functions. You have declared the fetchProducts reducer function async which implicitly returns a Promise object. This is the non-serializable object that is stored into state.products.
You should be creating and using an asynchronous action, i.e. a Thunk, to make asynchronous API calls. Refactor fetchProducts to be an asynchronous action. Use the productSlice's extraReducers to handle the fulfilled Promise returned from the fetchProducts thunk.
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import axios from "axios";
export const fetchProducts = createAsyncThunk(
"products/fetchProducts",
async () => {
const response = await axios.get("https://dummyjson.com/products");
return response.data;
},
);
const productsSlice = createSlice({
name: "products",
initialState: { items: [], likes: [] },
reducers: {
toggleLike: (state, action) => {
state.likes[action.payload.index] = action.payload.isLiked;
},
},
extraReducers: builder => {
builder
.addCase(fetchProducts.fulfilled, (state, action) => {
const { products } = action.payload;
state.items = products;
state.likes = new Array(products.length).fill(false);
});
},
});
export const { toggleLike } = productsSlice.actions;
export default productsSlice.reducer;
response.data is the object that contains the list of products.
{"products":[{"id":1,"title":"iPhone 9",...}]}
const response = await axios.get("https://dummyjson.com/products");
state.items = response.data.products;
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 have an initialState and a dynamic URL and async thunk for axios
I want to change URL query parameter according to a key in initial state.
?query=${initialState.category}
category changes but URL fetches data with unchanged category (it uses initial key not modified key)
const initialState = {
category: "burger",
products: [],
isLoading: true,
categories :{
burger:"burger",
kebab:"kebab",
chicken:"chicken",
pizza:"pizza",
fish:"fish",
vegan:"vegan",
salad:"salad",
pasta:"pasta",
steak:"steak",
dessert:"dessert",
waffle:"waffle"
}
};
const url = `https://api.spoonacular.com/food/menuItems/search?query=${initialState.category}&number=10&apiKey=API_KEY`;
export const getProducts = createAsyncThunk(
"products/getProducts",
async (_, thunkAPI) => {
try {
const response = await axios(url);
console.log(response);
return await response.data;
} catch (error) {
return thunkAPI.rejectWithValue({ error: error.message });
}
}
);
export const productSlice = createSlice({
name: "product",
initialState,
reducers: {
pickCategory: (state, action) => {
state.category = action.payload;
},
that line is static as value in initialState is not mutated:
const url = `https://api.spoonacular.com/food/menuItems/search?query=${initialState.category}&number=10&apiKey=API_KEY`;
you need to move that constant inside getProducts thunk as thunk has an access to current store:
...
async (_, thunkAPI) => {
const {category} = thunkApi.getState()
const url = `https://api.spoonacular.com/food/menuItems/search?
query=${category}&number=10&apiKey=API_KEY`;
}
...
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'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.