My Redux-async function endlessly called and hangs my system when i subscribe to the store using useSelector.
My Product.Slice
import { createSlice } from '#reduxjs/toolkit'
import { apiCallBegan } from './../../app/store/api/api.action';
const productSlice = createSlice({
name: 'product',
initialState: {
products: [],
},
reducers: {
productsReceived: (state, action) => {
state.products=action.payload
}
}
})
export const { addProducts,productsReceived } = productSlice.actions
export const loadProducts = () => apiCallBegan({
url: "/product/",
method: "get",
onSuccess: productsReceived.type
})
export const getProducts = (state) => state.product.products
export default productSlice.reducer
ProductList.js Use Reducer implementation:
const dispatch = useDispatch()
dispatch(loadProducts())
const products=useSelector(state=>state.product.products)
console.log(products)
I assume this is the problem:
const dispatch = useDispatch();
dispatch(loadProducts());
const products = useSelector(
(state) => state.product.products
);
console.log(products);
That is in a component that re renders when products change while also changing the products. Maybe you can only get the products on mount:
const dispatch = useDispatch();
React.useEffect(() => dispatch(loadProducts()), [
dispatch,
]);
Related
I'm trying to add a user to state.users in a redux toolkit slice. Here's userManagementSlice.js:
import { createSlice } from '#reduxjs/toolkit'
const initialState = {
users: [],
}
export const userManagementSlice = createSlice({
name: 'userManagement',
initialState,
reducers: {
addUser: (state, action) => {
const user = action.payload;
state.users.push(user);
},
},
})
// Action creators are generated for each case reducer function
export const { addUser } = userManagementSlice.actions
export default userManagementSlice.reducer
Here's key code from a component that listens to the users in global state and attempts to add a user to that array via dispatch:
const [firstName, setFirstName] = useState();
const [lastName, setLastName] = useState();
const users = useSelector((state) => state.users)
const dispatch = useDispatch()
const addUser2 = () => {
const user = {
firstName,
lastName
}
dispatch(addUser(user));
}
However, the USERS UPDATED value logged to the console from the component is always undefined:
useEffect(() => {
console.log('USERS UPDATED: ' + users);
// reset inputs
setFirstName('');
setLastName('');
}, [users]);
Here's the configureStore setup:
import { configureStore } from '#reduxjs/toolkit'
import userManagementReducer from '../features/userManagement/userManagementSlice'
export const store = configureStore({
reducer: {
userManagement: userManagementReducer,
},
})
Any idea what I'm doing wrong here?
You are mounting the slice at userManagement, so your selector would have to be
const users = useSelector((state) => state.userManagement.users)
I have an api which gives me the result, and I can see the data in my console, but I'm not able to get it in useSelector.
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import axios from "axios";
import { useNavigate } from "react-router-dom";
const initialState = {
value: [],
status: 'idle',
};
export const fetchEmployeesThunk = createAsyncThunk(
'employeelist/fetchEmployeesThunk',
async () => {
const res = await axios.get('https://localhost:7168/Employee/GetEmployeeList').then(
(result) => result.data
)
return res;
})
export const EmployeeListSlice = createSlice({
name: "employeelist",
initialState: initialState,
reducers: {
initialFetch: (state, action) => {
state.value = action.payload;
},
updateEmployeeList: (state, action) => {
state.value = action.payload;
},
},
extraReducers: (builder) => {
builder
.addCase(fetchEmployeesThunk.pending, (state, action) => {
state.status = 'idle';
state.value = [];
})
.addCase(fetchEmployeesThunk.fulfilled, (state, action) => {
console.log(action.payload);
state.value = action.payload;
state.status = 'finished';
})
},
});
export const getEmployeeListData = (state) => state.employeelist.value;
export const { updateEmployeeList, initialFetch } = EmployeeListSlice.actions;
export default EmployeeListSlice.reducer;
export function fetchEmployees() {
return async (dispatch) => {
const res = await axios.get('https://localhost:7168/Employee/GetEmployeeList').then(
(result) => result.data
)
dispatch(updateEmployeeList(res));
}
}
as you can see I tried using both thunk and creating a function and dispatching the data internally to an action, i was able to update the state but i'm not able to get the value through selector, I have a table which takes an array
export default function HomePage() {
const dispatch = useDispatch();
const [tempRows, setTempRows] = useState(useSelector((state) => state.employeelist.value));
const [rows, setTableRows] = useState(useSelector((state) => state.employeelist.value));
useEffect(() => {
//dispatch(fetchEmployees());
dispatch(fetchEmployeesThunk());
}, rows);
}
This is giving me empty array, but lets say if I change something then reload like a hot reload it returns the data now, any help would be deeply appreciated
Please do
const rows = useSelector((state) => state.employeelist.value)
and not
const [rows, setTableRows] = useState(useSelector((state) => state.employeelist.value));
The latter means "use local state that is once initialized from the Redux store". It will only change if setTableRows is called, not if the Redux store changes.
I have this reducer
import { createSlice } from "#reduxjs/toolkit";
export const projectSlice = createSlice({
name: "project-redux",
initialState: {
name: "",
},
reducers: {
get_project: (state, action) => {
axios
.get("http://localhost:5000/api/project/" + action.payload)
.then((res) => {
state = res.data; //which contains the name
});
},
},
});
export const { get_project } = projectSlice.actions;
export default projectSlice.reducer;
and want to access the "name" with useAppSelector
const dispatch = useAppDispatch();
const {name}=useAppSelector(state=>state.projs) //projs is the name of projectsReducer in the store
console.log(name) // only give initial state
How do I get the 'name' value after the get request is fulfilled?
Solution:
export const fetchProject = createAsyncThunk(
"fetchProject",
async (id) => {
const res = await axios.get("http://localhost:5000/api/project/" + id);
return res.data.title;
}
);
reducers: {//other reducers if ther is}
extraReducers: (builder) => {
builder.addCase(fetchProject.fulfilled, (state, action) => {
state.title = action.payload;
});
},
then:
const name = useAppSelector((state) => state.projs.title);
console.log("selecttitle", name);
You can't put the side effect(I/O operations) code inside reducer functions. The redux reducer function should be pure. You should use createAsyncThunk to fetch data.
After your dispatch the async thunk, you should mutate the state with the fetched data inside extraReducers field of createSlice. After mutating the state, the component will re-render, then the useAppSelector will be called. You will read the state fetched from the remote server from the redux store.
E.g.
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
import axios from 'axios';
import { useEffect } from 'react';
export const fetchProject = createAsyncThunk('fetchProject', (id) => {
return axios.get('http://localhost:5000/api/project/' + id).then((res) => res.data);
});
export const projectSlice = createSlice({
name: 'project-redux',
initialState: {
name: '',
},
reducers: {},
extraReducers: (builder) => {
builder.addCase(fetchProject.fulfilled, (state, action) => {
state.name = action.payload;
});
},
});
export default projectSlice.reducer;
// Component
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
function Test() {
const dispatch = useDispatch();
const { name } = useSelector((state) => state.projs);
useEffect(() => {
dispatch(fetchProject('1'));
}, [dispatch]);
return <div>{name}</div>;
}
I am using redux toolkit with thunk to receive data from api.
I need to fetch data from 2 apis in consecutive order using data I got from the first api call as a argument of second api call (search1 first, then search2)
In order to do that, I need to wait for the first dispatch to fully complete its job from calling getSearch1 to updating the state.
Please help!
// store
import { configureStore } from "#reduxjs/toolkit";
import searchReducer from "./slice/searchSlice";
export const store = configureStore({
reducer: {
search: searchReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
// slice
export const getSearch1 = createAsyncThunk(
"search/getSearch1",
async (args: string[]) => {
const result = await ApiUtil.search1(args);
return result;
}
);
export const getSearch2 = createAsyncThunk(
"search/getSearch2",
async (ids: string[]) => {
const result = await ApiUtil.search2(ids);
return result;
}
);
export const searchSlice = createSlice({
name: "search",
initialState,
reducers: {...},
extraReducers: (builder) => {
builder
.addCase(getSearch1.fulfilled, (state, action) => {
state.search1 = action.payload;
})
.addCase(getSearch2.fulfilled, (state, action) => {
state.search2 = action.payload;
});
},
});
// home page
import {
...
getSearch1,
getSearch2,
} from "../../redux/slice/searchSlice";
const handleSearch = () => {
dispatch(getSearch1(args));
const ids = search1?.map((item) => item.id.toString());
dispatch(getSearch2(ids ?? []));
history.push(ROUTES.RESULT_PAGE, search1);
};
You can use .unwrap() method to achieve that,see the documentation
:
try {
const { data } = await dispatch(getSearch1(args)).unwrap()
await dispatch(getSearch2(data ?? []));
// handle result here
} catch (rejectedValueOrSerializedError) {
// handle error here
}
I solved it just as slideshowp2's shared link.
useEffect(() => {
getResult();
}, [dispatch]); // listen for dispatch(), should run getResult() twice
const getResult = async () => {
let action;
if (!search1) { // skip below when called twice
action = await dispatch(getSearch1(args));
}
if (isFulfilled(action)) {
const id = action.payload.map((item) => item.id.toString());
dispatch(getSearch2(id ?? []));
}
};
I am working on a project and I need to fetch data from backend or from an API. I tried fetch the data but nothing appears. I think I am doing something wrong in the container. I am a beginner in react-redux, I don't know what I am doing wrong.
I've already read all the posts but nothing seems to works.
my reducer:
const initialState={
articles: [],
};
const rootReducer = (state = initialState, action) => {
const { type, payload }=action;
switch(type) {
case SRETRIEVE_ARTICLE:{
return {
...state,
articles:payload,
};
}
default: return state;
}
}
export default rootReducer;
This is what I have right now in container:
import Articles from 'components/Articles';
import { fetchArticles } from '../../pages/index';
const mapStateToProps = (state) => ({
articles:state.articles
})
const ConnectedArticles = connect(
mapStateToProps,
{fetchArticles}
)(Articles)
export default ConnectedArticles;
pages.js
axios.get('API').then((response) => {
const { data } = response;
dispatch({ type: RETRIEVE_ARTICLES, payload: data });
});
};
const Index = () => {
const articles= useSelector((state) => state.articles);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchArticles);
}, []);
return <>{articles && articles.map((article) => <Article key={article.id} name={article.name} />)}</>;
};
Index.getInitialProps = async () => ({
authRequired: true,
label: 'Dashboard',
});
export default Index;
Also I defined the action type: export const SET_UNOPENED_REWARD = 'SET_UNOPENED_REWARD';
and action const unopenedRewards = (payload) => ({ type: SET_UNOPENED_REWARD, payload });
One very nice way to do data fetching with redux is to use redux toolkit's createAsyncThunk and createSlice functions.
// src/features/articles/articlesSlice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
export const fetchArticles = createAsyncThunk("articles/get", async () => {
// Here you can use axios with your own api
const response = await fetch("https://rickandmortyapi.com/api/character");
const json = await response.json();
return json.results;
});
export const slice = createSlice({
name: "articles",
initialState: {
loading: false,
data: []
},
reducers: {},
extraReducers: (builder) => {
builder.addCase(fetchArticles.pending, (state) => {
state.loading = true;
});
builder.addCase(fetchArticles.fulfilled, (state, action) => {
state.data = action.payload;
state.loading = false;
});
builder.addCase(fetchArticles.rejected, (state) => {
state.loading = false;
});
}
});
export default slice.reducer;
// src/features/articles/Articles.js
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { fetchArticles } from "./articlesSlice";
export const Articles = () => {
const articles = useSelector((state) => state.articles.data);
const loading = useSelector((state) => state.articles.loading);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchArticles());
}, []);
return (
<>
{loading && "...loading"}
{articles.map((article) => <Article key={article.id} {...article} />)}
</>
);
};
you should use async and await
let response = await axios.get('https://run.mocky.io/v3/5c045896-3d18-4c71-a4e5-5ed32fbbe2de')
if(response.status==200){
dispatch({ type: RETRIEVE_ARTICLES, payload: data });
}