I keep getting this error but it's not clear why. It is a functional component and the hook is within the body. I tried searching around for an answer but it's not clear why it's having an issue.
import { useDispatch } from "react-redux";
import { selectItems } from "../pages/slices/cartSlice";
import { addToBasket } from "../pages/slices/cartSlice";
import ShoppingCartIcon from "#mui/icons-material/ShoppingCart";
import axios from "axios";
code
export default function Products({ data }) {
const dispatch = useDispatch();
const [item, setItem] = useState({});
const pushProducts = () => {
setItem(data.data.products.data);
}
const items = useSelector(selectItems);
const addCart = () => {
const product = {
description,
quantity,
price,
};
//send product to basket slice
dispatch(addToBasket(product));
};
This is the slice
cartSlice
import { createSlice } from '#reduxjs/toolkit'
const initialState = {
items: [],
};
const cartSlice = createSlice({
name: "basket",
initialState,
reducers: {
addToBasket: (state, action) => {
state.items = [...state.items, action.payload]
},
removeFromBasket: (state, action) => {},
},
});
export const {addToBasket, removeFromBasket} = cartSlice.actions
export const selectItems = (state) => state.basket.items;
export default cartSlice.reducer
Related
I have an error : Uncaught TypeError: Cannot destructure property 'users' of '(0 , _redux_hooks__WEBPACK_IMPORTED_MODULE_11__.useAppSelector)(...)' as it is undefined. which happens in app component on line const { users } = useAppSelector(state => state.users);
When hovering on { user } , it shows its type User[], which it inferes from slice file correctly. All parentesis also seems to be in place. Here is store File.
import { configureStore } from '#reduxjs/toolkit';
import usersReducer from './features/users';
const store = configureStore({
reducer: {
users: usersReducer,
},
});
export default store;
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
Here is hooks file where I define types of useDispatch() and useSelector()
import { TypedUseSelectorHook, useSelector, useDispatch } from 'react-redux';
import { AppDispatch, RootState } from './store';
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export const useAppDispatch: () => AppDispatch = useDispatch;
Here is users Slice file
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
import axios from 'axios';
import { User } from '../../types/User';
const GET_URL = '***********************************';
export const fetchUsers = createAsyncThunk('users/fetchUsers', async () => {
const response = await axios.get(GET_URL);
return response.data;
});
type InitialState = {
users: User[];
status:'idle' | 'failed' | 'pending' | 'fullfilled';
error: string | null;
};
const initialState: InitialState = {
users: [],
status: 'idle',
error: 'error',
};
const usersSlice = createSlice({
name: 'users',
initialState,
reducers: {},
extraReducers(builder) {
builder
.addCase(fetchUsers.pending, (state) => {
state.status = 'pending';
})
.addCase(fetchUsers.fulfilled, (state, action) => {
state.status = 'fullfilled';
state.users = action.payload;
})
.addCase(fetchUsers.rejected, (state) => {
state.status = 'failed';
state.error = 'error';
});
},
});
export default usersSlice.reducer;
and here is app file
import { useAppDispatch, useAppSelector } from './redux/hooks';
import { fetchUsers } from './redux/features/users';
export const App: React.FC = () => {
const dispatch = useAppDispatch();
const { users } = useAppSelector(state => state.users); // here shows an error
useEffect(() => {
dispatch(fetchUsers());
}, [dispatch]);
...
May be somebody encountered similar error before. Will be glad for any suggestion.
You're trying to access state.users.users here by destructuring the result of your useAppSelector
const { users } = useAppSelector(state => state.users);
If you want to access state.users you should change your declaration to match your needs
const users = useAppSelector(state => state.users);
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>;
}
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,
]);
I'm new to redux and I find it hard to find a good guide that both uses async calls and typescript. I've tried to figure it out myself but I'm a little bit stuck. If someone could take a look at my code and maybe give me feedback and/or suggestions as to how I can solve this I would be very grateful!
// types.ts file
export const FETCH_USERS_REQUEST = 'FETCH_USERS_REQUEST';
export const FETCH_USERS_SUCCESS = 'FETCH_USERS_SUCCESS';
export const FETCH_USERS_FAILURE = 'FETCH_USERS_FAILURE';
^ Here I define the constants for consistency.
// userActions.ts
import { FETCH_USERS_FAILURE, FETCH_USERS_REQUEST, FETCH_USERS_SUCCESS } from './types';
import { Dispatch } from 'redux';
import axios, { AxiosResponse } from 'axios';
export interface UserProps {
id: number
name: string
email: string
}
export const fetchUsersRequest = () => {
return {
type: FETCH_USERS_REQUEST,
};
};
export const fetchUsersSuccess = (users: Array<UserProps>) => {
return {
type: FETCH_USERS_SUCCESS,
users: users,
};
};
export const fetchUsersFailure = (error: string) => {
return {
type: FETCH_USERS_FAILURE,
error: error,
};
};
export const fetchUsers = () => {
return (dispatch: Dispatch): Promise<unknown> => {
dispatch(fetchUsersRequest());
return axios.get('https://jsonplaceholder.typicode.com/users')
.then((response: AxiosResponse<UserProps[]>) => {
dispatch(fetchUsersSuccess(response.data));
return response.data;
})
.catch((error: string) => {
dispatch(fetchUsersFailure(error));
});
};
};
userReducer
import { UserProps } from '../actions/userActions';
import { FETCH_USERS_FAILURE, FETCH_USERS_REQUEST, FETCH_USERS_SUCCESS } from '../actions/types';
interface Action {
type: string
payload: Array<UserProps> | string,
}
export interface initialStateProps {
loading: boolean,
users: Array<UserProps>
error: string
}
const initialState: initialStateProps = {
loading: false,
users: [],
error: '',
};
export const userReducer = (state = initialState, action: Action) => {
switch (action.type) {
case FETCH_USERS_REQUEST:
return {
...state,
loading: true,
};
case FETCH_USERS_SUCCESS:
return {
loading: false,
users: action.payload,
};
case FETCH_USERS_FAILURE:
return {
loading: false,
users: [],
error: action.payload,
};
default: {
return state;
}
}
};
export const getUsers = (state: initialStateProps) => state.users;
export const getUsersRequest = (state: initialStateProps) => state.loading;
export const getUsersFailure = (state: initialStateProps) => state.error;
And then for my project I use connected-router to help with keeping track of the routes.
import { combineReducers } from 'redux';
import { History } from 'history';
import { connectRouter } from 'connected-react-router';
import { userReducer } from './userReducers';
export const createRootReducer = (history: History) => combineReducers({
router: connectRouter(history),
userReducer,
});
The store:
import { createBrowserHistory } from 'history';
import { applyMiddleware, compose, createStore } from 'redux';
import thunkMiddleware from 'redux-thunk'
import { createLogger } from 'redux-logger';
import { routerMiddleware } from 'connected-react-router';
import { createRootReducer } from './reducers';
export const history = createBrowserHistory();
const loggerMiddleware = createLogger();
export const configureStore = (preloadedState?: any) => {
const composeEnhancer: typeof compose = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
return createStore(
createRootReducer(history),
preloadedState,
composeEnhancer(
applyMiddleware(
routerMiddleware(history),
thunkMiddleware,
loggerMiddleware,
),
),
);
};
And in the application I use this:
const mapStateToProps = (state: initialStateProps) => ({
error: getUsersFailure(state),
users: getUsers(state),
loading: getUsersRequest(state)
});
const mapDispatchToProps = (dispatch: Dispatch) => bindActionCreators({
fetchUsers: fetchUsers
}, dispatch);
export default connect(
mapStateToProps,
mapDispatchToProps,
)(TopbarNav);
But when I console.log(props); I get:
users: undefined
loading: undefined
error: undefined
EDIT:
Here I run the fetchUsers:
const TopbarNav: React.FC = (props: any) => {
const [loading, setLoading] = React.useState(true);
const classes = useStyles();
useEffect(() => {
props.fetchUsers();
});
const handler = () => {
console.log({props});
};
};
I left the render method out in this question.
EDIT 2 updated useEffect method:
const [users, setUsers] = React.useState([]);
const [loading, setLoading] = React.useState(true);
const classes = useStyles();
useEffect(() => {
setUsers(props.fetchUsers());
}, [users, props, loading]);
const handler = () => {
console.log(props.loading);
};
By using combineReducers that way I think your state will look like this
{
router: ...,
userReducer: ...,
}
so you could fix it by modifying the selectors to look for the correct props
state.userReducer.users
state.userReducer.loading
...
The problem is:
I'm trying to use redux-saga in my react app, but i still has this error: Actions must be plain objects. Use custom middleware for async actions. Code it seems correct but no idea why gives that error. I'll be glad for all the help. I'm fighting with it for about two days and still doesn't have a solution. I tried to look up, but I still have this error.
action...
import { GET_DISTRICTS} from '../../constants';
const getAdres = async (url) => {
let response = await fetch(url);
let data = await response.json();
let list = [];
data.AdresList.Adresler.Adres.forEach((item) => {
console.info(item);
list.push({
label: item.ADI,
value: item.ID
});
});
return list;
};
export const actions = {
handleGetDistrictsData: async () => {
let districts = await getAdres(`url is here`);
return {
type: GET_DISTRICTS,
payload: districts
};
},
reducer...
import { GET_DISTRICTS } from '../../constants';
export const initialState = {
districts: [],
quarters: [],
streets: [],
doors: [],
districtSelected: false,
districtSelectedID: null,
quarterSelected: false,
quarterSelectedID: null,
streetSelected: false,
streetSelectedID: null,
doorSelected: false,
doorSelectedID: null
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_DISTRICTS:
return {
...state,
districts: action.payload
};
default:
return state;
}
};
component...
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { actions as addressActions } from '../../../../redux/actions/address';
import Select from 'react-select';
const Districts = (props) => {
let [ fetchedData, setFetchedData ] = useState(false);
useEffect(() => {
props.handleGetDistrictsData();
setFetchedData(true);
});
return (
<React.Fragment>
<Select
name='adresSelect'
options={props.address.districts}
onChange={props.handleDistrictChange}
placeholder='Please Select'
/>
</React.Fragment>
);
};
const mapStateToProps = (state) => ({
address: state.address
});
const mapDispatchToProps = function(dispatch) {
return bindActionCreators({ ...addressActions }, dispatch);
};
export default connect(mapStateToProps, mapDispatchToProps)(Districts);
-------------
import React from 'react';
import Districts from './Districts';
const AddressSearchWidget = (props) => {
return (
<React.Fragment>
<Districts />
</React.Fragment>
);
};
export default AddressSearchWidget
store...
import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootSaga from './sagas/index';
import * as reducers from './';
export function initStore() {
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const rootReducer = combineReducers(reducers);
const sagaMiddleware = createSagaMiddleware();
const store = createStore(rootReducer, composeEnhancer(applyMiddleware(sagaMiddleware)));
// Run sagas
sagaMiddleware.run(rootSaga);
return store;
}
handleGetDistrictsData returns a promise (all async functions return promises). You cannot dispatch a promise in plain redux saga, and redux-saga does not change this. Instead, dispatch a normal action, and have that action run a saga. The saga can then do async things, and when it's done dispatch another action. The reducer listens only for that second action.
// Actions:
export const getDistrictsData = () => ({
type: GET_DISTRICTS,
})
export const districtsDataSuccess = (districts) => ({
type: DISTRICTS_DATA_SUCCESS,
payload: districts
})
// Sagas:
export function* watchGetDistricts () {
takeEvery(GET_DISTRICTS, getDistricts);
}
function* getDistricts() {
let response = yield fetch(url);
let data = yield response.json();
let list = [];
data.AdresList.Adresler.Adres.forEach((item) => {
console.info(item);
list.push({
label: item.ADI,
value: item.ID
});
});
yield put(districtsDataSuccess(list));
}
// reducer:
export default (state = initialState, action) => {
switch (action.type) {
case DISTRICTS_DATA_SUCCESS:
return {
...state,
districts: action.payload
};
default:
return state;
}
};