does anyone knows why nothing happens when the button is clicked?
im trying to fetch the movielist from the server when the button is clicked but it doesnt even shows that the action is working the way i expected to be.
my react index js
import React from 'react';
import { connect } from 'react-redux';
import { getMovies } from '../../actions/movieActions';
const Home = ({ movie }) => {
const handleClick = () => {
getMovies();
};
return (
<div>
<button onClick={() => handleClick()}>Get Movies</button>
</div>
);
};
const mapStateToProps = state => ({
movie: state.movie.movies
});
export default connect(mapStateToProps, { getMovies })(Home);
my rootReducer
import { combineReducers } from 'redux';
import movieReducer from './movieReducer';
export default combineReducers({
movie: movieReducer
// log is what we are calling our state
});
my movie reducer
import { GET_MOVIES } from '../actions/types';
const initialState = {
movies: null
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_MOVIES:
return {
...state,
movies: action.payload
};
default:
return state;
}
};
my movie actions
import { GET_MOVIES } from './types';
// get movies from server
export const getMovies = () => async dispatch => {
try {
const res = await fetch('http://localhost:5000/api/movies');
const data = await res.json();
dispatch({
type: GET_MOVIES,
payload: data
});
} catch (error) {
console.log(error);
}
};
my types.js
export const GET_MOVIES = 'GET_MOVIES';
my store.js
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
Your error is in calling the action directly, instead of the connected getMovies prop here:
const handleClick = () => {
getMovies();
};
Should be:
const handleClick = () => {
this.props.getMovies();
};
Related
Undefined is returned while trying to access the state.
Slice:
const initialState = { valid: false };
const validNameSlice = createSlice({
name: "validName",
initialState,
reducers: {
makeNameValid(state) {
state.valid = true;
},
makeNameInvalid(state) {
state.valid = false;
}
}
});
export const {
makeNameValid,
makeNameInvalid
} = validNameSlice.actions;
export default validNameSlice.reducer;
Combining the reducers : (there is actually more than one reducers)
import { combineReducers } from "redux";
import validSlice from "./validSlice";
const reducers = {
validSlice
};
const rootReducer = combineReducers(reducers);
export default rootReducer;
Store:
import { configureStore } from "#reduxjs/toolkit";
import rootReducer from "./rootReducers";
const store = configureStore({
reducer: {
rootReducer
}
});
export default store;
Component:
import { makeNameValid, makeNameInvalid } from "./validSlice";
import { connect } from "react-redux";
class App extends React.Component {
handleClick = () => {
console.log(this.props.isValidName);
};
handleDispatch = () => {
this.props.makeNameValid();
};
handleDispatchFalse = () => {
this.props.makeNameInvalid();
};
render() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button onClick={() => this.handleClick()}>test</button>
<button onClick={() => this.handleDispatch()}>Make true</button>
<button onClick={() => this.handleDispatchFalse()}>Make false</button>
</div>
);
}
}
const mapStateToProps = (state) => ({
isValidName: state.validSlice
});
const mapDispatchToProps = (dispatch) => ({
makeNameValid: () => dispatch(makeNameValid()),
makeNameInvalid: () => dispatch(makeNameInvalid())
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
Initially without root reducer it worked, but with root reducer the state in log is returning undefined.
Link to codesandbox : https://codesandbox.io/s/holy-shadow-ii8fs9?file=/src/App.js
You have a tiny error in your configuration - you're wrapping up your rootReducer, but you just need to pass it as the reducer parameter:
const store = configureStore({
reducer: rootReducer,
});
I was trying to run my Redux app with redux-saga.
Basically on my store.js I have the following codes:
import { applyMiddleware, createStore } from "redux";
import createSagaMiddleware from "redux-saga";
import logger from "redux-logger";
import rootReducer from "./reducers/rootReducer";
import rootSaga from "./sagas/userSagas";
const sagaMiddleware = createSagaMiddleware();
const middleware = [sagaMiddleware];
if (process.env_NODE_ENV === "development") {
middleware.push(logger);
}
const store = createStore(rootReducer, applyMiddleware(...middleware));
sagaMiddleware.run(rootSaga);
export default store;
My usersApi.js looks something like this:
import axios from "axios";
export const loadUsersApi = async () => {
await axios.get("http://localhost:5000/users");
};
And here is my userSagas:
import * as type from "../actionType";
import {
take,
takeEvery,
takeLatest,
put,
all,
delay,
fork,
call,
} from "redux-saga/effects";
import { loadUsersSuccess, loadUsersError } from "../actions/userAction";
import { loadUsersApi } from "../services/userApi";
export function* onLoadUsersStartAsync() {
try {
const response = yield call(loadUsersApi);
if (response.status === 200) {
yield delay(500);
yield put(loadUsersSuccess(response.data));
}
} catch (error) {
yield put(loadUsersError(error.response.data));
}
}
export function* onLoadUsers() {
yield takeEvery(type.LOAD_USERS_START, onLoadUsersStartAsync);
}
const userSagas = [fork(onLoadUsers)];
export default function* rootSaga() {
yield all([...userSagas]);
}
When I run this on my HomePage.js file where I load and dispatch the data:
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { loadUsersStart } from "../redux/actions/userAction";
export default function HomePage() {
const dispatch = useDispatch();
useEffect(() => {
dispatch(loadUsersStart());
}, [dispatch]);
return (
<div>
<h1>Home</h1>
</div>
);
}
It gave me this error:
[HMR] Waiting for update signal from WDS...
index.js:1 TypeError: Cannot read properties of undefined (reading 'data')
at onLoadUsersStartAsync (userSagas.js:25)
at onLoadUsersStartAsync.next (<anonymous>)
at next (redux-saga-core.esm.js:1157)
at currCb (redux-saga-core.esm.js:1251)
index.js:1 The above error occurred in task onLoadUsersStartAsync
created by takeEvery(LOAD_USERS_START, onLoadUsersStartAsync)
created by onLoadUsers
created by rootSaga
Tasks cancelled due to error:
takeEvery(LOAD_USERS_START, onLoadUsersStartAsync)
I am not sure what's causing this error, but even the logger of my app doesn't even show the actions being dispatched.
Any idea how can I fix this issue?
You need to return promise from
export const loadUsersApi = () => {
return axios.get("http://localhost:5000/users");
};
Maybe next time try to use typescript. It will prevent You from similar mistakes
Alternatively, you can build your redux without using redux-saga.
This is how I usually set them up:-
A. Reducer
/slices/auth.js
import { createSlice } from '#reduxjs/toolkit'
const initialState = {
users: [],
loading: false,
success: false,
error: false,
message: ''
}
export const usersSlice = createSlice({
name: 'users',
initialState,
// // The `reducers` field lets us define reducers and generate associated actions
reducers: {
setUsers: (state, action) => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.users = action.payload
},
setLoading: (state, action) => {
state.loading = action.payload
},
setSuccess: (state, action) => {
state.success = action.payload.status
state.message = action.payload.message
},
setError: (state, action) => {
state.error = action.payload.status
state.message = action.payload.message
}
}
})
export const { setUsers, setLoading, setSuccess, setError, setMessage } = usersSlice.actions;
// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they're used instead of
// in the slice file. For example: `useSelector((state: RootState) => state.counter.value)`
export const selectUsers = (state) => state.users.users
export const selectLoading = (state) => state.users.loading
export const selectSuccess = (state) => state.users.success
export const selectError = (state) => state.users.error
export const selectMessage = (state) => state.users.message
export default usersSlice.reducer;
B. Store
store.js
import { configureStore, getDefaultMiddleware } from '#reduxjs/toolkit'
import usersReducer from '../slices/users'
export const store = configureStore({
reducer: {
users: usersReducer
},
middleware: getDefaultMiddleware({
serializableCheck: false
})
})
C. App Component
import { Provider } from 'react-redux'
import { store } from '../app/store'
export default function App() {
return {
<>
<Provider store={store}>
{/* your content... */}
</Provider>
</>
}
}
D. Component (where you use the redux)
import { useContext, useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { selectUsers, selectLoading, selectSuccess, selectError, selectMessage, setUsers, setLoading, setSuccess, setError } from '../slices/users'
import axios from 'axios'
export default function HomePage() {
const dispatch = useDispatch()
const users = useSelector(selectUser)
const loading = useSelector(selectLoading)
const success = useSelector(selectSuccess)
const error = useSelector(selectorError)
const message = useSelector(selectorMessage)
useEffect(() => {
async function init() {
dispatch(setLoading(true))
const response = await axios.get('http://localhost:5000/users')
if(response?.status == 200) {
dispatch(setUsers(response?.data?.data))
dispatch(setSuccess({ status: true, message: 'Successfully get users data.' }))
} else {
dispatch(setError({ status: true, message: 'Failed to get data.' }))
}
dispatch(setLoading(false))
}
return init()
}, [])
return {
<>
{user}
</>
}
}
I am studying redux-saga and I want to fetch data from :
https://jsonplaceholder.typicode.com/posts
and in my redux folder I have the fallowing:
(it can be checked in this github repository
https://github.com/jotasenator/redux-saga-fetching-example/tree/main/src)
\src\redux\api.js
import axios from 'axios'
export const loadPostApi = async () => {
await axios.get(`https://jsonplaceholder.typicode.com/posts`)
}
the get request to the address in question
src\redux\app.actions.js
export const loadPostStart = () => ({
type: 'LOAD_POST_START',
})
export const loadPostSuccess = (posts) => ({
type: 'LOAD_POST_SUCCESS',
payload: posts,
})
export const loadPostFail = (error) => ({
type: 'LOAD_POST_FAIL',
payload: error,
})
those are the actions functions
src\redux\app.reducer.js
const INITIAL_STATE = {
loading: false,
posts: [],
errors: null,
}
export const appReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'LOAD_POST_START':
return {
...state,
loading: true,
}
case 'LOAD_POST_SUCCESS':
return {
...state,
posts: action.payload,
loading: false,
}
case 'LOAD_POST_FAIL':
return {
...state,
errors: action.payload,
loading: false,
}
default:
return state;
}
}
the reducer of the fetching, updating state,
src\redux\counterReducer.js
import { types } from "./types";
const initialState = {
value: 0
}
export const counterReducer = (state = initialState, action) => {
switch (action.type) {
case types.adicionar:
return {
...state,
value: state.value + 1
}
case types.resetear:
return {
...state,
value: 0
}
case types.restar:
return {
...state,
value: state.value - 1
}
default:
return state
}
}
this is the reducer of the counter app, with different approach, types are isolated in another file
src\redux\rootReducer.js
import { combineReducers } from 'redux'
import { counterReducer } from './counterReducer'
import { appReducer } from './app.reducer'
export const rootReducer = combineReducers({
counterReducer,
appReducer
})
the rootReducer for gathering the reducers
src\redux\sagas.js
import { put, takeLatest, call } from 'redux-saga/effects'
import { loadPostApi } from './api'
import { loadPostFail, loadPostSuccess } from './app.actions'
export function* onLoadPostStartAsync() {
try {
const response = yield call(loadPostApi)
yield put(loadPostSuccess(response.data))
} catch (error) {
yield put(loadPostFail(error))
}
}
export function* onLoadPost() {
yield takeLatest('LOAD_POST_START', onLoadPostStartAsync)
}
export default function* rootSaga() {
yield ([
onLoadPost(),
])
}
saga onLoadPostStartAsync called by saga onLoadPost inside rootSaga
src\redux\store.js
import { applyMiddleware, compose, createStore } from "redux";
import createSagaMiddleware from 'redux-saga'
import { rootReducer } from "./rootReducer";
import rootSaga from "./sagas";
const sagaMiddleware = createSagaMiddleware()
const composeEnhancers = (typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose
const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware))
export const store = createStore(rootReducer, enhancer)
sagaMiddleware.run(rootSaga)
this is the store with the redux_devtool_extension, the reducers, and running rootSaga
src\redux\types.js
export const types = {
adicionar: 'ADICIONAR',
resetear: 'RESETEAR',
restar: 'RESTAR'
}
those are the types of the counterApp reducer
src\Counter.js
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
export const Counter = () => {
const dispatch = useDispatch()
const { value } = useSelector(state => state.counterReducer)
const handleAdicionar = () => {
dispatch({ type: 'ADICIONAR' })
}
const handleResetear = () => {
(value !== 0) && dispatch({ type: 'RESETEAR' })
}
const handleRestar = () => {
dispatch({ type: 'RESTAR' })
}
console.log(value)
return (
<div>
<button onClick={handleAdicionar}>Adicionar</button>
{' '}
<button onClick={handleResetear}>Resetear</button>
{' '}
<button onClick={handleRestar}>Restar</button>
<hr />
</div>
)
}
this is the Counter component, it works ok
src\Fetching.js
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { loadPostStart } from './redux/app.actions'
export const Fetching = () => {
const dispatch = useDispatch()
const fetchPost = () => {
dispatch(loadPostStart())
}
const state = useSelector(state => state.appReducer)
console.log(state)
return (
<>
<h1>Fetching from https://jsonplaceholder.typicode.com</h1>
<button onClick={fetchPost}>Fetching</button>
{
!state.loading && state.posts.map((post) => (
<li key={post.id}><h2>{post.title}</h2></li>
))
}
</>
)
}
the Fetching component click on the button calls fetchPost function who dispatch loadPostStart() function which is the same of dispatching {type: 'LOAD_POST_START'}, but nothing happens here when clicking, not fetch nothing from here https://jsonplaceholder.typicode.com/posts
src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { store } from './redux/store';
import { Provider } from "react-redux";
import { Unificator } from './Unificator';
ReactDOM.render(
<Provider store={store}>
<Unificator />
</Provider>,
document.getElementById('root')
);
component Unificator has Counter and Fetching component
src\Unificator.js
import React from 'react'
import { Counter } from './Counter'
import { Fetching } from './Fetching'
export const Unificator = () => {
return (
<div>
<Counter />
<Fetching />
</div>
)
}
as you can see is about of two reducers, one is the famous counter, and the another one is the fetching issue, do not know what is happening that is not fetching the data
obviously, i am doing something wrong here...don t see where
Axio returns promise, You need to capture that and return. Please try replacing below code.
export const loadPostApi = async () => {
await axios.get(`https://jsonplaceholder.typicode.com/posts`)
.then((response) => {
console.log('Response', response);
return response;
})
.catch((error) => {
console.log('error', error);
})
}
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;
}
};
I'm calling the action requestLoadOrders to fetch the orders I need. I'm dispatching with type: REQUEST and afterwards with SUCCESS or FAILURE. The fetch succeeded because my orders are in the payload in the redux dev-tools, but the action that I receive in my reducer is ##redux/PROBE_UNKNOWN_ACTION_z.r.p.l.z. I found a thread about this here, however I can't seem to find what I'm doing wrong?
actions.js
import {
LOAD_ORDERS_REQUEST,
LOAD_ORDERS_SUCCESS,
LOAD_ORDERS_FAILURE
} from './constants';
import { fetchOrders } from '../../api';
export const requestLoadOrders = () => {
return (dispatch, getState) => {
dispatch({ type: LOAD_ORDERS_REQUEST });
fetchOrders().then(orders => {
dispatch({ type: LOAD_ORDERS_SUCCESS, payload: orders });
}).catch(error => {
console.error(error);
dispatch({ type: LOAD_ORDERS_FAILURE, payload: error });
});
};
};
reducer.js
import {
LOAD_ORDERS_REQUEST,
LOAD_ORDERS_SUCCESS,
LOAD_ORDERS_FAILURE
} from './constants';
const initialState = {
orders: []
};
const orderReducer = ( state = initialState, { payload, type }) => {
switch (type) {
case LOAD_ORDERS_REQUEST :
return state;
case LOAD_ORDERS_SUCCESS :
return { ...state, orders: payload};
case LOAD_ORDERS_FAILURE :
return { ...state, error: payload.error};
default :
return state;
}
};
export default orderReducer;
My actions get dispatched correctly, but I suppose there's a problem with the reducer receiving its data. Therefor I also added my store and combined reducers files.
store.js
import { createStore, applyMiddleware, compose } from 'redux';
import { routerMiddleware } from 'react-router-redux';
import createHistory from 'history/createBrowserHistory';
import thunk from 'redux-thunk';
import makeRootReducer from './reducers';
export const history = createHistory();
const initialState = {}
const enhancers = [];
const middleware = [ routerMiddleware(history), thunk ];
if (process.env.NODE_ENV === 'development') {
const devToolsExtension = window.devToolsExtension;
if (typeof devToolsExtension === 'function') {
enhancers.push(devToolsExtension());
}
}
const composedEnhancers = compose(
applyMiddleware(...middleware),
...enhancers
);
const store = createStore(
makeRootReducer,
initialState,
composedEnhancers
);
export default store;
reducers.js
import { combineReducers } from 'redux';
import orderReducer from '../modules/Order/reducer';
export const makeRootReducer = asyncReducers => {
return combineReducers({
order: orderReducer,
...asyncReducers
});
}
export default makeRootReducer;
I found my mistake. I should execute the makeRootReducer function by adding the brackets after the word in createStore().
Updated the createStore() part of store.js to:
const store = createStore(
makeRootReducer(),
initialState,
composedEnhancers
);
was the fix.