I am unable to dispatch an action to reducer.
State comes out just fine. Not sure where I have gone wrong.
For other user reducer, dispatch works just fine. my project has nested drawer navigation > tabs navigation > stack navigation and current page is the 3rd stack screen. Not sure if that is the issue or what.
Store.js
import { combineReducers, createStore } from '#reduxjs/toolkit';
import cartReducer from './cartSlice';
import userReducer from './userSlice';
const rootReducer = combineReducers({
userReducer: userReducer,
cartReducer: cartReducer,
})
const configureStore = () => createStore(rootReducer)
export default configureStore
cartSlice.js
const initialState = {
cart: [{ key: 1, data: { freq: 'Daily', duration: 30 } }]
}
const cartReducer = (state = initialState, action) => {
switch (action.types) {
case "ADD_TO_CART":
console.log(action)
return {
...state,
cart: [...state.cart, { key: 2, data: action.data }]
}
case "REMOVE_FROM_CART":
const idx = state.cart.map((cartItem) => (
cartItem.key === action.id
))
const tempNewCart = [...state.cart]
if (idx >= 0) {
tempNewCart.splice(idx, 1)
}
return { ...state, cart: tempNewCart }
case "CLEAR_CART":
return {
...state,
cart: []
}
default:
return state
}
}
export default cartReducer
SubscriptionQuantity Component
const mapStateToProps = (state) => {
console.log(state)
return {
cart: state.cartReducer.cart
}
}
const mapDispatchToProps = (dispatch) => {
// console.log(dispatch)
return {
addtoCartStore: (freq, duration) => dispatch({
type: 'ADD_TO_CART',
data: {
freq: freq,
duration: duration
}
})
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SubscriptionQuantity)
function call for dispatch
const addtoCart = () => {
addtoCartStore('Alternate', 30)
navigation.navigate("CartScreen")
}
The issue was with the switch statement!!!!
it should be switch(action.type) not action.types. that's why it was going in on the default route. changed the typo and works fine now.!
Related
Im implementing redux in my react native app, I need to create 2 reducers, one of them is designed to manage login state and the other to manage some process status.
Reducers:
isLogged
processStatus
processStatus Actions:
export function start() {
return {
type: 'START_LOADING'
}
}
export function stop() {
return {
type: 'END_LOADING'
}
}
isLogged Actions:
export function signIn() {
return {
type: 'SIGN_IN'
}
}
export function loadFinished() {
return {
type: 'LOAD_FINISHED'
}
}
export const signOut = userAuthParams => (
{
type: 'SIGN_OUT'
}
);
export const firstLogin = userAuthParams => (
{
type: 'FIRST_LOGIN'
}
);
export const fbLogin = userAuthParams => (
{
type: 'FB_LOGIN'
}
);
process Reducer:
const isLoading = (state = false, action) => {
switch (action.type) {
case 'START_LOADING':
return 1;
case 'END_LOADING':
return 0
default:
return 0
}
}
export default isLoading
isLogged Reducer
const isLogged = (state = false, action) => {
switch (action.type) {
case 'SIGN_IN':
return 2;
case 'LOAD_FINISHED':
return 1
case 'SIGN_OUT':
return -1
case 'FIRST_LOGIN':
return 3
case 'FB_LOGIN':
return 4
default:
return 0
}
}
export default isLogged
root Reducer:
import isLogged from './IsLoggedReducer'
import process from './ProcessReducer'
import { combineReducers } from 'redux'
const allReducers = combineReducers({
isLogged: isLogged,
processState: process
})
export default allReducers
store
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk';
import allReducers from './index'
const store = createStore(allReducers, applyMiddleware(thunk))
export default store
Mapping state and actions to props
const mapStateToProps = (state) => {
const { processState, isLogged} = state
return { processState, isLogged }
};
const mapDispatchToProps = (dispatch) => {
return {
//loadFinished: () => dispatch({ type: 'LOAD_FINISHED' })
startProcess: bindActionCreators(start, dispatch),
endProcess: bindActionCreators(stop, dispatch),
}
};
export default connect(mapStateToProps, mapDispatchToProps)(ReportStickerStatus);
When I dispatch startProcess my app got redirected to a empty page
THERES NO ANY ERRORS ON CONSOLE
UPDATE [ SOLVED ]
I had to return the state in each action, even in default
import initialState from './initialState'
const isLogged = (state = initialState, action) => {
switch (action.type) {
case 'SIGN_IN':
return {
...state,
value: 2
};
case 'LOAD_FINISHED':
return {
...state,
value: 1
};
case 'SIGN_OUT':
return {
...state,
value: -1
};
case 'FIRST_LOGIN':
return {
...state,
value: 3
};
case 'FB_LOGIN':
return {
...state,
value: 4
};
default:
return {
...state
};
}
}
export default isLogged
I used redux persist with asyncstorage for offline data, but after i clear my app (like swipe right to delete it from the processes in my android phone), my data is gone the next time i open the app again.
Here is the relevant code:
// src/reduxActions/store.ts
import { configureStore } from '#reduxjs/toolkit'
import { combineReducers, Store } from 'redux';
import {
Persistor,
persistStore,
persistReducer,
} from 'redux-persist'
import AsyncStorage from '#react-native-async-storage/async-storage';
import AuthReducer from './auth/reducer';
import ItemReducer from './item/reducer';
import autoMergeLevel1 from 'redux-persist/lib/stateReconciler/autoMergeLevel1';
const rootReducer = combineReducers({
auth: AuthReducer,
item: ItemReducer,
});
const persistConfig = {
key: 'root',
storage: AsyncStorage,
whitelist: [],
storageReconciler: autoMergeLevel1,
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
export type RootState = ReturnType<typeof rootReducer>;
let store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
}),
});
export function getStore(): Store<RootState> {
return store;
}
export const persistor = persistStore(store);
export type AppDispatch = typeof store.dispatch;
// src/reduxActions/item/reducer.ts
import { createSlice } from '#reduxjs/toolkit';
import { Item } from 'models/item';
import { createItem, getItems, updateItem, deleteItem } from './actions';
interface ItemState {
items: {
[index: string]: Item,
}
itemIds: string[],
offlineItems: {
[index: string]: Item,
},
lastOfflineItemId: string,
}
const initialState: ItemState = {
items: {},
itemIds: [],
offlineItems: {},
lastOfflineItemId: '0',
}
const itemSlice = createSlice({
name: 'item',
initialState,
reducers: {
createOfflineItem(state, action) {
const id = (parseInt(state.lastOfflineItemId)+1).toString();
const item = {
"id": id,
"createdAt": Date.now(),
}
state.offlineItems[id] = { ...item, ...action.payload };
state.lastOfflineItemId = id;
},
toggleOfflineIsFavorite(state, action) {
state.offlineItems[action.payload.id].isFavorite = action.payload.isFavorite;
},
editOfflineItem(state, action) {
state.offlineItems[action.payload.id] = {
...state.offlineItems[action.payload.id],
...action.payload,
};
},
deleteOfflineItem(state, action) {
delete state.offlineItems[action.payload];
}
},
extraReducers: (builder) => {
builder.addCase(createItem.fulfilled, (state, action) => {
state.items[action.payload.id] = action.payload;
state.itemIds.unshift(action.payload.id);
})
builder.addCase(getItems.fulfilled, (state, action) => {
state.items = action.payload.results.reduce(function(map: { [index: string]: Item }, item: Item) {
map[item.id] = item;
return map;
}, {});
state.itemIds = action.payload.results.map((item: Item) => item.id);
})
builder.addCase(updateItem.fulfilled, (state, action) => {
state.items[action.payload.id] = action.payload;
})
builder.addCase(deleteItem.fulfilled, (state, action) => {
state.itemIds = state.itemIds.filter(id => id != action.payload);
delete state.items[action.payload];
})
builder.addDefaultCase((state) => {
return state;
})
},
})
const { actions, reducer } = itemSlice;
export const { createOfflineItem, toggleOfflineIsFavorite, editOfflineItem, deleteOfflineItem } = actions;
export default reducer;
Here is the complete code: https://www.github.com/roumanite/wmi-mobile
I want to be able to store offline data that will never go away, not with a phone restart and can also be backup & synced. I guess it will go away only if uninstalled & cleared completely
How can I fix this? thanks
You can update const persistConfig object:
const persistConfig = {
key: 'root',
storage: AsyncStorage,
whitelist: ['auth', 'item'],
storageReconciler: autoMergeLevel1,
};
My Redux thunk dispatch was working until i made a single API call but stopped working after using combineReducers for multiple api calls.
This is my component code :
const mapStateToProps = state => ({
loading: state.loading,
data: state.data,
error: state.error,
networkError: state.networkError
})
const mapDispatchToProps = {
login
}
These are my Actions :
export const GET_LOGIN_LOADING = "GET_LOGIN_LOADING"
export const GET_LOGIN_SUCCESS = "GET_LOGIN_SUCCESS"
export const GET_LOGIN_ERROR = "GET_LOGIN_ERROR"
export const GET_REGISTER_LOADING = "GET_REGISTER_LOADING"
export const GET_REGISTER_SUCCESS = "GET_REGISTER_SUCCESS"
export const GET_REGISTER_ERROR = "GET_REGISTER_ERROR"
This is my reducer for login and register actions :
import * as Actions from './Actions'
const loginState = {
loginLoading: false,
loginData: [],
loginError: '',
}
const registerState = {
registerLoading: false,
registerData: [],
registerError: '',
}
export const loginReducer = (state = { loginState }, action) => {
switch (action.type) {
case Actions.GET_LOGIN_LOADING:
return {
...state,
loginLoading: action.payload
}
case Actions.GET_LOGIN_SUCCESS:
return {
...state,
loginData: action.payload,
loginLoading: false,
}
case Actions.GET_LOGIN_ERROR:
return {
...state,
loginError: action.payload,
loginLoading: false,
}
default: return loginState
}
}
export const registerReducer = (state = { registerState }, action) => {
switch (action.type) {
case Actions.GET_REGISTER_LOADING:
return {
...state,
registerLoading: action.payload
}
case Actions.GET_REGISTER_SUCCESS:
return {
...state,
registerData: action.payload,
registerLoading: false,
}
case Actions.GET_REGISTER_ERROR:
return {
...state,
registerError: action.payload,
registerLoading: false,
}
default: return registerState
}
}
My Redux Store Code :
import { createStore, applyMiddleware, combineReducers } from 'redux'
import thunk from 'redux-thunk'
import{ loginReducer, registerReducer } from '../redux/Reducer'
const reducer = combineReducers({loginReducer, registerReducer})
export default createStore(reducer, applyMiddleware(thunk))
Finally my thunk code used for making API calls :
export const login = (countryCode, phone, password) => {
const userName = {
countryCode,
phone
}
return dispatch => {
dispatch(getLoginLoading(true))
service.post('login', {
userName,
password
})
.then(response => {
console.log(response.data)
dispatch(getLoginSuccess(response.data))
})
.catch(error => {
console.log(error)
dispatch(getLoginError(error.response.data))
})
}
}
export const register = (name, countryCode, phone) => {
return dispatch => {
dispatch(getRegisterLoading(true))
service.post('register', {
name,
countryCode,
phone,
})
.then(response => {
console.log(response.data)
dispatch(getRegisterSuccess(response.data))
})
.catch(error => {
console.log(error.response)
dispatch(getRegisterError(error.response))
})
}
}
Finally found an answer by myself. When you are use combine reducers, Redux creates a nested state object for each reducer, Hence when accessing the state you should use :
const reducer = combineReducers({loginReducer, registerReducer})
const mapStateToProps = state => ({
loading: state.loginReducer.loading,
data: state.loginReducer.data,
error: state.loginReducer.error,
networkError: state.loginReducer.networkError
})
This is the filterAction action that emits a value to to the persisted reducer. This action is called with dispatch() in a dropdown in a component.
import { SORT_BY_TITLE, SORT_BY_RELEASED_AT } from './types';
// sort by title
export const sortByTitle = () => ({
type: SORT_BY_TITLE
});
// sort by released at
export const sortByReleasedAt = () => ({
type: SORT_BY_RELEASED_AT
});
The corresponding filterReducer
import { SORT_BY_TITLE, SORT_BY_RELEASED_AT } from '../actions/types';
const initialState = {
sortBy: 'title'
};
export default function(state = initialState, action) {
switch(action.type) {
case SORT_BY_TITLE:
return {
...state,
sortBy: 'title'
};
case SORT_BY_RELEASED_AT:
return {
...state,
sortBy: 'releasedAt'
};
default:
return state;
}
};
The filterReducer value is the one persisted in the main combined reducer.
export default combineReducers({
books: booksReducer,
book: bookReducer,
form: addBookFormReducer,
filter: filterReducer
});
The app's store
import { createStore, applyMiddleware } from 'redux';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
const middleware = [thunk];
const persistConfig = {
key: 'root',
storage,
whitelist: ['filter']
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = createStore(persistedReducer, {}, applyMiddleware(...middleware));
export const persistor = persistStore(store);
The value persisted on the filter part of the main reducer gets displayed in the dropdown changes on which call the filterAction with dispatch().
Here's the booksReducer that creates the books part of the app's store so books are displayed in a component.
import {
GET_BOOKS,
BOOKS_LOADING,
DELETE_BOOK,
SORT_BY_TITLE,
SORT_BY_RELEASED_AT
} from '../actions/types';
const initialState = {
books: [],
loading: false
};
const booksReducer = (state = initialState, action) => {
switch(action.type) {
case BOOKS_LOADING:
return {
...state,
loading: true
};
case GET_BOOKS:
return {
...state,
books: action.payload,
loading: false
};
case DELETE_BOOK:
return {
books: [...state.books.filter(book => book._id !== action.payload.id)]
};
case SORT_BY_TITLE:
return {
...state,
books: [...state.books.sort((a, b) => a.title < b.title ? -1 : 1 )]
};
case SORT_BY_RELEASED_AT:
return {
...state,
books: [...state.books.sort((a, b) => a.releasedAt < b.releasedAt ? -1 : 1 )]
};
default:
return state;
}
};
export default booksReducer;
The filter part of the main reducer persists Ok on the page reload however the books list is displayed with the default by title sort.
How do I get the app to persist the sort on the page reload? The complete repo is on https://github.com/ElAnonimo/booklister
On the BookList load the getBooks() action return reset the sorted books list to its default sorting so it was easier to persist only the filter prop of the store then sort the books list on each load of the BookList component.
The changes were made
to booksReducer
import {
GET_BOOKS,
BOOKS_LOADING,
DELETE_BOOK
} from '../actions/types';
const initialState = {
books: [],
loading: false
};
const booksReducer = (state = initialState, action) => {
switch(action.type) {
case BOOKS_LOADING:
return {
...state,
loading: true
};
case GET_BOOKS:
return {
...state,
books: action.payload,
loading: false
};
case DELETE_BOOK:
return {
books: [...state.books.filter(book => book._id !== action.payload.id)]
};
default:
return state;
}
};
export default booksReducer;
to BookList
class BookList extends Component {
componentDidMount() {
this.props.getBooks();
}
applySorting(books) {
const sortBy = this.props.filter.sortBy;
if (!sortBy) {
return books;
}
return books.sort((a, b) => a[sortBy] < b[sortBy] ? -1 : 1);
}
render() {
const { books, loading } = this.props.books;
let booksContent;
if (!books || loading) {
booksContent = <Spinner />;
} else {
if (books.length > 0) {
booksContent = this.applySorting(books).map(book => <BookItem book={book} key={book._id} />);
} else {
booksContent = <h4>No books found</h4>;
}
}
...
...
I follow the demo to create a project. I used react-native with redux, then I get this error "undefined is not a function(evaluating '(0, _redux.createStore)(_todoListRedux.reducer)')`".
This is my code:
index.android.js
import {AppRegistry, View} from 'react-native'
import {createStore} from 'redux'
import {reducer} from './todoListRedux'
const store = createStore(reducer)
import App from './App'
const AppWithStore = () => <App store={store} />
AppRegistry.registerComponent('redux', () => AppWithStore)
todoListRedux.js
export const types = {
ADD: 'ADD',
REMOVE: 'REMOVE',
}
export const actionCreators = {
add: (item) => {
return {type: types.ADD, payload: item}
},
remove: (index) => {
return {type: types.REMOVE, payload: index}
}
}
const initialState = {
todos: ['Click to remove', 'Learn React Native','Write Code','Ship App'],
}
export const reducer = (state = initialState, action) => {
const {todos} = state
const {type, payload} = action
switch(type) {
case types.ADD: {
return {
...state,
todos: [payload, ...todos],
}
}
case types.REMOVE: {
return {
...state,
todos: todos.filter((todo,i) => i !== payload),
}
}
}
return state
}
any help will be appreciated!
The store added in createStore. Should be a store with combineReducers.
And as far as I know, the middleware should be passed as second parameter in the createStore.
Something like.
import { applyMiddleware, createStore } from 'redux';
import rootReducer from '../store';
const middleware = applyMiddleware({you can pass here (logger or thunk, or the available options)});
//example
const middleware = applyMiddleware(thunk);
const store = createStore(rootReducer, middleware);