The issue I have is handling the addCase for each of the async calls. For every async call I have, do I need to keep adding the pending and fulfilled cases to the builder under extraReducers or is there another way?
Code for context:
export const counterSlice = createSlice({
...
extraReducers: (builder) => {
builder
.addCase(incrementAsync.pending, (state, action) => {
state.status = 'loading'
console.log(action);
})
.addCase(incrementAsync.fulfilled, (state, action) => {
state.status = 'idle'
state.value += action.payload
console.log(action);
})
.addCase(getWorkouts.pending, (state, action) => {
state.status = 'loading'
console.log(action);
})
.addCase(getWorkouts.fulfilled, (state, action) => {
state.status = 'idle'
state.data = action.payload
console.log(action);
})
// ... and so on?
},
})
where incrementAsync and getWorkouts are async thunks:
export const incrementAsync = createAsyncThunk(
FETCH_COUNT,
async (amount: number) => {
const response = await fetchCount(amount)
// The value we return becomes the `fulfilled` action payload
return response.data
}
)
export const getWorkouts = createAsyncThunk(
FETCH_WORKOUTS,
async (payload: { userId: string, date: Date }) => {
const response = await getWorkoutsAsync(payload.userId, payload.date);
// The value we return becomes the `fulfilled` action payload
return response;
}
)
Related
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.
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'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),
});
I saw two different ways to handle async operation in redux-toolkit. I want to know is there any difference and what are the difference, and which is the best way?
Option 1:
user-actions.js
export const getUsers = () => {
return async (dispatch) => {
try {
dispatch(userActions.setLoading(true));
const data = await getDealershipUsers();
dispatch(userActions.setUsers({ data }));
dispatch(userActions.setLoading(false));
} catch (err) {}
};
};
user-slice.js
setUsers(state, action) {
const { users } = action.payload.data;
state.users = users;
}
Option 2 - with createAsyncThunk:
user-slice.js
export const fetchUsers = createAsyncThunk('users/fetchUsers', async () => {
const response = await axios.get(url)
return response.data
})
// ...and then in extraReducers...
extraReducers(builder) {
builder
.addCase(fetchUsers.pending, (state, action) => {
state.status = 'loading'
})
.addCase(fetchUsers.fulfilled, (state, action) => {
state.status = 'succeeded'
state.users = state.users.concat(action.payload)
})
.addCase(fetchUsers.rejected, (state, action) => {
state.status = 'failed'
state.error = action.error.message
})
}
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?