I have been trying to connect my Redux Action and Reducer to my component. But it doesn't seem to work properly.
Currently, when I call my Action, it does get to that Action but it does not move onto my reducer. I think I am missing something here but having a hard time finding out what is the issue.
Could anyone please help me with this issue?
Thank you.
Here is my Action:
export const getItem = () => {
return (dispatch, getState) => {
debugger;
dispatch({
type: 'API_REQUEST',
options: {
method: 'GET',
endpoint: `18.222.137.195:3000/v1/item?offset=0`,
actionTypes: {
success: types.GET_ITEM_SUCCESS,
loading: types.GET_ITEM_LOADING,
error: types.GET_ITEM_SUCCESS
}
}
});
};
};
Here is my Reducer:
export const initialState = {
getItem: {}
};
const registerItemReducer = (state = initialState, action) => {
switch (action.type) {
case types.GET_ITEM_LOADING:
debugger;
return { ...state, loading: true, data: null };
case types.GET_ITEM_SUCCESS:
debugger;
return { ...state, loading: false, getItem: action.data};
case types.GET_ITEM_ERROR:
debugger;
return { ...state, loading: false, error: action.data};
default: {
return state;
}
}
}
export default registerItemReducer;
Here is my store:
/* global window */
import { createStore, applyMiddleware, compose } from 'redux';
import { persistStore, persistCombineReducers } from 'redux-persist';
import storage from 'redux-persist/es/storage'; // default:
localStorage if web, AsyncStorage if react-native
import thunk from 'redux-thunk';
import reducers from '../reducers';
// Redux Persist config
const config = {
key: 'root',
storage,
blacklist: ['status'],
};
const reducer = persistCombineReducers(config, reducers);
const middleware = [thunk];
const configureStore = () => {
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__(),
compose(applyMiddleware(...middleware)),
);
const persistor = persistStore(
store,
null,
() => { store.getState(); },
);
return { persistor, store };
};
export default configureStore;
Lastly here is my component that has "connect" part & componentDidMount:
componentDidMount() {
this.props.getItem();
}
const mapStateToProps = state => ({
registerItem: state.registerItem || {},
});
const mapDispatchToProps = {
getItem: getItem
};
export default connect(mapStateToProps, mapDispatchToProps)(RegisterItemComponent);
Is registerItem name of your reducer? Your reducer has two state getItem and loading. But in the below code you are calling state.registerItem. Looks like there is some mismatch between the actual state and the mapped state.
In the code below, try to print the state value, it will help you to navigate to the exact parameter you are looking for.
Add the below line in your existing code to debug:
const mapStateToProps = state => ({
console.log("State of reducer" + JSON.stringify(state));
registerItem: state.registerItem || {},
});
Related
I am having this error in react-redux. I don't know how to solve it. I wanted to send a param which is taken from an api to another api and fetch results.
This is my store
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
import rootReducer from "./Reducers/rootReducer";
const store = createStore(
rootReducer,
{},
compose(applyMiddleware(thunk), composeWithDevTools())
);
export default store;
This is my reducer code:
import * as types from "../Actions/types";
const initialState = {
posts: [],
table: [],
};
const postReducer = (state = initialState, action) => {
switch (action.type) {
case types.FETCH_DATA:
return {
...state,
posts: action.payload,
};
case types.FETCH_TABLE:
return {
...state,
table: action.payload,
};
default:
return {
...state,
};
}
};
export default postReducer;
This is my action code
export const getData = (from_userpart) => async (dispatch) => {
try {
const { data } = await api.getData(from_userpart);
dispatch({
type: types.FETCH_TABLE,
payload: data,
});
} catch (error) {
console.log(error);
}
};
When i use this code on the parent component,it works but i want to use that in my child component but it gives this error as i mentioned on the label. How to solve that?
useEffect(() => {
dispatch(getData(phone));
}, [dispatch]);
Change the setting of your store, your enhancer should come second.
let composeEnhancers = null
if (process.env.NODE_ENV === 'development') {
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
} else {
composeEnhancers = compose
}
const store = createStore(
rootReducer,
composeEnhancers(applyMiddleware(thunk))
);
I've noticed that i had imported my function from api folder not the action one. It works now
I am trying to make multi dispatch action in the action phase of redux:
Here is my code:
export const get_data_time_slot_week = (params) => {
return async (dispatch) => {
dispatch({
type: common.CALL_API_REQUEST,
});
const res = await callAPI.post(TIME_SLOT_WEEK + GET, {
params: { ...params },
});
if (res.status === 200 && res.data.code >= 0) {
//Here, I get new state of Selector timeSlotWeek
dispatch({
type: timeSlotWeek.GET_DATA_TIME_SLOT_WEEK_SUCCESS,
payload: {
data: [...res.data.data],
dataPage: { ...res.data.dataPage },
errCode: res.data.code,
},
});
//And here, I lost state of a Selector timeSlotWeek add get new state of Selector common
dispatch({
type: common.GET_FEEDBACK,
payload: {
msg: "__msg_can_not_to_server",
},
});
}
};
};
Why did it happen? And how can i keep the state of timeSlotWeek with same flow in my code ?
This is my result when i check by Redux tool
GET_DATA_TIME_SLOT_WEEK_SUCCESS => data: { 0: {...}, 1{...} }
GET_FEEDBACK => data: {}
msg: "new msg"
This is my store.js
import { createStore, applyMiddleware } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import rootReducer from "./reducers";
import thunk from "redux-thunk";
const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(thunk)));
export default store;
This is my {combineReducers}
import { combineReducers } from "redux";
import feedback from "./feedback.reducer";
import loadingReducer from "./loading.reducer";
import timeSlotWeek from "./timeSlotWeek.reducer";
const rootReducer = combineReducers({
dataTimeSlotWeek: timeSlotWeek,
loading: loadingReducer,
feedback: feedback,
});
export default rootReducer;
Thanks for your help
UPDATE: Problem solve:
Because in my reducer of timeSlotWeek.reducer I have a default case, and when I dispatch another action, this case will run and make the state of timeSlotWeek become initState.
import { common, timeSlotWeek } from "../actions/constants";
const initState = {
data: [],
};
export default (state = initState, action) => {
switch (action.type) {
case timeSlotWeek.GET_DATA_TIME_SLOT_WEEK_SUCCESS:
state = {
// Pass payload to this
};
break;
default:
state = { ...initState };
}
return state;
};
I fix it by this way:
import { common, timeSlotWeek } from "../actions/constants";
const initState = {
data: [],
};
export default (state = initState, action) => {
switch (action.type) {
case timeSlotWeek.GET_DATA_TIME_SLOT_WEEK_SUCCESS:
state = {
// Pass payload to this
};
break;
**case common.CALL_API_FINISH:
state = state;
break;
case common.GET_FEEDBACK:
state = state;
break;**
default:
state = { ...initState };
}
return state;
};
Have any way better than my way ? Thank for cmt
The default reducer case should always return the current state object. I can't think of a single counter-example otherwise (though I have seen some tutorials throw an error here you generally don't want to do that as it adds unnecessary error handling and complicates everything).
You need only define cases for actions your state slice reducer needs to handle, otherwise the let the default case handle it by simply returning the current state.
const initState = {
data: [],
};
export default (state = initState, action) => {
switch (action.type) {
case timeSlotWeek.GET_DATA_TIME_SLOT_WEEK_SUCCESS:
return {
// Pass payload to this
};
default:
return state;
}
};
If you need to reset some state then use another action and case for this, i.e.:
case 'resetTimeSlotWeek':
return initState;
I have seen solutions for clearing/resetting the store after logout but did not understand how to implement the same functionality for the following way of setting up the redux store.
Store.js:
import { configureStore, getDefaultMiddleware } from '#reduxjs/toolkit'
import authReducer from './ducks/authentication'
import snackbar from './ducks/snackbar'
import sidebar from './ducks/sidebar'
import global from './ducks/global'
import quickView from './ducks/quickView'
import profileView from './ducks/profileView'
const store = configureStore({
reducer: {
auth: authReducer,
snackbar,
sidebar,
global,
quickView,
profileView,
},
middleware: [...getDefaultMiddleware()],
})
export default store
Here is how all the reducers implemented using createAction and createReducer from #reduxjs/toolkit.
snackbar.js:
import { createAction, createReducer } from '#reduxjs/toolkit'
export const handleSnackbar = createAction('snackbar/handleSnackbar')
export const openSnackBar = (
verticalPosition,
horizontalPosition,
message,
messageType,
autoHideDuration = 10000
) => {
return async dispatch => {
dispatch(
handleSnackbar({
verticalPosition,
horizontalPosition,
message,
autoHideDuration,
messageType,
isOpen: true,
})
)
}
}
export const closeSnackbar = () => {
return dispatch => {
dispatch(handleSnackbar({ isOpen: false }))
}
}
const initialState = {
verticalPosition: 'bottom',
horizontalPosition: 'center',
message: '',
autoHideDuration: 6000,
isOpen: false,
messageType: 'success',
}
export default createReducer(initialState, {
[handleSnackbar]: (state, action) => {
const {
isOpen,
verticalPosition,
horizontalPosition,
message,
autoHideDuration,
messageType,
} = action.payload
state.isOpen = isOpen
state.verticalPosition = verticalPosition
state.horizontalPosition = horizontalPosition
state.message = message
state.autoHideDuration = autoHideDuration
state.messageType = messageType
},
})
As per Dan Abramov's answer, create a root reducer which will simply delegate the action to your main or combined reducer. And whenever this root reducer receives a reset type of action, it resets the state.
Example:
const combinedReducer = combineReducers({
first: firstReducer,
second: secondReducer,
// ... all your app's reducers
})
const rootReducer = (state, action) => {
if (action.type === 'RESET') {
state = undefined
}
return combinedReducer(state, action)
}
So, if you have configured your store with #reduxjs/toolkit's configureStore, it might look like this:
import { configureStore } from '#reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
export default configureStore({
reducer: {
counter: counterReducer,
// ... more reducers
},
});
where configureStore's first parameter reducer accepts a function (which is treated as root reducer) or an object of slice reducers which is internally converted to root reducer using combineReducers.
So, now instead of passing object of slice reducers (shown above), we can create and pass root reducer by ourselves, here is how we can do it:
const combinedReducer = combineReducers({
counter: counterReducer,
// ... more reducers
});
Now, lets create a root reducer which does our reset job when needed:
const rootReducer = (state, action) => {
if (action.type === 'counter/logout') { // check for action type
state = undefined;
}
return combinedReducer(state, action);
};
export default configureStore({
reducer: rootReducer,
middleware: [...getDefaultMiddleware()]
});
Here is CodeSandbox
I wanted to extend Ajeet's answer so that it is accessible to those who want complete type safety throughout their Redux store.
The key differences are that you need to declare a RootState type, which is documented in the RTK docs
const combinedReducer = combineReducers({
counter: counterReducer
});
export type RootState = ReturnType<typeof combinedReducer>;
And then in your rootReducer, where you are executing your logout function, you want to maintain type safety all the way down by giving the state param the RootState type, and action param AnyAction.
The final piece of the puzzle is setting your state to an empty object of type RootState instead of undefined.
const rootReducer: Reducer = (state: RootState, action: AnyAction) => {
if (action.type === "counter/logout") {
state = {} as RootState;
}
return combinedReducer(state, action);
};
I forked Ajeet's answer on CodeSandbox, added the required types, and you can view it here.
If you're looking to reset each slice to its initial state (unlike setting the entire state to an empty object) you can use extraReducers to respond to a logout action and return the initial state.
In auth.tsx:
const logout = createAction('auth/logout')
In foo.tsx:
const initialState = {
bar: false,
}
const fooSlice = createSlice({
name: 'foo',
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(logout, () => {
return initialState
})
},
})
A simplified example with two reducers:
// actions and reducer for state.first
const resetFirst = () => ({ type: 'FIRST/RESET' });
const firstReducer = (state = initialState, action) => {
switch (action.type) {
// other action types here
case 'FIRST/RESET':
return initialState;
default:
return state;
}
};
// actions and reducer for state.second
const resetSecond = () => ({ type: 'SECOND/RESET' });
const secondReducer = (state = initialState, action) => {
switch (action.type) {
// other action types here
case 'SECOND/RESET':
return initialState;
default:
return state;
}
};
const rootReducer = combineReducers({
first: firstReducer,
second: secondReducer
});
// thunk action to do global logout
const logout = () => (dispatch) => {
// do other logout stuff here, for example logging out user with backend, etc..
dispatch(resetFirst());
dispatch(resetSecond());
// Let every one of your reducers reset here.
};
The simple solution - just add a reducer like this...
resetList: (state) => {
return (state = []);
},
... and call it with a button:
const handleResetList = () => {
dispatch(resetList());
};
return (
<div>
<div>List</div>
<button onClick={handleResetList}>Reset</button>
I need to fetch data from server with the help of such instrument as Redux. I watched some tutorials about it and wrote some code for it. Here it is:
actions/fetching_actions.js
import * as Actions from '../constants/action_types';
function fetchListOfCities() {
return fetch(`${Actions.BASE_URL}/data/2.5/find?lat=55.5&lon=37.5&cnt=10&appid=8df903ce56f6d18245e72f380beb297d`);
}
export const listOfCitiesRequest = () => function (dispatch) {
return fetchListOfCities()
.then(list => list.json())
.then((list) => {
dispatch(getListOfCities(list));
}).catch((error) => {
console.log(error);
});
};
export const getListOfCities = result => ({
type: Actions.LIST_RESPONSE,
result,
});
constants/action_types.js
export const BASE_URL = 'http://api.openweathermap.org';
export const LIST_RESPONSE = 'LIST_RESPONSE';
export const CITY_RESPONSE = 'CITY_RESPONSE';
reducers/fetching_reducer.js
import * as Actions from '../constants/action_types';
const initialState = {
list: [],
city: {},
};
const FETCHING_REDUCER = (state = initialState, action) => {
switch (action.type) {
case Actions.LIST_RESPONSE:
return {
...state,
list: action.result,
};
case Actions.CITY_RESPONSE:
return {
...state,
city: action.result,
};
default:
return state;
}
};
export default FETCHING_REDUCER;
reducers/index.js
import * as Actions from '../constants/action_types';
const initialState = {
list: [],
city: {},
};
const FETCHING_REDUCER = (state = initialState, action) => {
switch (action.type) {
case Actions.LIST_RESPONSE:
return {
...state,
list: action.result,
};
case Actions.CITY_RESPONSE:
return {
...state,
city: action.result,
};
default:
return state;
}
};
export default FETCHING_REDUCER;
And unfortunately I don't know what should I do further. Before I fetched data in this way in Component:
getCitiesListFromApiAsync = async () => {
const fetchData = await fetch('http://api.openweathermap.org/data/2.5/find?lat=55.5&lon=37.5&cnt=10&appid=8df903ce56f6d18245e72f380beb297d').then();
const data = await fetchData.json();
if (data.cod !== '200') {
Alert.alert('Loading failed');
} else {
this.setState({ data });
}
};
But I heard that it's better to fetch data by redux, so, please, can you explain me how to finish this fetching part, what should I add here?
In saga please import these things
import { put } from 'redux-saga/effects';
getCitiesListFromApiAsync = async () => {
const fetchData = await fetch('http://api.openweathermap.org/data/2.5/find?lat=55.5&lon=37.5&cnt=10&appid=8df903ce56f6d18245e72f380beb297d').then();
const data = await fetchData.json();
if (data.cod !== '200') {
Alert.alert('Loading failed');
} else {
yield put({ type: LIST_RESPONSE, payload: data });
}
};
In reducer
switch (action.type) {
case Actions.LIST_RESPONSE:
return {
...state,
list: action.payload,
};
To send some request to the server via redux you should use one of middlewares:
redux-thunk
redux-promise
redux-saga
redux-observable
etc.
The easiest I think is redux-thunk.
1. Install the package:
$ npm install redux-thunk
2. Connect it to your store:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
// Note: this API requires redux#>=3.1.0
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
After that, you will be allowed to dispatch to redux not only a plain javascript object but also functions.
3. So you can dispatch like this:
store.dispatch(listOfCitiesRequest());
Fairly new to redux, and have gone through the official guides. Now I'm trying to do something solo. I have two reducers and am using react-thunk. When I dispatch an action after the first one it clears my collection of my other reducer. To illustrate what I mean is I have:
Actions.js
import axios from 'axios';
function fetchAtms() {
return axios.get('http://localhost:4567');
}
export const recievedAtms = (atms) => {
return {
type: 'RECIEVED_ATMS',
atms
}
}
export const completed = () => {
return {
type: 'COMPLETED',
}
}
export const loadMore = () => {
return {
type: 'LOAD_MORE',
}
}
export const loadAtms = (forPerson) => {
return function (dispatch) {
return fetchAtms().then((response) => {
let atms = response.data.map((item) => {return item['location']})
dispatch(recievedAtms(atms));
// When dispatch(completed()); is called
// it is clears my app collection.
dispatch(completed());
// $r.store.getState() => Object {app: {atms: []}, isLoading: false, router: Object}
}, (error) => {
console.log('implement me');
})
}
}
Reducers
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
const app = (state = {}, action) => {
switch (action.type) {
case 'RECIEVED_ATMS':
return {
atms: action.atms
}
default:
return {};
}
}
const isLoading = (state = true, action) => {
switch (action.type) {
case 'COMPLETED':
return !state;
default:
return state;
}
}
const appReducer = combineReducers({
app,
isLoading,
router: routerReducer
});
export default appReducer;
Store.js
import { createStore, applyMiddleware } from 'redux';
import { routerMiddleware} from 'react-router-redux';
import createHistory from 'history/createBrowserHistory';
import thunk from 'redux-thunk';
import appReducer from './reducers/app';
export const history = createHistory()
const middleware = routerMiddleware(history);
const store = createStore(appReducer, applyMiddleware(middleware, thunk));
export default store;
If you hone in on Actions.js where in the loadAtms function I:
Fetch my atms
Dispatch receivedAtms
Dispatch Completed
When I dispatch completed() it clear my atms collection. I'm not entirely sure. I would not expect that since the states between the two reducers are separate. My expectation is:
After I've fired completed() I do not expect it to clear my collection of atms. The resulting state after calling completed() should look like this:
{
isLoading: false,
app: {atms: [{id: 1}, {id: 2}, {id: 3}]}
}
currently what is happening is this:
{isLoading: false, app: {}}
Any thoughts on what I may have done wrong here.
Your atms reducer is returning {} if the action isn't one it is looking for. Instead, you should be returning state I believe. So:
const app = (state = {}, action) => {
switch (action.type) {
case 'RECIEVED_ATMS':
return {
atms: action.atms
}
default:
return state;
}
}