useEffect goes to cyclic dependency with Redux Toolkit - reactjs

Tryed to render component using useEffect with "product" dependency but it goes cyclic dependency.
Tryed to use prev state but it doesn't help.
React don't gives any error, but useEffect send request every second.
Component:
export const ProductPage = (props: any) => {
const { product, isLoading, error } = useTypedSelector(state => state.Product)
const dispatch = useTypedDispatch()
const { id } = useParams()
const prevProd = usePrevious(product)
useEffect(() => {
if (prevProd !== product){dispatch(fetchProduct(Number(id)))}
}, [product])
return (
<div>
<div>{product.name}</div>
<div>{product.id}</div>
<div>{product.price}</div>
</div>
)
}
Async Thunk:
export const fetchProduct = createAsyncThunk(
'product/fetch',
async (id: number, thunkApi) => {
try {
const response = await productService.fetch(id)
return response.data
} catch (error: any) {
return thunkApi.rejectWithValue(error.message)
}
}
)
Slice:
export const ProductSlice = createSlice({
name: 'product',
initialState,
reducers: {},
extraReducers: {
[fetchProduct.fulfilled.type]: (state, action: PayloadAction<IProductData>) => {
state.error = ''
state.isLoading = false
state.product = action.payload
},
[fetchProduct.pending.type]: (state) => {
state.isLoading = true
},
[fetchProduct.rejected.type]: (state, action: PayloadAction<string>) => {
state.isLoading = false
state.error = action.payload
},
}
})
Explain please, why this problem occured and how resolve this?

Related

dispatch in Redux won't add new data in store

I have an array of incomes that I display in a table. There's a "+" button to add a new income. I want to increment database and redux store with a dispatch.
Increment database works fine, but I have to reload page to see new data.
Why my dispatch does not update the state.data of incomes ?
// AddNewData.tsx
[...]
const onSubmit: SubmitHandler<FieldValues> = async (data) => {
if (user) {
const formData = {
userId: user._id,
label: data.label,
amount: data.amountInput,
};
dispatch(addUserIncome(formData));
}
};
[...]
//incomes_slice.ts
import { IDataState, IEntry } from "../../helpers/Interfaces";
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import { deleteData, getData, updateData, addData } from "../../APIs/UserData";
import { Notifier } from "../../helpers/Notifier";
const initialState: IDataState = {
data: [],
loading: false,
error: null,
};
export const getUserIncomes = createAsyncThunk("incomes/getUserIncomes", (userId: string) => {
return getData("incomes", userId);
});
export const addUserIncome = createAsyncThunk("incomes/addUserIncome", async (data: { userId: string; label: string; amount: number }) => {
const response = await addData("incomes", data).then((res) => {
const newEntry: IEntry = {
_id: res.data.income._id,
user: data.userId,
label: data.label,
amount: data.amount,
};
return newEntry;
});
return response;
});
export const updateUserIncome = createAsyncThunk("incomes/updateUserIncome", async (data: { id: string; data: IEntry }) => {
await updateData("incomes", data.id, data.data);
return data;
});
export const deleteUserIncome = createAsyncThunk("incomes/deleteUserIncome", async (incomeId: string) => {
await deleteData("incomes", incomeId);
return incomeId;
});
const incomesSlice = createSlice({
name: "incomes",
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(getUserIncomes.pending, (state) => {
state.loading = true;
});
builder.addCase(getUserIncomes.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
state.error = null;
});
builder.addCase(getUserIncomes.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
action.error.message && Notifier(action.error.message, "error");
});
builder.addCase(addUserIncome.fulfilled, (state, action) => {
state.data.push(action.payload);
});
builder.addCase(updateUserIncome.fulfilled, (state, action) => {
state.data = state.data.map((income) => {
if (income._id === action.payload.id) {
return action.payload.data;
}
return income;
});
});
builder.addCase(deleteUserIncome.fulfilled, (state, action) => {
state.data = state.data.filter((income) => income._id !== action.payload);
});
},
});
export const incomesReducer = incomesSlice.reducer;
PS: addData function is just an axios.post that works fine.
Inside addUserIncome you are mixing styles of awaiting a promise.
Instead of this:
const response = await addData("incomes", data).then((res) => {
const newEntry: IEntry = {
_id: res.data.income._id,
user: data.userId,
label: data.label,
amount: data.amount,
};
return newEntry;
});
Either use this:
try {
const response = await addData("incomes", data);
// do stuff
} catch (e) {
console.log(e);
}
Or do this:
addData("incomes", data).then((res) => {
// do stuff
})
I can't leave a comment (just don't have enough credit yet).
You said you checked your store? Are you sure that the data is changed in the store?
(Because this is async of course, and you're not managing loading and pending state of your call, probably your UI is rendered before the data is updated.)

having problem in set payload in redux using createSlice() and createasyncthunk()

component where I am using the state data
const { contentTitles: ContentTitles } = useSelector((state) => state);
const dispatch = useDispatch();
useEffect(() => {
const fetchData = async () => {
const response = await dispatch(getContentTitles()).unwrap();
};
fetchData();
}, [ContentTitles]);
slice
const contentTitles = JSON.parse(localStorage.getItem("contentTitles"));
export const getContentTitles = createAsyncThunk("contenttitles/getContenttitles", async (thunkAPI) => {
try{
const response = await contentitleService.getContenttitles();
return { contentTitles: response };
} catch (error) {
const message =
(error.response &&
error.response.responsedata &&
error.response.responsedata.message) ||
error.message ||
error.toString();
thunkAPI.dispatch(setMessage(message));
return thunkAPI.rejectWithValue();
}
});
const initialState = contentTitles ? contentTitles : null
const contenttitleSlice = createSlice({
name: "contenttitles",
initialState,
reducers: (state, action) => {
state.contentTitles = action.payload.contentTitles;
}
});
const { reducer } = contenttitleSlice;
export default reducer;
Can anyone tell me that why my data is not getting set to the redux? I am new to the redux and asyncthunk. I can't find the reason of not getting my redux state updated.
You have to define an extra actions (extraReducers) for this. Since your codebase is not clear to me, I will use a different example to explain it to you.
// First, create the thunk
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId: number, thunkAPI) => {
const response = await userAPI.fetchById(userId)
return response.data
}
)
const initialState = {
user: null
}
const usersSlice = createSlice({
name: 'users',
initialState,
reducers: {
// Define your other actions here
},
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.user = action.payload;
})
},
})
As you can see here, after the request completed, it will either be a success or error response. You have to define extra reducers to catch this. Above example shows a successful scenario. But you can define extra actions for following phases as well.
pending: 'users/requestStatus/pending'
fulfilled: 'users/requestStatus/fulfilled'
rejected: 'users/requestStatus/rejected'
const initialState = contentTitles ? {contentTitles} : {contentTitles: null}
const contenttitleSlice = createSlice({
name: "contenttitles",
initialState,
extraReducers: {
[getContentTitles.fulfilled]: (state, action) => {
state.contentTitles = action.payload.contentTitles
},
},
});
Yes, the extraReducers were missing. The above code of adding extraReducers in my specific scenario solved the problem.

How to prevent duplicate execution of redux toolkit dispatch in useEffect in react?

I'm using the rudux toolkit in react. After rendering the first page, useEffect is executed and the farmloadPost action is executed.
By the way, armloadPost.pending is executed three times. How can I make it run only once?
i tried disabled stricmode, but it same
this is my code
useEffect( () => {
dispatch(farmloadPost());
}, [dispatch]);
export const farmloadPost = createAsyncThunk(
"farm/farmloadPost",
async (data, { rejectWithValue }) => {
try {
const response = await axios.get(api);
return response.data;
} catch (error: any) {
console.log("error:",error);
return rejectWithValue(error.response.data);
}
}
);
const postSlice = createSlice({
name: "post",
initialState,
reducers: {},
extraReducers: (builder) =>
builder
// loadPost
.addCase(farmloadPost.pending, (state) => {
state.farmLoading = true;
state.farmDone = false;
state.farmError = null;
})
.addCase(farmloadPost.fulfilled, (state, action) => {
// console.log("action.payload:", action.payload);
state.farmLoading = false;
state.farmDone = true;
state.farm = action.payload;
})
.addCase(farmloadPost.rejected, (state, action) => {
state.farmLoading = false;
// state.farmDone = action.error.message;
})
.addDefaultCase((state) => state),
});

Why useEffect works only after manual reload?

I use react redux redux-toolkit
When switching to CardDetails, everything works and I get data in the state of only one element, but when I go from this page to any other page that has useEffect and in which I use the same state, it does not work and I have to reload the page manually. For what reason can this happen?
Here is the page code for one element
const CardDetails = () => {
const {id} = useParams()
const dispatch = useDispatch()
const location = useLocation()
const {posts, loading} = useSelector((state) => ({...state.posts}))
useEffect(() => {
dispatch(getPost(id))
}, []);
console.log(posts)
return (
<div>
<img style={{maxWidth: "150px"}} src={posts.img} alt="image"/>
</div>
);
};
Here is the page I go to from CardDetails
const Main = () => {
const dispatch = useDispatch()
const {posts, loading} = useSelector((state) => ({...state.posts}))
const user = JSON.parse(localStorage.getItem('profile'))
useEffect(() => {
dispatch(getPosts())
}, []);
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [creator, setCreator] = useState("");
const [category, setCategory] = useState("");
const [img, setImg] = useState("");
const [tags, setTags] = useState(["tag1", "tag2"]);
const onSavePostClicked = () =>{
const newPost = {title, description, category, img}
dispatch(createPost(newPost))
setTitle('')
setDescription('')
setCreator('')
setCategory('')
setImg('')
}
return (
And here is the postsSlice
import {createAsyncThunk, createSlice} from "#reduxjs/toolkit";
import * as api from "../../../api/index";
import {fetchPost, fetchPostsCategory} from "../../../api/index";
export const getPosts = createAsyncThunk(
'posts/getPosts',
async (thunkAPI)=> {
const response = await api.fetchPosts()
return response.data.data
})
export const getPost = createAsyncThunk(
'posts/getPost',
async (id,thunkAPI)=> {
const response = await api.fetchPost(id)
return response.data.data
})
export const createPost = createAsyncThunk(
'posts/createPosts',
async (newPost,thunkAPI)=> {
const response = await api.createPost(newPost)
return response.data.data
})
export const deletePost = createAsyncThunk(
'posts/deletePost',
async (post,thunkAPI)=> {
const {_id} = post
const response = await api.deletePost(_id)
return response.data
})
export const updatePost = createAsyncThunk(
'posts/updatePost',
async (post,thunkAPI)=> {
const {_id} = post
const postEdited = {...post, published: true}
const response = await api.updatePost(_id, postEdited)
return response.data
})
export const getCategory = createAsyncThunk(
'posts/getCategory',
async (category,thunkAPI)=> {
const response = await api.fetchPostsCategory(category)
return response.data.data
})
const initialState = {
posts: [],
loading: false,
error:null
}
export const postsSlice = createSlice({
name: "posts",
initialState,
reducers:{
postAdded: {
reducer(state, action) {
state.posts.push(action.payload)
},
prepare(title, description, creator, category, img) {
return {
payload: {
title,
description,
creator,
category,
img
}
}
}
},
},
extraReducers: {
[createPost.pending]: (state, action) => {
state.loading = true;
},
[createPost.fulfilled]: (state, action) => {
state.loading = false;
state.posts.push(action.payload);
},
[createPost.rejected]: (state, action) => {
state.loading = false;
state.error = action.payload.message;
},
[getPosts.pending]: (state) => {
state.loading = true
},
[getPosts.fulfilled]: (state, action) => {
state.loading = false
state.posts = action.payload
},
[getPosts.rejected]: (state) => {
state.loading = false
},
[deletePost.fulfilled]: (state, action)=>{
if (!action.payload?._id) {
console.log('Delete could not complete')
console.log(action.payload)
return;
}
const { _id } = action.payload;
state.posts = state.posts.filter(post => post._id !== _id);
},
[updatePost.pending]: (state, action) => {
state.loading = true;
},
[updatePost.fulfilled]: (state, action) => {
const { _id } = action.payload;
state.loading = false;
const posts = state.posts.filter(post => post._id !== _id);
state.posts = [...posts, action.payload];
},
[updatePost.rejected]: (state, action) => {
state.loading = false;
state.error = action.payload.message;
},
[getCategory.pending]: (state) => {
state.loading = true
},
[getCategory.fulfilled]: (state, action) => {
state.loading = false
console.log()
state.posts = action.payload
},
[getCategory.rejected]: (state) => {
state.loading = false
},
[getPost.pending]: (state) => {
state.loading = true
},
[getPost.fulfilled]: (state, action) => {
state.loading = false
state.posts = action.payload
},
[getPost.rejected]: (state) => {
state.loading = false
},
},
})
export const {postAdded} = postsSlice.actions
export default postsSlice.reducer
When you are using useEffect with an empty array as deps [] it will only be trigger when you mount your component (and cleaned when you unmount the component)

Redux wont remove item from state array by ID

I dont understant what is wrong with this code, im passing in ID and filtering out state, but it just wont remove, cant figure it out, would love some help.
slice:
import { createSlice } from "#reduxjs/toolkit";
const movieSlice = createSlice({
name: "movie",
initialState: { favoriteMovies: [] },
reducers: {
addMovie: (state, action) => {
state.favoriteMovies = [...state.favoriteMovies, action.payload];
},
removeMovie: (state, action) => {
state.favoriteMovies = state.favoriteMovies.filter(
(movie) => movie.imdbID !== action.payload
);
},
},
});
export const { addMovie, removeMovie } = movieSlice.actions;
export const selectMovie = (state) => state.movie.favoriteMovies;
export default movieSlice.reducer;
dispatching:
const MovieDetail = ({ movie }) => {
const [isFavorite, setIsFavorite] = useState(false);
const dispatch = useDispatch();
const imdbID = movie.imdbID;
const handleAddFavorite = () => {
dispatch(addMovie({ movie }));
setIsFavorite(true);
};
const handleRemoveFavorite = () => {
dispatch(removeMovie({ imdbID }));
console.log(imdbID);
setIsFavorite(false);
};
it does nothing when should remove, and then add it again. The ids i pass in are correct.
The way you are dispatching with
dispatch(removeMovie({ imdbID }));
imdbID will end up as action.payload.imdbID.
So you need to either access that, or dispatch like
dispatch(removeMovie(imdbID));

Resources