I am pretty new to Redux and the whole Redux-Saga thing and wanted to use React-Boilerplate to try a small project that basically just makes an API call and iterates over the data. And I currently have a problem I've been stuck at for hours. Maybe you have an idea?
My React Component looks like this:
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { compose } from 'redux';
import { useInjectSaga } from 'utils/injectSaga';
import { useInjectReducer } from 'utils/injectReducer';
import {
makeSelectDevices,
makeSelectLoading,
makeSelectError
} from './selectors';
import reducer from './reducer';
import { fetchDevices } from './actions';
import saga from './saga';
export function LeafletMap(props) {
const {devices, loading, error, fetchDevices } = props;
useInjectReducer({ key: 'leafletMap', reducer });
useInjectSaga({ key: 'leafletMap', saga });
useEffect(() => {
fetchDevices();
}, [fetchDevices]);
if (loading) return(<div>Loading...</div>)
return (
<div>
{ !error ?
<Map center={[47.3, 9.9]} zoom={9} style={{height: '500px'}}>
<TileLayer
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
attribution='© OpenStreetMap contributors'
/>
{ devices && devices.map((device)=> {
let coordinates = [device.latitude, device.longitude];
return (
<Marker key={device.id} position={coordinates}></Marker>
);
})}
</Map>
: ''
}
</div>
);
};
LeafletMap.propTypes = {
devices: PropTypes.array,
loading: PropTypes.bool,
error: PropTypes.any,
};
const mapStateToProps = createStructuredSelector({
devices: makeSelectDevices(),
loading: makeSelectLoading(),
error: makeSelectError(),
});
function mapDispatchToProps(dispatch) {
return {
fetchDevices: () => dispatch(fetchDevices())
};
}
const withConnect = connect(
mapStateToProps,
mapDispatchToProps,
);
export default compose(withConnect)(LeafletMap);
When my component mounts I use the useEffect Hook to dispatch an action that I bound to my props using mapDispatchToProps. The actions file looks like this:
import {
FETCH_DATA,
FETCH_DATA_ERROR,
FETCH_DATA_SUCCESS,
CLICK_DEVICE
} from './constants';
export function fetchDevices() {
return {
type: FETCH_DATA,
};
}
export function fetchDevicesSuccess(devices) {
return {
type: FETCH_DATA_SUCCESS,
devices
};
}
export function fetchDevicesError(error) {
return {
type: FETCH_DATA_ERROR,
error
};
}
My saga then reacts to the FETCH_DATA action and calls a generator to fetch the data from my local API:
import { all, call, put, takeEvery } from 'redux-saga/effects';
import request from 'utils/request';
import { fetchDevicesSuccess, fetchDevicesError } from './actions';
import { FETCH_DATA } from './constants';
function* fetchDevicesAsync() {
yield takeEvery(FETCH_DATA, fetchAllDevices);
}
function* fetchAllDevices() {
try {
const requestUrl = '/api/devices';
const devices = yield call(request, requestUrl);
yield put(fetchDevicesSuccess(devices));
} catch (error) {
yield put(fetchDevicesError(error.toString()));
}
}
export default function* rootSaga() {
yield all([fetchDevicesAsync()]);
}
This in return should trigger my reducer which looks as follows:
import produce from 'immer';
import {
FETCH_DATA,
FETCH_DATA_ERROR,
FETCH_DATA_SUCCESS,
} from './constants';
export const initialState = {
devices: [],
loading: true,
error: false,
};
/* eslint-disable default-case, no-param-reassign */
const leafletMapReducer = (state = initialState, action) =>
produce(state, () => {
switch (action.type) {
case FETCH_DATA:
state.loading = true;
state.error = false;
break;
case FETCH_DATA_ERROR:
state.loading = false
state.error = action.error;
break;
case FETCH_DATA_SUCCESS:
state.loading = false;
state.error = false;
state.devices = action.devices;
break;
}
});
export default leafletMapReducer;
My problem here is that everything seems to work but my action is neither being displayed in Redux DevTools nor does my component update after the initial render. It seems as if the action is being dispatched before the ##INIT event.
Any idea why this happens?
Thanks in advance!
EDIT:
Just in case it has something to do with my selectors:
import { createSelector } from 'reselect';
import { initialState } from './reducer';
/**
* Direct selector to the leafletMap state domain
*/
const selectLeafletMapDomain = state => state.leafletMap || initialState;
/**
* Other specific selectors
*/
const makeSelectDevices = () =>
createSelector(
selectLeafletMapDomain,
leafletMapState => leafletMapState.devices
);
const makeSelectLoading = () =>
createSelector(
selectLeafletMapDomain,
leafletMapState => leafletMapState.loading,
);
const makeSelectError = () =>
createSelector(
selectLeafletMapDomain,
leafletMapState => leafletMapState.error,
);
/**
* Default selector used by LeafletMap
*/
const makeSelectLeafletMap = () =>
createSelector(selectLeafletMapDomain, leafletMapState => leafletMapState.toJS());
export default makeSelectLeafletMap;
export {
selectLeafletMapDomain,
makeSelectDevices,
makeSelectLoading,
makeSelectError
};
Found the problem myself :)
The problem was in my reducer:
const leafletMapReducer = (state = initialState, action) =>
produce(state, () => { // <-- here
switch (action.type) {
case FETCH_DATA:
state.loading = true;
state.error = false;
break;
I here wrongly mutated my state which leads to the error. The correct solution is:
const leafletMapReducer = (state = initialState, action) =>
produce(state, draftState => { // use draftState instead of normal state
switch (action.type) {
case FETCH_DATA:
draftState.loading = true; //<------
draftState.error = false; //<------
break;
Related
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 recently started using redux for a new personal project. It worked pretty well until I started using "combineReducers". Whenever I click "Fetch todos" both my user as well as my todo reducer get updated and even though they have different data field names both get the same data. Now I probably did some wrong encapsulation here. But no matter how often I went over the docs, I just cannot see what I am doing wrong.
My store initialization script:
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import toDoReducer from './todos/reducer';
import userReducer from './users/reducer';
const rootReducer = combineReducers({
todosSlice: toDoReducer,
usersSlice: userReducer
});
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)));
export default store;
gets injected into index:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './containers/app/App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
ReactDOM.render(<Provider store={ configureStore }><App /></Provider>, document.getElementById('root'));
serviceWorker.unregister();
My app hold the logic for the todo container
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as todoActions from '../../store/todos/actions';
import UserContainer from '../usersContainer/UserContainer';
class App extends Component {
componentDidMount() {
console.log(this.props);
}
render() {
let loading = '';
let error = '';
let todos = [];
// check whether the component is fetching data
this.props.loading === true ? loading = <p>Loading...</p> : loading = '';
// check if there was an error
this.props.error && this.props.loading === false ? error = <p>There was an error</p> : error = '';
// map the todos in the desired html markup.
todos = this.props.todos.map( todo => {
return <div key={todo.id}> name: {todo.title} </div>
});
return (
<div className="App">
{/* <UserContainer /> */}
{loading}
{error}
<p onClick={() => this.props.onFetchTodos()}>Fetch Todos</p>
{todos}
</div>
);
}
}
const mapStateToProps = state => {
return {
error: state.todosSlice.error,
loading: state.todosSlice.loading,
todos: state.todosSlice.todos
}
}
const mapDispatchToProps = dispatch => {
return {
onFetchTodos: () => dispatch(todoActions.fetchTodos())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
Which has the following actions:
import axios from 'axios';
export const FETCH_TODOS = 'FETCH_TODOS';
export const GET_TODOS_STARTED = 'GET_TODOS_STARTED';
export const FETCH_TODOS_SUCCESS = 'FETCH_TODOS_SUCCESS';
export const FETCH_TODOS_FAILURE = 'FETCH_TODOS_FAILURE';
export const fetchRequest = () => {
return dispatch => {
dispatch(getTodoStarted());
axios.get('https://one365-api-dev.azurewebsites.net/api/teams/')
.then(result => {
dispatch(fetchTodosSucces(result));
}).catch(error => {
dispatch(fetchTodoFailure(error));
});
}
}
const getTodoStarted = () => ({
type: GET_TODOS_STARTED
});
const fetchTodosSucces = todos => ({
type: FETCH_TODOS_SUCCESS,
payload: {
...todos
}
});
const fetchTodoFailure = error => ({
type: FETCH_TODOS_FAILURE,
payload: {
error
}
});
export const fetchTodos = () => {
return (dispatch => {
dispatch(fetchRequest());
});
}
And it's reducer
import * as actions from './actions';
const initialState = {
error: null,
loading: false,
todos: []
}
const todosReducer = (state = initialState, action) => {
switch(action.type) {
case actions.GET_TODOS_STARTED: {
console.log('fetch todo state', state)
return {
...state,
loading: state.loading = true
};
}
case actions.FETCH_TODOS_SUCCESS: {
const todos = action.payload.data;
return {
...state,
loading: false,
todos: state.todos = todos
};
}
case actions.FETCH_TODOS_FAILURE: {
const error = action.payload.error;
return {
...state,
loading: false,
error: state.error = error
};
}
default: {
return state;
}
}
}
export default todosReducer;
The Users Component
import React from 'react';
import { connect } from 'react-redux';
import * as userActions from '../../store/users/actions';
class UserContainer extends React.Component {
render () {
let loading = '';
let error = '';
let users = [];
// check whether the component is fetching data
this.props.usersLoading === true ? loading = <p>Loading...</p> : loading = '';
// check if there was an error
this.props.usersError && this.props.loading === false ? error = <p>There was an error</p> : error = '';
// map the users in the desired html markup.
users = this.props.users.map( user => {
return <div key={user.id}> name: {user.title} </div>
});
return (
<div className="Users">
{loading}
{error}
<p onClick={() => this.props.onFetchUsers()}>Fetch Users</p>
{users}
</div>
);
}
}
const mapStateToProps = state => {
return {
usersError: state.usersSlice.error,
usersLoading: state.usersSlice.loading,
users: state.usersSlice.users
}
}
const mapDispatchToProps= (dispatch) => {
return {
onFetchUsers: () => dispatch(userActions.fetchUsers())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(UserContainer);
the user actions:
import axios from 'axios';
export const FETCH_USERS = 'FETCH_TODOS';
export const FETCH_USERS_STARTED = 'GET_TODOS_STARTED';
export const FETCH_USERS_SUCCESS = 'FETCH_TODOS_SUCCESS';
export const FETCH_USERS_FAILURE = 'FETCH_TODOS_FAILURE';
export const fetchRequest = () => {
return dispatch => {
dispatch(fetchUsersStarted());
axios.get('https://one365-api-dev.azurewebsites.net/api/me')
.then(result => {
dispatch(fetchUsersSuccess(result));
}).catch(error => {
dispatch(fetchUsersFailure(error));
});
}
}
export const fetchUsersSuccess = (users) => {
return {
type: FETCH_USERS_SUCCESS,
payload: {
...users
}
}
}
export const fetchUsersStarted = () => ({
type: FETCH_USERS_STARTED
});
export const fetchUsersFailure = (error) => {
return {
type: FETCH_USERS_FAILURE,
payload: {
error
}
}
}
export const fetchUsers = () => {
return dispatch => {
dispatch(fetchRequest())
}
};
And it's reducer:
import * as actions from './actions';
const initialState = {
error: '',
loading: false,
users: []
}
const userReducer = (state = initialState, action) => {
switch(action.type) {
case actions.FETCH_USERS_STARTED: {
console.log('fetch users state', state)
return {
...state,
loading: state.loading = true
}
}
case actions.FETCH_USERS_SUCCESS: {
const users = action.payload.data;
return {
...state,
loading: false,
users: state.users = users
}
}
case actions.FETCH_USERS_FAILURE: {
const error = state.payload.error;
return {
...state,
loading: false,
error: state.error = error
}
}
default: {
return state;
}
}
}
export default userReducer;
Now when I run my DEV server I only see the fetch todo button. I commented out the users on click handler to see if it was an event bubble going up. Bu t this wasn't the case.
Once the app load redux dev tools shows the state as follows:
but once i click the fetch todo's handler. Both todos and users get filled.
I appreciate anyone who read though so much (boilerplate) code. I probably made a problem encapsulating my state. but again after reading many tutorials I still cannot find my issue.
You have a copy/paste issue. You changed the names of the constants for your "USERS" actions, but left the values the same as the "TODOS" actions.
export const FETCH_USERS = 'FETCH_TODOS';
export const FETCH_USERS_STARTED = 'GET_TODOS_STARTED';
export const FETCH_USERS_SUCCESS = 'FETCH_TODOS_SUCCESS';
export const FETCH_USERS_FAILURE = 'FETCH_TODOS_FAILURE';
I assume you meant to have:
export const FETCH_USERS = 'FETCH_USERS';
export const FETCH_USERS_STARTED = 'FETCH_USERS_STARTED';
export const FETCH_USERS_SUCCESS = 'FETCH_USERS_SUCCESS';
export const FETCH_USERS_FAILURE = 'FETCH_USERS_FAILURE';
please help me with a situation around react-redux.
I have issues in updating the state ( which i try to do in a immutable way ), and the component where I use it, never rerenders.
/store/users/index.js
import { USER_LOGIN, USER_LOGOUT} from './actionTypes';
import {
USER_LOGIN,
USER_LOGOUT
} from './actionTypes';
const usersReducer = (user = {}, action) => {
switch(action) {
case USER_LOGIN : /* tried to change id manually, not based on payload, just to see if it works */
return {
...user,
name: 'New user',
isLoggedIn: true
}
case USER_LOGOUT:
return {
...user,
name: 'Anonymous',
isLoggedIn: false
}
default:
return user;
}
}
export default usersReducer;
/store/loops/index.js
import {
LOOPS_ADD
} from './actionTypes';
const loopsReducer = (loops =[], action) => {
switch(action) {
case LOOPS_ADD:
return [
...loops,
action.payload
]
default:
return loops;
}
}
export default loopsReducer;
/store/users/actions.js
import {
USER_LOGIN,
USER_LOGOUT
}
from './actionTypes';
export const userLogin = (newUser) => {
return {
type: USER_LOGIN,
}
}
export const userLogout = () => {
return {
type: USER_LOGOUT
}
}
/store/index.js
import {
createStore,
combineReducers,
applyMiddleware,
compose
} from 'redux';
/* import reducers */
import usersReducer from './users';
import loopsReducer from './loops';
/* import middleware */
import logger from 'redux-logger';
/* initial state */
const initialState = {
user: {
name: 'Anonymous',
isLoggedIn: false,
email: null,
lastLogin: null,
firstTimeLogin: false,
authProvider: 'email',
friendsCount: null
},
loops: []
}
/* reducers */
const rootReducer = combineReducers({
user: usersReducer,
loops: loopsReducer,
});
/* store creation */
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const middleware = composeEnhancers(applyMiddleware(logger));
const store = createStore(
rootReducer,
initialState,
middleware
);
export default store;
/pages/HomeScreen/HomeScreen.js
import React from 'react';
import {connect} from 'react-redux'
import {userLogin} from '../../store/users/actions';
class Home extends React.PureComponent {
render() {
return (
<React.Fragment>
<NavBar>Nav</NavBar>
<Listing>
<Filter>Filter</Filter>
<Card>
<CardAvatar>Avatar</CardAvatar>
<CardBody>Rest of the card</CardBody>
Salut {this.props.name}
<button onClick={() => this.props.login()}>login</button>
</Card>
</Listing>
</React.Fragment>
)
}
}
const mapStateToProps = (state) => {
return {
name: state.user.name
}
}
const mapDispatchToProps = (dispatch) => {
return {
login: () => dispatch(userLogin()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
I don't know if it matters but i'm using styled-components for styling.
I tried removing the combineReducers method, and remain with a single reducer, that did not help.
I tried removing the react-logger middleware, I tried using Immutable.jsbut even if the actions is triggered the redux state does not update, and the Home component doesn't rerender as well.
It seems you are not setting the user on your action:
export const userLogin = (newUser) => {
return {
type: USER_LOGIN,
}
}
Looks like it needs to be:
export const userLogin = (newUser) => {
return {
type: USER_LOGIN,
user: newUser
}
}
Without this there will be no change in state and also no render needed.
Of course, you would then need to change your reducer function so that it is dynamic:
case USER_LOGIN :
return {
...user,
name: action.user.name,
isLoggedIn: true
}
The answer was simple.
I was doing :
const loopsReducer = (loops =[], action) => {
switch(action) {
case LOOPS_ADD:
return [
...loops,
action.payload
]
default:
return loops;
}
}
instead i should have done
const loopsReducer = (loops =[], action) => {
switch(action.type) {
case LOOPS_ADD:
return [
...loops,
action.payload
]
default:
return loops;
}
}
Notice the switch(action) before to switch(action.type)
Sorry for wasting your time, and thank you for all your replies !
**Hello! my problem is my state is not uploading, is always empty altough my actions brings data correct. Can anyone give me some help of what am I doing wrong ?
I think is something with the name or the combine reducers part.
Maybe I am not accesing data correct with my reducer or something like that **
The object I receive from the api call has this format {categories: Array(4), items: Array(50)}
Component
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import ItemList from '../components/ItemList/ItemList';
import { getItems } from './actions'
class ItemListContainer extends PureComponent {
async componentDidMount() {
const { getItems } = this.props;
await getItems()
console.log(this.props)
}
render() {
return <ItemList />;
}
}
const mapStateToProps = state => (
{
items: state.items.items,
});
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
getItems,
},
dispatch,
);
export default connect(mapStateToProps, mapDispatchToProps)(ItemListContainer);
actions.js
export const GET_ITEMS = 'GET_ITEMS';
export const GET_ITEM = 'GET_ITEM';
export const GET_ITEM_DESCRIPTION = 'GET_ITEM_DESCRIPTION';
export function getItems(query) {
return async function (dispatch) {
// const res = await fetch(`http://localhost:3000/api/items?q=${query}`)
const res = await fetch(`http://localhost:3000/api/items?q=ipad`)
const items = await res.json()
return dispatch({
type: 'GET_ITEMS',
items: items.items,
})
}
}
reducer.js
import { GET_ITEMS } from './actions';
const initialState = {
items: [],
itemsLoaded: false,
};
export default function(state = initialState, action) {
const { type, data } = action;
switch (type) {
case GET_ITEMS:
return {
...state,
items: data,
itemsLoaded: true,
};
default: {
return {
...state
}
}
}
}
I was accessing { data} in the reducer which of course it was empty. The correnct action was items.
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;
}
}