Redux-toolkit: state is a proxy inside a createSlice reducer - javascript

I am trying to append / update some data in a state array inside of my slice reducers, but when I try to console.log the .projects array of the state that I am interested in I just get a javascript Proxy. What is going on here (what am I doing wrong)?
import { createSlice } from '#reduxjs/toolkit';
const initialState = {
projects: [],
};
const projectsSlice = createSlice({
name: 'projectsSlice',
initialState: { ...initialState },
reducers: {
addProject(state, action) {
const { project } = action.payload;
const newProjects = [project, ...state.projects];
console.group('add project');
console.log('project: ', project);
console.log('state projects: ', state.projects);
console.log('newProjects: ', newProjects);
console.groupEnd();
state.projects = newProjects;
},
setProjects(state, action) {
const { projects } = action.payload;
state.projects = projects;
},
removeProject(state, action) {
const { projectId } = action.payload;
const newProjects = [...state.projects].filter((project) => project.id !== projectId);
state.projects = newProjects;
},
updateProject(state, action) {
const { project } = action.payload;
const projectIndex = state.projects.findIndex((stateProject) => stateProject.id === project.id);
const newProjects = [...state.projects].splice(projectIndex, 1, project);
console.group('updateProject');
console.log('project: ', project);
console.log('projectIndex: ', projectIndex);
console.log('state projects: ', state.projects);
console.log('newProjects: ', newProjects);
console.groupEnd();
state.projects = newProjects;
},
},
});
export const { addProject, removeProject, updateProject, setProjects } = projectsSlice.actions;
export default projectsSlice.reducer;

The Proxy there is the reason you can just mutate state in that reducer and just get an immutable copy in your state - but browsers are really bad at logging proxies.
per the docs, you can use the current export of RTK&immer:
const slice = createSlice({
name: 'todos',
initialState: [{ id: 1, title: 'Example todo' }],
reducers: {
addTodo: (state, action) => {
console.log('before', current(state))
state.push(action.payload)
console.log('after', current(state))
},
},
})

You need to use current
import { current } from '#reduxjs/toolkit'
and with that, you can reach the current state and work with them and after that, you can send a return in reducer to return new data.
Will looks like this:
const referralSlicer = createSlice({
name: 'referral',
initialState: {
referrals: [],
currentCard: 0,
} as IReferralSlicer,
reducers: {
addReferrals(state, { payload }) {
return {
referrals: [...state.referrals, payload],
}
},
deleteReferral(state, { payload }) {
const currentState = current(state)
return {
...currentState,
referrals: currentState.referrals.splice(payload, 1),
}
},
setCurrentCard(state, { payload }) {
return {
referrals: state.referrals,
currentCard: payload,
}
},
},
})

Related

How to call an object using useSelector hook in react

I am currently trying to get the array of objects namely -Animearray in another component and i am getting an error of undefined in the console.
Here is the code of the Store component
import {
configureStore,
createAsyncThunk,
createSlice,
} from "#reduxjs/toolkit";
import { API_KEY, TMBD_BASE_URL } from "../utils/constent";
import axios from "axios";
const initialState = {
movies: [],
genresLoaded: false,
genres: [],
};
const initialAnime = {
anime: [],
genresLoaded: false,
genres: [],
};
const createArrayfromRawdata = (array, moviesArray, genres) => {
array.forEach((movie) => {
const movieGenres = [];
movie.genre_ids.forEach((genre) => {
const name = genres.find(({ id }) => id === genre);
if (name) movieGenres.push(name.name);
});
if (movie.backdrop_path)
moviesArray.push({
id: movie.id,
name: movie?.original_name ? movie.original_name : movie.original_title,
image: movie.backdrop_path,
genres: movieGenres.slice(0, 3),
});
});
};
async function createAnimeFromRawData(rawData, animeArray) {
const data = rawData;
console.log(animeArray);
for (let i = 0; i < data.length; i++) {
const anime = data[i];
if (anime) {
const genreArr = anime.genres.map((genre) => genre.name);
animeArray.push({
name: anime.title,
genre: genreArr,
score: anime.score,
image: anime.images.jpg.image_url,
trailer: anime.trailer.embed_url,
episodes: anime.episodes,
synopsis: anime.synopsis,
});
}
}
console.log(animeArray);
return animeArray;
}
export const RawdataAnime = async () => {
const Animearray = [];
for (let i = 1; Animearray.length < 60 && i < 10; i++) {
const { data } = await axios.get(`https://api.jikan.moe/v4/top/anime`); // Equivalent to response.data
const results = data?.data || [];
try {
await createAnimeFromRawData(results, Animearray);
await new Promise((resolve) => setTimeout(resolve, 1000));
} catch (error) {
console.error(error);
}
}
return Animearray;
};
const rawData = async (api, genres, paging) => {
const moviesArray = [];
for (let i = 1; moviesArray.length < 60 && i < 10; i++) {
const {
data: { results },
} = await axios.get(`${api}${paging ? `&page=${i}` : ""}`);
createArrayfromRawdata(results, moviesArray, genres);
}
return moviesArray;
};
export const fetchMovies = createAsyncThunk(
"neflix/trending",
async ({ type }, thunkAPI) => {
const {
netflix: { genres },
} = thunkAPI.getState();
return rawData(
`${TMBD_BASE_URL}/trending/${type}/week?api_key=${API_KEY}`,
genres,
true
);
}
);
//`${TMBD_BASE_URL}/discover/${type}?api_key=${API_KEY}&with_genres=${genres}`
export const getGenres = createAsyncThunk("netflix/genres", async () => {
const {
data: { genres },
} = await axios.get(`${TMBD_BASE_URL}/genre/movie/list?api_key=${API_KEY}`);
return genres;
});
const netflixSlice = createSlice({
name: "netflix",
initialState,
extraReducers: (builder) => {
builder.addCase(getGenres.fulfilled, (state, action) => {
state.genres = action.payload;
state.genresLoaded = true;
});
builder.addCase(fetchMovies.fulfilled, (state, action) => {
state.movies = action.payload;
});
},
});
const animeSlice = createSlice({
name: "anime",
initialState: initialAnime,
extraReducers: (builder) => {
builder.addCase(RawdataAnime.fulfilled, (state, action) => {
state.anime = action.payload;
});
},
});
export const store = configureStore({
reducer: {
netflix: netflixSlice.reducer,
anime: animeSlice.reducer,
},
});
and turns out when I tried to use the animeArray in my main component it did not load
import BackgroundVid from "../components/BackgroundVid";
import { fetchMovies, getGenres, RawdataAnime, setAnime } from "../store";
import Slider from "../components/Slider";
export default function Netflix() {
const [scrolled, isScrolled] = useState(false);
const genresLoaded = useSelector((state) => state.netflix.genresLoaded);
const movies = useSelector((state) => state.netflix.movies);
const anime = useSelector((state) => state.anime.anime);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getGenres());
onAuthStateChanged(firbaseauth, (user) => {
if (user) {
setUser(user);
} else {
setUser(null);
}
});
}, []);
useEffect(() => {
if (genresLoaded) {
dispatch(fetchMovies({ type: "all" }));
}
});
If you have any suggestions on how can i get the data from that component do let me know.
The targeted data is stored in the component as animeArray
From what I can tell, the RawdataAnime function isn't an action creator. If you want to have a RawdataAnime.fulfilled reducer case then it should be converted to an asynchronous action so when the returned Promise resolves, i.e. it is fulfilled, the reducer can handle the returned payload.
Example:
store
import {
configureStore,
createAsyncThunk,
createSlice,
} from "#reduxjs/toolkit";
import axios from "axios";
const initialAnime = {
anime: [],
genresLoaded: false,
genres: [],
};
...
function createAnimeFromRawData(rawData) {
return rawData.map((anime) => ({
id: anime.mal_id,
name: anime.title,
genre: anime.genres.map((genre) => genre.name),
score: anime.score,
image: anime.images.jpg.image_url,
trailer: anime.trailer.embed_url,
episodes: anime.episodes,
synopsis: anime.synopsis
}));
}
export const RawdataAnime = createAsyncThunk("anime/fetchAnime", async () => {
const { data } = await axios.get(`https://api.jikan.moe/v4/top/anime`); // Equivalent to response.data
const results = data?.data || [];
return createAnimeFromRawData(results);
});
...
const animeSlice = createSlice({
name: "anime",
initialState: initialAnime,
extraReducers: (builder) => {
builder.addCase(RawdataAnime.fulfilled, (state, action) => {
state.anime = action.payload;
});
}
});
export const store = configureStore({
reducer: {
netflix: netflixSlice.reducer,
anime: animeSlice.reducer
}
});
App
const dispatch = useDispatch();
const anime = useSelector((state) => state.anime.anime);
useEffect(() => {
dispatch(RawdataAnime());
}, []);

Working with Array TypeScript in React Redux

in react-redux:
API file, returnes data from DB:
import axios from "axios";
const URL = "http://127.0.0.1:8000/GetAllExercises/";
export function GetAllExercies(){
return new Promise((resolve) =>
axios.get(URL).then((res)=> resolve({data: res.data})))
}
Slice File, calling the async function and setting the payload to the varibale- exercises:
import { createAsyncThunk, createSlice, PayloadAction } from "#reduxjs/toolkit";
import { RootState, AppThunk } from "../../app/store";
import { GetAllExercies } from "../API/GetAllExercisesAPI";
export interface CounterState {
exercieses: string[];
status: "idle" | "loading" | "failed";
}
const initialState: CounterState = {
exercieses: [],
status: "idle",
};
export const GetAllExerciesAsync = createAsyncThunk(
"get_all_exercises/GetAllExercies",
async () => {
const response = await GetAllExercies();
return response;
}
);
export const GetAllExerciesSlice = createSlice({
name: "get_all_exercises",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(GetAllExerciesAsync.pending, (state) => {
state.status = "loading";
})
.addCase(
GetAllExerciesAsync.fulfilled,
(state, action: PayloadAction<any>) => {
state.status = "idle";
console.log(action.payload);
state.exercieses = action.payload;
--> console.log(state.exercieses);
}
);
},
});
export const selectExercises = (state: RootState) => state.get_all_exercises.exercieses;
export default GetAllExerciesSlice.reducer;
at the line with arrow i get this at the console debugger(f12):
{data:Array(17)}
how can i accesses the data inside of it?
If you want access the data duting setting state then you can simply access data from payload
export const GetAllExerciesSlice = createSlice({
name: "get_all_exercises",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(GetAllExerciesAsync.pending, (state) => {
state.status = "loading";
})
.addCase(
GetAllExerciesAsync.fulfilled,
(state, action: PayloadAction<any>) => {
state.status = "idle";
// data is inside action.payload
state.exercieses = action.payload.data;
console.log(state.exercieses);
}
);
},
});
Apparently you are assigning an object to your "exercieses" variable. If using redux there should be a reducer where you do this.
Something similar to this:
const { response } = action.payload
return {...state, exercises:response.data}

Testing Redux reducer fails to pass

New to unit testing in general and especially in Redux, forgive the dumb question.
I'm trying to test a simple reducer but can't get to make it work.
Reducer:
import { ActionTypes } from "../constants/action-types";
const initialState = {
products: [],
};
export const productReducer = (state = initialState, { type, payload }) => {
switch (type) {
case ActionTypes.FETCH_PRODUCTS:
return { ...state, products: payload };
case ActionTypes.RESET_PRODUCTS:
return {};
default:
return state;
}
};
Test:
import { ActionTypes } from "../../redux/constants/action-types";
import { productReducer } from ".././../redux/reducers/productReducer";
describe("Product reducer", () => {
describe("fetching products", () => {
it("adds products", () => {
const action = {
type: ActionTypes.FETCH_PRODUCTS,
product: [{ name: "test" }],
};
const initialState = undefined;
const nextState = productReducer(initialState, action);
expect(nextState).toEqual([{ name: "test" }]);
});
});
});
That's what I get:
expect(received).toEqual(expected) // deep equality
Expected: [{"name": "test"}]
Received: {"products": undefined}
Just don't understand how to test it.
use payload instead of product
const action = {
type: ActionTypes.FETCH_PRODUCTS,
payload: [{ name: "test" }],
};

error when passing parameters to an async redux action

I am trying to create a scraping application using redux toolkit for learning purposes but the scraping process fails whenever I pass custom parameters in the dispatch statement but works correctly on passing default parameters in the thunk
My async thunk
export const loadData = createAsyncThunk(
"alldata/getdata",
async ({ pageNo, language }, thunkAPI) => {
const data = await fetch(
`http://localhost:5000/scrape?pageNo=${encodeURIComponent(
pageNo
)}&language=${encodeURIComponent(language)}`
);
const json = data.json();
return json;
}
);
My slice
const projectSlice = createSlice({
name: "allprojects",
state: {
projectState: [],
workingState: [],
isLoading: false,
hasError: false,
},
reducers: {
addProject: (state, action) => {
return state.workingState.push(action.payload);
},
removeProject: (state, action) => {
return state.workingState.filter(
(project) => project.id !== action.payload.id
);
},
},
extraReducers: {
[loadData.pending]: (state, action) => {
state.isLoading = true;
state.hasError = false;
},
[loadData.fulfilled]: (state, action) => {
const { json } = action.payload;
state.isLoading = false;
state.hasError = false;
},
[loadData.rejected]: (state, action) => {
state.isLoading = false;
state.hasError = true;
},
},
});
export const { addProject, removeProject } = projectSlice.actions;
export const { Projectreducer } = projectSlice.reducer;
export const selectAllPosts = (state) => state.allprojects.projectState;
Calling the async action
React.useEffect(() => {
console.log(dispatch(loadData(1, "ruby")));
}, []);
//url:https://github.com/search?p=undefined&q=language%3Aundefined
how do I solve this error
The async thunk arguments must be collected in one object:
dispatch(loadData({ pageNo: 1, language: "ruby" }))
See https://redux-toolkit.js.org/api/createAsyncThunk#payloadcreator

Updating state in Redux using Redux toolkit in NextJS based application

I am trying to make a cart based ecommerce website. I am using Redux and Redux Toolkit to simplify things. I am fairly new to this and my code is not working as expected.
I am trying to dispatch data to state but the state does not update. Redux Devtools show that data is being indeed dispatched to store, but the state remains the same.
In the action, I am console logging the state items and I think the problem lies here which I can't understand.
import { createSlice } from "#reduxjs/toolkit";
let initialState = [
{
name: "",
id: "",
quantity: "",
mode: "",
bookType: "",
},
];
const cartSlice = createSlice({
name: "cart",
initialState,
reducers: {
add: (state, action) => {
return state.map((item) => {
console.log(item);
if (item.id !== action.payload.id) {
return item;
}
return {
...item,
added: true,
};
});
},
remove: (state, action) => {
return state.map((item) => {
if (item.id !== action.payload.id) {
return item;
}
return {
...item,
added: false,
};
});
},
},
});
export const { add, remove } = cartSlice.actions;
export const getCart = (state) => state.cart;
export default cartSlice.reducer;
Here is console output:
This is how I am dispatching in my code:
function formSubmit(event) {
event.preventDefault();
const requiredCourse = courses[0];
const dispatchItem = {
name: requiredCourse.name,
id: requiredCourse.id,
quantity: 1,
mode: mode,
bookType: bookType,
};
if (userLoggedIn) {
console.assert("About to dispatch");
dispatch(add(dispatchItem));
setTimeout(() => {
Router.push("/cart");
}, 2000);
} else {
openModal();
setTimeout(() => {
Router.push({
pathname: "/user/signup",
query: {
redirect: requiredCourse.url,
},
});
}, 6000);
}
}
Every help is really appreciated.

Categories

Resources