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.
Related
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?
I am trying to extend my frontend code with another redux call but the data is not appearing in store.
Here is store definition
const store = configureStore({
reducer: {
login: loginSlice.reducer,
cart: cartSlice.reducer,
product: productSlice.reducer,
notification: notificationSlice.reducer
}
});
Here is a slice
const productSlice = createSlice({
name: 'product',
initialState: {
products: []
},
reducers: {
replaceData(state,action) {
console.log(action)
state.products = action.payload.products;
}
}
});
export const productActions = productSlice.actions
export default productSlice
And action
export const fetchProducts = () => {
return async (dispatch) => {
const fetchHandler = async () => {
const resp = await fetch("https://shoppingcart-a62bb-default-rtdb.europe-west1.firebasedatabase.app/products.json")
const data = await resp.json();
}
try {
const productData = await fetchHandler();
dispatch(productActions.replaceData(productData))
} catch (err) {
dispatch(notificationActions.showNotification({
open: true,
message: "Error reading product data",
type: 'error'
}));
}
}
}
That's what I call in APP.js
useEffect(()=>{
dispatch(fetchCartData())
dispatch(fetchProducts())
},[dispatch]);
Here I read data from store in component
let respProducts = useSelector(state => state.product.products);
console.log(respProducts)
The problem is that fetch in action works,but payload in dispatch empty and no data in useSelector.
I really don't get what's wrong as similar code in the same app works.
Your fetchHandler is missing a return statement.
const fetchHandler = async () => {
const resp = await fetch("https://shoppingcart-a62bb-default-rtdb.europe-west1.firebasedatabase.app/products.json")
const data = await resp.json();
return data
}
use 'useReduxSelector' instead of 'useSelector'
So I'm using redux-thunk with redux toolkit, and I'm getting data with movies from the server.
My initial state looks like this:
const initialState: IPopularMoviesState = {
popularMovie: null,
fetchStatus: null,
popularSearchPage: 1,
}
Interfaces:
export interface IPopularMoviesState {
popularMovie: MovieResults | null;
fetchStatus: FetchStatus | null;
popularSearchPage: number;
}
export type MovieResults = {
results: IMovie[],
};
Fetching thunk function:
export const fetchPopular = createAsyncThunk('popular/fetchPopular', async () => {
const response = await fetchPopularMovies('popular');
console.log(response.data);
return response.data;
})
fetchPopularMovies() function:
export async function fetchPopularMovies(type:string) {
let url: string = `${API_BASE}movie/${type}?api_key=${TMDB_API_KEY}`;
const response = await axios.get<MovieResults>(url);
return response;
}
And finally my slice:
const popularSlice = createSlice({
name:'popular',
initialState,
reducers:{},
extraReducers(builder) {
builder
.addCase(fetchPopular.pending, (state, action) => {
state.fetchStatus = FetchStatus.PENDING
})
.addCase(fetchPopular.fulfilled, (state, action) => {
state.fetchStatus = FetchStatus.SUCCESS
// Add any fetched posts to the array
state.popularMovie.results = state.popularMovie.results.concat(action.payload);
})
.addCase(fetchPopular.rejected, (state, action) => {
state.fetchStatus = FetchStatus.FAILURE
})
}
})
So as you see, it's all pretty basic, but I have a problem on that line:
.addCase(fetchPopular.fulfilled, (state, action) => {
state.fetchStatus = FetchStatus.SUCCESS
// Add any fetched posts to the array
//here typescript says that `state.popularMovie` object is possibly null
state.popularMovie.results = state.popularMovie.results.concat(action.payload);
})
So typescript says that popularMovie object is possible null, and it is in initial state, but I called a fetchPopular function in a component like this:
useEffect(() =>{
if (postStatus === 'pending') {
dispatch(fetchPopular())
}
}, [postStatus, dispatch])
So I'm not sure why it's being labeled as null, maybe there's some way to prevent it?
How to update the state just after dispatch?
State should be updated but is not.
What should be changed? Even when we will use then in our code -> even then we will not receive updated state, only when we will take value from the like .then((value) => { value.data }), but I want to take data from the state
Slice Code:
const authSlice = createSlice({
name: 'auth',
initialState: {
user: {},
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(activateUser.fulfilled, (state, action) => {
state.user = action.payload.userData
})
},
})
export const activateUser = createAsyncThunk('auth/activate', async (data) => {
try {
const userData = await authService.activateAccount(data)
return { userData: userData.data.data }
} catch (error) {
const message =
(error.response && error.response.data && error.response.data.message) ||
error.message ||
error.toString()
return message
}
})
const { reducer } = authSlice
export default reducer
Component:
function ActivateAccount() {
const { user } = useSelector((state) => state.auth)
const [code, setCode] = useState('')
const dispatch = useDispatch()
const activateUserAccount = () => {
const data = {
code: code?.value,
userId: user?._id,
email: user?.email || email?.value,
}
dispatch(activateUser(data))
console.log('Why here value is not updated yet?', user)
if (!user.activated) {
setCode({
...code,
isNotActivated: true,
})
return
}
return navigate('/on-board')
}
}
Why in the console log value is not yet updated?
What should be changed?
Any ideas?
Even though it's Redux it still needs to work within the confines of the React component lifecycle. In other words, the state needs to be updated and subscribers notified and React to rerender with the updated state. You are currently logging the user state closed over in function scope of the current render cycle. There's no possible way to log what the state will be on any subsequent render cycle.
You can chain from the asynchronous action though, and check any resolved values.
function ActivateAccount() {
const { user } = useSelector((state) => state.auth);
const [code, setCode] = useState('');
const dispatch = useDispatch();
const activateUserAccount = () => {
const data = {
code: code?.value,
userId: user?._id,
email: user?.email || email?.value,
}
dispatch(activateUser(data))
.unwrap()
.then(user => {
console.log(user);
if (!user.activated) {
setCode(code => ({
...code,
isNotActivated: true,
}));
return;
}
return navigate('/on-board');
});
}
...
}
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));