I'm working toward having my React/Redux app update the URL based on actions. I've done quite a bit of looking around. I thought I had a handle on it, but obviously I'm missing something. I have other reducers that respond correctly.
Currently, I'm just trying to log the action.
Routing Reducer
const initialState = { locationBeforeTransitions: null };
export default function routing(state = initialState, action)
{
switch (action.type)
{
case 'LOCATION_CHANGE':
console.log(action)
default:
return state
}
}
Store
/*
Things from other people
*/
import { createStore, applyMiddleware, compose } from 'redux'
import { syncHistoryWithStore } from 'react-router-redux';
import { browserHistory } from 'react-router'
import thunkMiddleware from 'redux-thunk'
import createLogger from 'redux-logger'
/*
Things from us
*/
import { fetchSuitesAndFails, fetchResults } from './actions/actions';
import rootReducer from './reducers/index'
const enhancers = compose(
window.devToolsExtension ? window.devToolsExtension() : f => f
);
const loggerMiddleware = createLogger()
/*
Store
*/
export const store = createStore(
rootReducer,
enhancers,
applyMiddleware(
thunkMiddleware, // lets us dispatch() functions
loggerMiddleware // neat middleware that logs actions
)
);
// Export the history so we can keep track of things
export const history = syncHistoryWithStore(browserHistory, store);
/*
Enable Hot Reloading for the reducers
We re-require() the reducers whenever any new code has been written.
Webpack will handle the rest
*/
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('./reducers/index', () => {
const nextRootReducer = require('./reducers/index').default
store.replaceReducer(nextRootReducer)
})
}
Index
/*
React router needs
*/
import { combineReducers } from 'redux'
import { routerReducer } from 'react-router-redux'
/*
Reducers
*/
import suite from './suite'
import suitesandfails from './suitesandfails'
import routing from './routing'
/*
Put them all together and return a single reducer
*/
const rootReducer = combineReducers({
suite,
suitesandfails,
routing,
routing: routerReducer
})
export default rootReducer
you can use string "##router/LOCATION_CHANGE" to catch the action.
react-router-redux provides const for that
import { LOCATION_CHANGE } from 'react-router-redux'
....
case LOCATION_CHANGE:
console.warn('LOCATION_CHANGE from your reducer',action)
return state
webpackbin DEMO
routerReducer code from react-router-redux
You can also import the constant from the 'react-router-redux'.
import { LOCATION_CHANGE } from 'react-router-redux'
...
case LOCATION_CHANGE:
console.warn('LOCATION_CHANGE from your reducer',action)
return state
Related
I have a function that check the role of a user. I want to base on that pass different reducer into a store, is that possible?
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { getUserRole } from './utils'
import { composeWithDevTools } from 'redux-devtools-extension'
import userReducers from './reducers'
import adminReducers from './reducers/admin'
//share reducer btw member and admin
let reducers
if (getUserRole() === 'member') {
reducers = userReducers
} else {
reducers = adminReducers
}
console.log('reducers', reducers) //undefined unless I refresh the page
const store = createStore(
reducers,
composeWithDevTools(
applyMiddleware(thunk)
)
)
export default store
I found a problem, reducers is undefined for the first time (like I login) unless I refresh it.
I am attempting to implement redux-persist to persist a specific reducer only, invoices, version, but in my case, my reducers are all gone.
This is the entry point:
import React, { Component } from "react";
import { Provider } from "react-redux";
import { createStore, applyMiddleware, compose } from "redux";
import { persistStore, persistCombineReducers } from "redux-persist";
import { createWhitelistFilter } from "redux-persist-transform-filter";
import { PersistGate } from 'redux-persist/es/integration/react';
import storage from "redux-persist/lib/storage";
import thunkMiddleware from "redux-thunk";
import { apiMiddleware } from "redux-api-middleware";
import reducers from "./actions/reducers";
import AppContainer from "./containers/App";
import FlashMessage from "./containers/common/FlashMessage";
import "./App.css"
const middleware = [thunkMiddleware, apiMiddleware];
const persistConfig = {
storage,
key: 'root',
transforms: [
createWhitelistFilter('invoices')
]
};
const store = createStore(
persistCombineReducers(persistConfig, reducers),
compose(
applyMiddleware(...middleware),
)
);
const persistor = persistStore(store);
export default class App extends Component {
render () {
return (
<Provider store={store}>
<PersistGate persistor={persistor}>
{/*<FlashMessage/>*/} // Since there is no reducer, this fails
<AppContainer/>
</PersistGate>
</Provider>
)
}
}
And this is the reducers file I have:
import { combineReducers } from "redux";
import { routerReducer as router } from "react-router-redux";
import app from "./app";
import invoices from "./invoices/invoices";
import recipients from "./recipients/recipients";
export const makeRootReducer = (asyncReducers) => {
return combineReducers({
router,
app,
invoices,
recipients,
...asyncReducers
})
};
export const injectReducer = (store, { key, reducer }) => {
store.asyncReducers[key] = reducer;
store.replaceReducer(makeRootReducer(store.asyncReducers))
};
export default makeRootReducer;
I have followed many suggestions but I must be missing something.
I found out why. After checking the source code, I saw the following documentation block of persistCombineReducers:
/**
* It provides a way of combining the reducers, replacing redux's #see combineReducers
* #param config persistence configuration
* #param reducers set of keyed functions mapping to the application state
* #returns reducer
*/
export function persistCombineReducers<S>(config: PersistConfig, reducers: ReducersMapObject): Reducer<S & PersistedState>;
The key words were "replacing redux's #see combineReducers".
Removing combineReducers from my reducers file to the following solved the issue:
import { routerReducer as router } from "react-router-redux";
import app from "./app";
import invoices from "./invoices/invoices";
import recipients from "./recipients/recipients";
const reducers = {
router,
app,
invoices,
recipients,
};
export default reducers;
I am a novice in Javascript and Redux, and I just created my first app based on this tutorial. I have a problem that the Store absolutely doesn't react to any dispatch call. I don't know if there is an error in the connection between Redux and React or in the store configuration itself.
Could you please help me with this problem?
This is snippes from my actions file where "addTodo" action is defined.
export const ADD_TODO = 'ADD_TODO';
let todoId = 0;
export const addTodo = (text) => ({
type: ADD_TODO,
id: todoId++,
text,
});
Below is my Store configuration.
import { createStore, applyMiddleware, compose } from 'redux';
import rootReducer from '../reducers';
import DevTools from '../containers/DevTools';
const enhancer = compose(
applyMiddleware(createLogger),
DevTools.instrument()
);
export default function configureStore(initialState) {
const store = createStore(rootReducer, initialState, enhancer);
if (module.hot) {
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers').default;
store.replaceReducer(nextRootReducer);
});
}
return store;
}
My index file where I try to call a dispatch function with "AddTodo" action creator. Similarly, I call this function in Redux containers, but it doesn't work for both.
import React from 'react';
import { render } from 'react-dom';
import configureStore from './store/configureStore';
import { addTodo } from './actions';
const store = configureStore();
store.subscribe(() =>
console.log(store.getState())
);
store.dispatch(addTodo('test'));
The whole project is placed on Github too. I will be thankful if you help me.
you forgot to invoke createLogger, try
const enhancer = compose(
applyMiddleware(createLogger())
...
);
webpackbin test
Here is my index.js where I initially dispatch an action to read my list of locations:
import 'babel-polyfill';
import React from 'react';
import { render } from 'react-dom';
import configureStore from './store/configureStore';
import {Provider} from 'react-redux';
import { Router, browserHistory } from 'react-router';
import routes from './routes';
import {loadLocationList} from './actions/locationActions';
import './css/styles.css';
const store = configureStore();
render(
<Provider store={store}>
<Router history={browserHistory} routes={routes} />
</Provider>,
document.getElementById('app')
);
Then here is my action where I get the data & then create an action out of it:
export function loadLocationListSuccess(alistingData) {
return { type: types.LOAD_LOCATION_LIST_SUCCESS, listingData: alistingData};
}
export function loadLocationList() {
return function(dispatch){ //we return a function that accepts a parameter, we just called it dispatch
//dispatch(fetchCallActions.fetchCallStart("")); // we dispatch a function fetchCallStart to indicate the start of our call, this is to keep in check with our asynchronous function calls
let link = 'http://example.com:8399/location';//our fetch url
console.log(link); //we log our link, just for debug purposes
return fetch(link) //start fetch
.then(function(response) {
return response.json();
}).then(function(json) {
dispatch(loadLocationListSuccess(json));
}).catch(function(ex) {
console.log('parsing failed', ex);
});
};
}
Then here is my reducer:
import * as types from '../actions/actionTypes';
export default function locationReducer(state = [], action) {
switch(action.type) {
case types.LOAD_LOCATION_LIST_SUCCESS:
return {listingData: action.listingData};
default:
return state;
}
}
Then here is my mapStateToProps & connect function:
function mapStateToProps(state, ownProps) {
return {
// we'll call this in our component -> this.props.listingData
listingData: state.listingData
};
}
export default connect(mapStateToProps, mapDispatchToProps)(homePage);
For some reason, it cannot read state.listingData or am I actually doing it wrongly? Anyone can help me with this problem?
I tried logging state.listingData and it showed undefined
Here is my configureStore:
import {createStore, applyMiddleware} from 'redux';
import rootReducer from '../reducers';
import reduxImmutableStateInvariant from 'redux-immutable-state-invariant';
import thunk from 'redux-thunk';
export default function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk, reduxImmutableStateInvariant())
);
}
Here is my combined Reducer:
import {combineReducers} from 'redux';
import courses from './courseReducer';
import locations from './locationReducer';
const rootReducer = combineReducers({
courses,
locations
});
export default rootReducer;
Did I not connect it to the store properly?
Recent update:
Logging JSON.stringify(state) in mapStateToProps would actually shows the result. Thanks guys.
The correct path turned out to be state.locations.listingData because I think in my combined Reducer I included the reducer as locations so maybe thats why the state for it is state.locations. Hope this helps anyone with the problem.
Can you show the code of configureStore file? The problem might be there, may be you forgot to add reducer to list of reducers.
Does the action works right? Did you log data before dispatch(loadLocationListSuccess(json));?
UPD:
Because of rootReducer. Each reducer creates their own key in store. When you combine your reducers in rootReducer, like:
import locations from './locationReducer';
const rootReducer = combineReducers({
courses,
locations
});
It creates store with this kind of structure:
const store = {
courses: {},
locations: {}
}
So, after that you dispatched action and reducer changed the data to this:
const store = {
courses: {},
locations: {
listingData: someData
}
}
If you want to access to listingData like: state.listingData, you need to change a little your reducer and combineReducer to:
export default function listingData(state = {}, action) {
switch(action.type) {
case types.LOAD_LOCATION_LIST_SUCCESS:
return action.listingData;
default:
return state;
}
}
...
import listingData from './locationReducer';
const rootReducer = combineReducers({
courses,
listingData
});
My code is working fine, but I have an annoying problem whenever I make a coding mistake and get a runtime error. For instance, in one of my JSX pages I did Date() instead of new Date() and instead of reporting the actual error, I got...
Uncaught Error: Expected the reducer to be a function.
Any error I make almost always shows up as this. It's being reported from createStore.js, which is in my configureStore.jsx code below.
Is there a way that I can get better error reporting that helps me identify the real problem? Any help or ideas are greatly appreciated!!!
Here's my setup for reference....
main.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ReduxRouter } from 'redux-router';
import configureStore from './store/configureStore'
import routes from './routes';
const rootEl = document.getElementById('app-container');
const store = configureStore();
ReactDOM.render(
<div>
<Provider store={store}>
<ReduxRouter routes={routes} />
</Provider>
</div>
, rootEl
);
configureStore.jsx
import { createHashHistory } from 'history';
import { applyMiddleware, createStore, compose } from 'redux';
import { reduxReactRouter } from 'redux-router';
import thunk from 'redux-thunk';
import promiseMiddleware from 'redux-promise-middleware';
import rootReducer from '../reducers/rootReducer';
import routes from '../routes';
export default function configureStore(initialState = {}) {
const history = createHashHistory();
const middlewares = [
thunk,
promiseMiddleware({
promiseTypeSuffixes: ['PENDING','SUCCESS','ERROR']
})
];
const toolChain = [
applyMiddleware(...middlewares),
reduxReactRouter({
routes,
history
})
];
const store = compose(...toolChain)(createStore)(rootReducer, initialState);
if (module.hot) {
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers/rootReducer');
store.replaceReducer(nextRootReducer);
});
}
return store;
}
rootReducer.jsx
import { combineReducers } from 'redux';
import { routerStateReducer } from 'redux-router';
import siteReducer from './siteReducer';
const rootReducer = combineReducers({
router: routerStateReducer,
sites: siteReducer
});
export default rootReducer;
siteReducer.jsx
import {GET_SITES} from '../actions/siteActions';
const defaultState = {
isPending: null,
isSuccess: null,
isError: null,
error: null,
data: null
};
export default function siteReducer(state = defaultState, action) {
switch (action.type) {
case `${GET_SITES}_PENDING`:
return {
...defaultState,
isPending: true
};
case `${GET_SITES}_SUCCESS`:
return {
...defaultState,
isSuccess: true,
error: false,
data: action.payload
};
case `${GET_SITES}_ERROR`:
return {
...defaultState,
isError: true,
error: action.payload
};
default:
return state;
}
}
Change the following line:
const nextRootReducer = require('../reducers/rootReducer');
To:
const nextRootReducer = require('../reducers/rootReducer').default;
Use export const variable_name instead of const variable_name whenever you want to export that variable.
For ex: rootReducer.jsx should be re-written as
import { combineReducers } from 'redux';
import { routerStateReducer } from 'redux-router';
import siteReducer from './siteReducer';
export const rootReducer = combineReducers({
router: routerStateReducer,
sites: siteReducer
});
export default rootReducer;
Note the extra export specifier with const rootReducer
My issue was importing Store from the root reducer path rather than the actual bundled store root (with devtools on the window and root reducer, middleware being composed, etc).
import Store from '../../../src/state/Store/reducer';
changed to
import Store from '../../../src/state/Store';