I have a react app that was working until I converted over to a nextjs app. The axios call is being made, the data is being returned but not being stored(the selectors are showing undefined). I suspect it is a setup issue in the conversion.
The call which works:
React.useEffect(() => {
dispatch(fetchCoinsByMarketCap()); // coin gecko
}, [dispatch]);
The slice. I am logging correct data and status in my fetchCoinsByMarketCap.fulfilled case:
import axios from "axios";
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import { coinGecko } from "../../url/urlList";
const initialState = {
coinsMCap: [],
status: "idle",
error: null,
};
export const fetchCoinsByMarketCap = createAsyncThunk(
"coinsByMarketCap/fetchCoinsByMarketCap",
async () => {
const res = await coinGecko.get(
`/coins/markets/?
vs_currency=usd&order=market_cap_desc&per_page=50&page=1&sparkline=false`
);
return res.data;
}
);
export const marketCapSlice = createSlice({
name: "coinsByMarketCap",
initialState,
reducers: {
updateCoinsMcapState: (state, action) => {
Object.assign(state, action.payload);
},
},
extraReducers(builder) {
builder
.addCase(fetchCoinsByMarketCap.pending, (state) => {
state.status = "loading";
})
.addCase(fetchCoinsByMarketCap.fulfilled, (state, action) => {
state.coinsMCap = action.payload;
state.status = "succeeded";
console.log("sc: ", state.coinsMCap);
console.log("ss: ", state.status);
})
.addCase(fetchCoinsByMarketCap.rejected, (state, action) => {
state.status = "failed";
state.error = action.error.message;
});
},
});
export const { updateCoinsMcapState } = marketCapSlice.actions;
export default marketCapSlice.reducer;
My store:
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import { createWrapper } from "next-redux-wrapper";
import reducer from "./rootReducer";
const middleware = [thunk];
const makeStore = () =>
createStore(reducer, compose(applyMiddleware(...middleware)));
export const wrapper = createWrapper(makeStore);
The next _app file which has the wrapper.withRedux export:
import React from "react";
import { wrapper } from "../redux/store";
const MyApp = ({ Component, pageProps }) => <Component {...pageProps} />;
export default wrapper.withRedux(MyApp);
The root reducer:
import { combineReducers } from "redux";
import marketCapReducer from "./slices/marketCapSlice";
const reducer = combineReducers({
coinsByMarketCap: marketCapReducer,
});
export default reducer;
The selector file:
// marketCapSlice selectors
export const selectCoinsMCap = (state) => state.coinsByMarketCap.coinsMCap;
export const selectCoinsMCapStatus = (state) => state.coinsByMarketCap.status;
export const selectCoinsMCapError = (state) => state.coinsByMarketCap.error;
These selectors should have data after the axios call, but they don't. Again bear in mind that this used to work before I tried to convert it to next and the only thing I changed was the store and the _app.js files. The old store without next was very simple:
import { configureStore } from "#reduxjs/toolkit";
import reducer from "./rootReducer";
const store = configureStore({ reducer });
export default store;
And the standard provider setup in the old index:
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
Maybe one of you nextjs experts can help me.
A reducer is supposed to return the updated state, as far as I know. It seems to me like you may have forgotten to return a value here:
reducers: {
updateCoinsMcapState: (state, action) => {
Object.assign(state, action.payload);
},
},
Should it be something like this?
reducers: {
updateCoinsMcapState: (state, action) => {
return Object.assign(state, action.payload);
},
},
Here is the store setup that fixed it:
import { configureStore } from "#reduxjs/toolkit";
import { applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import { createWrapper } from "next-redux-wrapper";
import reducer from "./rootReducer";
const middleware = [thunk];
const makeStore = () =>
configureStore({ reducer }, compose(applyMiddleware(...middleware)));
export const wrapper = createWrapper(makeStore);
I used configureStore instead of createStore and enclosed reducer in curly braces.
Related
I am tried to use saga-toolkit package to create saga action but getting some error while dispatching, although I went to official documentation of saga-toolkit npm package but something is missing over there i think and I am not able to figure out what going wrong. This is official documentation link https://npm.io/package/saga-toolkit
And I wanted to pass some argument while dispatching the action but getting error in console like
"Actions must be plain objects. Use custom middleware for async actions."
saga-toolkit provide createSagaAction which is resposibe for creating saga action but I am still get the above during dispatching
dataSlice.js
import { createSlice } from "#reduxjs/toolkit";
import { createSagaAction } from "saga-toolkit";
export const fetchData = createSagaAction("data/fetchData");
const dataSlice = createSlice({
name: "data",
initialState: {
data: [],
error: null,
loading: false,
},
extraReducers: {
[fetchData.pending]: (state) => {
state.loading = true;
},
[fetchData.fulfilled]: (state, action) => {
state.loading = false;
state.data = action.payload;
},
[fetchData.rejected]: (state, action) => {
state.loading = false;
state.error = action.payload;
},
},
});
export default dataSlice.reducer;
dataSagas.js
import { put, call, fork } from "redux-saga/effects";
import { takeLatestAsync, putAsync } from "saga-toolkit";
import * as actions from "./feature/dataSlice";
import { getDataApi } from "./api";
function* onLoadData({payload}) {
const result = yield call(getDataApi, payload);
return result;
}
function* onLoadData() {
yield takeLatestAsync(actions.fetchData.type, onLoadDataAsync);
}
export const dataSagas = [fork(onLoadData)];
rootSaga.js
import { all } from "redux-saga/effects";
import { dataSagas } from "./dataSagas";
export default function* rootSaga() {
yield all([...dataSagas]);
}
store.js
import { configureStore } from "#reduxjs/toolkit";
import createSagaMiddleware from "redux-saga";
import DataReducer from "./feature/dataSlice";
import rootSaga from "./rootSaga";
const sagaMiddleware = createSagaMiddleware();
const store = configureStore({
reducer: {
data: DataReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({ thunk: false }).concat(sagaMiddleware),
});
sagaMiddleware.run(rootSaga);
export default store;
App.js
import { useEffect } from "react";
import logo from "./logo.svg";
import "./App.css";
import { useDispatch } from "react-redux";
import { fetchData } from "./redux/feature/dataSlice";
function App() {
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchData());
}, []);
return (
<div className="App">
<h2>Calling API using Saga-toolkit</h2>
</div>
);
}
export default App;
I am following the Redux tutorials on the Redux site and I am having difficulty using combine reducers. If I run my code without combine reducers, it works fine. As soon as I put the single reducer into the combined reducers function. I get this: Error: "reducer" is a required argument, and must be a function or an object of functions that can be passed to combineReducers. What am I doing wrong?
Here is the store before using combine reducers:
import { configureStore } from "#reduxjs/toolkit";
import CoinsAllReducer from "./slices/CoinsAll";
export const store = configureStore({
reducer: {
coinsAll: CoinsAllReducer,
},
});
and here is the slice:
import axios from "axios";
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
const initialState = {
coinsAll: [],
status: "idle",
error: null,
};
export const fetchAllCoins = createAsyncThunk(
"coinsAll/fetchAllCoins",
async () => {
const res = await axios.get(
`https://api.coingecko.com/api/v3/coins/markets/?vs_currency=usd&order=market_cap_desc&per_page=100&page=1&sparkline=false`
);
return res.data;
}
);
export const coinsAll = createSlice({
name: "coinsAll",
initialState,
reducers: {},
extraReducers(builder) {
builder
.addCase(fetchAllCoins.pending, (state) => {
state.status = "loading";
})
.addCase(fetchAllCoins.fulfilled, (state, action) => {
state.status = "succeeded";
state.coinsAll = action.payload;
})
.addCase(fetchAllCoins.rejected, (state, action) => {
state.status = "failed";
state.error = action.error.message;
});
},
});
export const selectCoinsAll = (state) => state.coinsAll.coinsAll;
export const selectStatus = (state) => state.coinsAll.status;
export default coinsAll.reducer;
The above code works and gets the data from the endpoint.
Here is the store modified to use the combined reducer:
import { configureStore } from "#reduxjs/toolkit";
import reducer from "./rootReducer";
export const store = configureStore(reducer);
Here is the rootReducer:
import { combineReducers } from "redux";
import coinsAllReducer from "./slices/coinsAll";
const reducer = combineReducers({
coinsAll: coinsAllReducer,
});
export default reducer;
I changed nothing in the slice.
Any help would be appreciated, thanks.
configureStore takes an object with a reducer key.
It should be
export const store = configureStore({
reducer, // <-- object shorthand for the imported root reducer
// other configurations, etc...
});
You have to pass the combined reducer inside the configuration object to the configureStore method
export const store = configureStore({
reducer:combinedReducer
});
Hence, your updated code should be
import { configureStore } from "#reduxjs/toolkit";
import reducer from "./rootReducer";
export const store = configureStore({reducer:reducer});
And since key and value is same, you can simply go with
import { configureStore } from "#reduxjs/toolkit";
import reducer from "./rootReducer";
export const store = configureStore({reducer});
authReducer.js
import {
LOGIN_SUCCESS,
LOGIN_FAIL,
REGISTER_SUCCESS,
REGISTER_FAIL
} from '../Actions/types';
export default (state = { isLoading: true, user: [] }, action) => {
switch(action.type){
case LOGIN_SUCCESS:
localStorage.setItem('token',action.payload.token);
return {
...state,
user: action.payload.data,
}
case LOGIN_FAIL:
return state;
case REGISTER_SUCCESS:
return {
...state,
user: action.payload.data,
}
case REGISTER_FAIL:
return state;
default:
return state;
}
}
profile.js
import React from 'react'
import { useSelector} from 'react-redux'
export const EditProfile = () => {
const data = useSelector((state) => state.user);
return (
<div>
{data}
</div>
)
}
index.js (rootReducer)
import { combineReducers } from 'redux';
import authReducer from './authReducer';
export default combineReducers({
auth: authReducer,
})
store.js
import {createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './Reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(rootReducer,initialState,compose(
applyMiddleware(...middleware),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
));
export default store;
So Here when i logged in the details were on user object so i need to access then in profile.js so that i can display the details of the user
I don't how can i access i need to save the user data which are stored in user (redux store).
I don't know what i am missing any help will be grateful
I don't know how you setup your redux.
This is how I usually set them up:-
A. Reducer
/slices/auth.js
import { createSlice } from '#reduxjs/toolkit'
const initialState = {
user: [],
isLoading: false
}
export const authSlice = createSlice({
name: 'auth',
initialState,
// // The `reducers` field lets us define reducers and generate associated actions
reducers: {
setUser: (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.user = action.payload
},
setLoading: (state, action) => {
state.loading = action.payload
}
}
})
export const { setUser, setLoading } = authSlice.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 selectUser = (state) => state.auth.user
export const selectLoading = (state) => state.auth.isLoading
export default authSlice.reducer;
B. Store
store.js
import { configureStore, getDefaultMiddleware } from '#reduxjs/toolkit'
import authReducer from '../slices/auth'
export const store = configureStore({
reducer: {
auth: authReducer
},
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 } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { selectUser } from '../slices/auth'
export default function EditProfile() {
const dispatch = useDispatch()
const user = useSelector(selectUser)
return {
<>
{user}
</>
}
}
I am supposed to fetch data from an endpoint and display the results using Redux-Sauce.
All is fine except I can't seem to update the state after I fetch data. Read the docs so this is what I could come up with. Please tell me what I am doing wrong!?
How do I update the state calling the action creators inside HomeContainer.js?
Link to codeSandbox
https://codesandbox.io/s/fragrant-sky-56yhi?file=/src/index.js
HomeContainer.js
import React, { useEffect, useState } from "react";
import axios from "axios";
import { connect } from "react-redux";
import Creators from "../redux/Reducers/reducers";
const HomeContainer = ({ iTunesData, actions }) => {
const { loading, data, error } = iTunesData;
const [searchTerm, setSearchTerm] = useState("");
const submitHandler = (e) => {
e.preventDefault();
const getData = async () => {
actions.fetchDataRequest();
try {
const { data } = await axios.get(
`https://itunes.apple.com/search?term=${searchTerm}`
);
// console.log(data);
actions.fetchDataSuccess(data);
} catch (error) {
actions.fetchDataFail(error);
}
};
getData();
// console.log("On submit handler clicked!");
};
// console.log(iTunesData, actions);
// console.log(searchTerm);
// console.log(iTunesData);
console.log(loading, data, error);
return (
<form onSubmit={submitHandler}>
<h1> Home Container</h1>
<input
placeholder="Search..."
type="text"
onChange={(e) => setSearchTerm(e.target.value)}
/>
<button>Go</button>
</form>
);
};
const mapStateToProps = (state, ownProps) => {
return {
iTunesData: state
};
};
const mapDispatchToProps = (state, ownProps) => {
return {
actions: Creators
};
};
export default connect(mapStateToProps, mapDispatchToProps)(HomeContainer);
reducer.js
import { createReducer, createActions } from "reduxsauce";
const { Types, Creators } = createActions({
fetchDataRequest: null,
fetchDataSuccess: ["payload"],
fetchDataFail: ["error"]
});
export default Creators;
const initialState = {
loading: false,
data: [],
error: false
};
export const fetchDataRequest = (state = initialState, action) => {
return { ...state, loading: true, data: [], error: false };
};
export const fetchDataSuccess = (state = initialState, action) => {
return { ...state, data: action.payload, error: false };
};
export const fetchDataFail = (state = initialState, action) => {
return { ...state, data: null, error: action.error };
};
// map our action types to our reducer functions
export const HANDLERS = {
[Types.FETCH_DATA_REQUEST]: fetchDataRequest,
[Types.FETCH_DATA_SUCCESS]: fetchDataSuccess,
[Types.FETCH_DATA_FAIL]: fetchDataFail
};
export const reducer = createReducer(initialState, HANDLERS);
store.js
import { applyMiddleware, combineReducers, compose, createStore } from "redux";
import { reducer } from "./Reducers/reducers";
import thunk from "redux-thunk";
const store = createStore(reducer, applyMiddleware(thunk));
export default store;
index.js
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import store from "./redux/store";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>,
rootElement
);
Your mapDispatchToProps is wrong. Written like you want to use it, it would need to acutally bind dispatch to the actions, which you don't.
If you want to use that nested, you will have to call bindActionCreators manually.
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(Creators, dispatch)
};
};
Otherwise you could also use the "object notation"
const mapDispatchToProps = Creators
in which case the bound action creators will be available as props.fetchDataSuccess, not props.actions.fetchDataSuccess.
Generally, it is also recommended to not use connect at all with function components, but the React-Redux hooks useSelector and useDispatch.
See https://react-redux.js.org/api/hooks
Also, as for your internship, please forward the official Redux Style Guide to your team, with best regards from a Redux Maintainer ;)
https://redux.js.org/style-guide/style-guide/
We really want them to use the official Redux Toolkit, as it will simplify their code a lot more than Redux-Sauce already does - including allowing for immutable logic in reducers thanks to immer integration and containing a full blown api cache abstraction.
Maybe trying that out and prototyping using it might make for a nice internship project for you in the end ;)
There is problem in setting redux store!
It cannot read that state.dataValue.data, says DataSlice is undefined.
Also in Redux Devtool no store have been setup
File: store.js
import rootReducer from "./slices";
import thunk from "redux-thunk";
import { configureStore } from "#reduxjs/toolkit"
const store = configureStore({ reducer: rootReducer, middleware: [thunk] });
export default store;
File2: slice/dataSlice.js
import { combineReducers } from 'redux';
import { dataSlice } from './dataSlice'
export default combineReducers({ dataSlice: dataSlice.reducer });
File3
import { createSlice } from "#reduxjs/toolkit";
import store from '../store'
const initialState = {
data: [],
};
const dataSlice = createSlice({
name: "dataSlice",
initialState,
reducers: {
setData(state, action) {
const { data } = action.payload;
state.data = data.map((item) => item);
},
}
});
export const {
setData,
} = dataSlice.actions;
export default dataSlice.reducer
File4 inventory.jsx
import {
setData,
} from "../../slices/dataSlice";
const Inventory = (props) => {
const dispatch = useDispatch();
const { data } = useSelector((state) => {
return {
data: state.dataSlice.data,
};
});
It showing that dataValue is undefined,
I am using React-Toolkit