I m coding a bookmark list for users. I want to stock the api response in an array. I m using Google Book Api. I m using Redux Toolkit.
User can add books in his personal wishlist and to display and made some operations I need to stock in state books informations added.
For the moment I have only 1 book stock cause if I try to add another one in state, this one changes with the new one only. I need to stock all books added.
Any ideas ?
bookSlice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import { BOOKS_BY_ID } from "../../services/api/googleBooks";
export const getBook = createAsyncThunk(
"book/getBook",
async ({ bookid }) => {
const response = await fetch(`${BOOKS_BY_ID}${bookid}`);
const data = await response.json();
return data;
}
);
const bookSlice = createSlice({
name: "book",
initialState: {
list: [],
status: null,
},
reducers: {},
extraReducers: {
[getBook.pending]: (state, action) => {
state.status = "loading";
},
[getBook.fulfilled]: (state, action) => {
state.status = "success";
state.list = action.payload;
},
[getBook.rejected]: (state, action) => {
state.status = "failed";
},
},
});
export default bookSlice.reducer;
FetchBookBookmarked.js
useEffect(() => {
// let allBook = [];
let ignore = false;
bookInfos?.map(async (book) => {
dispatch(getBook({ bookid: book }));
await axios.get(`${BOOKS_BY_ID}${book}`).then((res) => {
// allBook.push(res.data);
if (!ignore) {
setBookDetail((oldValues) => [...oldValues, res.data]);
}
});
});
return () => {
ignore = true;
};
}, [bookInfos, dispatch]);
Push the book data into the list.
Replace following line:
state.list = action.payload;
To:
state.list.push(action.payload)
To avoid duplicates, you can check the ID before adding it:
const bookAlreadyExists = state.list.some(book => book.id === action.payload.id)
if (!bookAlreadyExists) {
state.list.push(action.payload)
}
Related
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.)
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.
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?