I'm learning Immutable.js, but I'm having difficulty to handle with Immutable.js in reducers.
I've declared my initialState like this:
import { fromJS } from 'immutable';
const INITIAL_STATE = fromJS({
users: {
isLoading: false,
items: []
}
});
I'm trying to modify the initialState but I'm getting an error: "state.setIn is not a function".
case 'FETCH_USERS_SUCCESS':
return state
.setIn(['users', 'isLoading'], false)
.setIn(['users', 'items'], action.users)
In the index.js, I'm declaring the deafult state as a Immutable Map() object:
let store = createStore(..., Map({}), composeEnhancers(...));
And in the combineReducers I'm using the 'redux-immutable'.
import { combineReducers } from 'redux-immutable';
What is the right way to modify the reducer state with Immutable.js?
toJS and fromJS are expensive operations.
You shouldn't do a fromJS of the either initial_state or state.
I suggest you to do this at your users reducer to have better performance and don't deal with toJS and fromJS. And also use in your code (selectors, components, views) a get to retrieve any value.
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
import { FETCH_USERS_SUCCESS } from './constants'; // or whatever you have your constants file
const initialState = new ImmutableMap({
isLoading: false,
items: new ImmutableList([]);
});
function users(state = initialState, action) {
switch (action.type):
case FETCH_USERS_SUCCESS:
return state.merge({
isFetching: false,
users: action.users
});
default:
return state;
}
Please, don't use immutable when creating the Store. You don't need there any immutable action since it's supposed the store is immutable.
So Immutable should be any data structure inside your reducer.
This is an example of a common configure-store file (or index in your case):
import { applyMiddleware, createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from 'state/reducers';
export default function configureStore(initialState = {}) {
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(thunk)),
);
// to query state in the console
window.appState = store.getState();
if (module.hot) {
const nextReducer = require('state/reducers');
module.hot.accept('state/reducers', () => {
store.replaceReducer(nextReducer);
});
}
return store;
}
And in the file state/reducers you have all the reducers of the different entities:
import { combineReducers } from 'redux';
import users from './users';
const ownReducers = {};
const appReducer = combineReducers({
...ownReducers,
// add here your reducers after importing the entity state
// i.e: myStuff: myStuff.reducer, etc...
users: claims.reducer,
});
const rootReducer = (state, action) => {
return appReducer(state, action);
};
export default rootReducer;
Best!
I assume that the state being returned within the reducer is not an immutable map. As far as I can see, you are just converting your initial state to an immutable map by using fromJS(...), you could try to do the same thing within your reducer.
return fromJS(state)
.setIn(.setIn(['users', 'isLoading'], false)
.setIn(['users', 'items'], fromJS(action.users))
But I'd recommend to take a look at this documentation, since it targets your demands.
Related
I have created a reducer called auth and persisted in this reducer.
I want to get auth value outside of the functional component or class component, for example in the utils. How can I do that?
authAction.js
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
export const LoginSuccess = (payload) => {
return {
type: LOGIN_SUCCESS,
payload
};
};
authReducer.js
import { LOGIN_SUCCESS } from './authAction';
// INITIAL TIMER STATE
const initialState = {
user: {}
};
// Auth REDUCER
export const authReducer = (state = initialState, { type, payload }) => {
switch (type) {
case LOGIN_SUCCESS:
return { ...state, user: payload };
default:
return state;
}
};
persist auth reducer
const reducers = {
auth: authReducer
};
const persistConfig = {
key: 'primary',
storage,
whitelist: ['auth'] // place to select which state you want to persist
};
This is not really "react-redux" way. Your store is a part of react application that is just big react component. However, you can create your store in separate module outside of your application and use it where you want importing instance of your store.
For example, you are creating your store in store.js:
// store.js
export default createStore(reducer, preloadedState, enhancers);
Then, you can import it inside your react application
// app.jsx
import store from '/path/to/store';
import { Provider } from 'react-redux';
function App () {
<Provider store={store}>{everything else}</Provider>
}
and inside your utils
// my-util.js
import store from '/path/to/store';
function util() {
// do whatever you want with same instance of store
// for example, return current state
return store.getState()
}
You can subscribe to store, if you need or just get current state. Or you can do complex stuff with replacing reducers for seamless client side updates.
I am trying to set up Redux in React for the first time and I can't seem to pass my initial state from the store to the component. My store file is setting state to the return value of the reducer. Here is what happens when I log this.props to the console
Component
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { exampleAction } from '../../actions';
class Header extends React.Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
console.log(this.props)
return (
<div>
<p>this is {this.props.examplePropOne}</p>
</div>
);
}
}
const mapStateToProps = state => ({
examplePropOne: state.examplePropOne,
examplePropTwo: state.examplePropTwo
});
const mapDispatchToProps = dispatch => {
return bindActionCreators({ exampleAction }, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(Header);
Reducer
import { EXAMPLE_ACTION } from './../actions/types'
const initialState = {
examplePropOne : 'Example Property One',
examplePropTwo : 'Example Property Two'
}
export default function (state = initialState, action) {
switch(action.type) {
case EXAMPLE_ACTION:
return {
...state,
examplePropOne: action.payload
}
default:
return state
}
}
Action
import { EXAMPLE_ACTION } from './types'
export const exampleAction = text => ({
type: EXAMPLE_ACTION,
payload: text,
})
[Edit]
Here is what happens when I log the state within mapStateToProps
import React from 'react';
import { createStore, combineReducers } from 'redux';
import reducers from '../reducers';
export const store = createStore(
combineReducers({
state: reducers
}),
);
With how combineReducers() was used with state passed in as a key, your mapStateToProps() would need to look like this instead to access examplePropOne and examplePropTwo:
const mapStateToProps = state => ({
examplePropOne: state.state.examplePropOne,
examplePropTwo: state.state.examplePropTwo
});
Given that combineReducers():
The state produced by combineReducers() namespaces the states of each
reducer under their keys as passed to combineReducers()
The issue is that:
export const store = createStore(
combineReducers({
state: reducers
}),
);
The key state passed to combineReducers() created a namespace/property of state. With the argument named state for the mapStateToProps(), requires that properties are accessed as state.state. This can probably be resolved by instead giving the key passed to combineReducers() a more descriptive name representing what is being used to manage in the store. For example, if it's related to authentication, it could be called some like auth. It would look like:
export const store = createStore(
combineReducers({
auth: reducers
}),
);
// ...
const mapStateToProps = state => ({
examplePropOne: state.auth.examplePropOne,
examplePropTwo: state.auth.examplePropTwo
});
Hopefully that helps!
As part of my ongoing project to learn React (I'm natively an ASP.NET guy) I've hit this issue. I have a suite of React apps in which I want to use some common UI elements, so I've attempted to break these out into a separate npm package. For the shared components themselves this has worked fine.
However, some of these components depend on redux actions to operate, so I've tried to bundle these actions and a reducer function into the external package. Here's a simplified version of my actions\index.js:
export const SNACKBAR_MESSAGE = "SNACKBAR_MESSAGE";
export const SNACKBAR_HIDE = "SNACKBAR_HIDE";
export function showSnackBarMessage(message) {
console.log('hit 1');
return (dispatch, getState) => {
console.log('hit 2');
dispatch(hideSnackBar());
dispatch({
type: SNACKBAR_MESSAGE,
message: message
});
}
}
export const hideSnackBar = () => {
type: SNACKBAR_HIDE
};
And this is reducer\index.js:
import {
SNACKBAR_MESSAGE,
SNACKBAR_HIDE
} from "../actions";
const initialState = {
snackBarMessage: null,
snackBarVisible: false
};
export default function UiReducer(state = initialState, action) {
switch(action.type) {
case SNACKBAR_MESSAGE:
return Object.assign({}, state, {
snackBarMessage: action.message,
snackBarVisible: true
});
case SNACKBAR_HIDE:
return Object.assign({}, state, {
snackBarMessages: '',
snackBarVisible: false
});
default:
return state;
}
}
This is the same code that worked fine when part of the original project. These are exported by my package's entry point file like this:
// Reducer
export { default as uiReducer } from './reducer';
// Actions
export { showSnackBarMessage as uiShowPrompt } from './actions';
export { hideSnackBar as uiHidePrompt } from './actions';
Then in my consuming project, my default reducer looks like this:
import { routerReducer } from 'react-router-redux';
import { combineReducers } from 'redux';
import { uiReducer } from 'my-custom-ui-package';
// Import local reducers
const reducer = combineReducers(
{
// Some local reducers
ui: uiReducer
}
);
export default reducer;
The problem is when I try to dispatch one of these actions imported from my external package. I include the action, e.g. import { uiShowPrompt } from "my-custom-ui-package"; and dispatch it like dispatch(uiShowPrompt("Show me snackbar")); then I see the two console messages (hit 1 and hit 2) displayed, but then the following error:
Uncaught TypeError: Cannot read property 'type' of undefined
at store.js:12
at dispatch (applyMiddleware.js:35)
at my-custom-ui-package.js:1
at index.js:8
at middleware.js:22
at store.js:15
at dispatch (applyMiddleware.js:35)
at auth.js:28
at index.js:8
at middleware.js:22
The store itself looks like this:
import { createStore, combineReducers, applyMiddleware, compose } from "redux";
import thunk from 'redux-thunk';
import { browserHistory } from "react-router";
import {
syncHistoryWithStore,
routerReducer,
routerMiddleware
} from "react-router-redux";
import reducer from "./reducer";
const loggerMiddleware = store => next => action => {
console.log("Action type:", action.type);
console.log("Action payload:", action.payload);
console.log("State before:", store.getState());
next(action);
console.log("State after:", store.getState());
};
const initialState = {};
const createStoreWithMiddleware = compose(
applyMiddleware(
loggerMiddleware,
routerMiddleware(browserHistory),
thunk)
)(createStore);
const store = createStoreWithMiddleware(reducer, initialState);
export default store;
I'm afraid I don't understand this error. I don't see what I'm doing differently other than essentially moving identical code from my local project to an npm package. Since neither the actions nor reducer actually depend on redux, my npm package doesn't itself have a dependency on react-redux. Is that a problem? If there's anything else I could share to help you help me just let me know. Like I say, I'm still fairly new to all this so clearly there's something I'm not getting right!
The problem might be in declaration of hideSnackBar function
export const hideSnackBar = () => {
type: SNACKBAR_HIDE
};
Here the function is trying to return an Object Literal from Arrow Function. This will always return undefined. As the parser doesn't interpret the two braces as an object literal, but as a block statement. Thus the error, Cannot read property 'type' of undefined as store is expecting an action with property type.
Replace code like this and see if it works.
export const hideSnackBar = () => ({
type: SNACKBAR_HIDE
});
The parentheses forces it to parse as Object Literal. Hope this helps
I had exported it like
export default userReducer();
and not like this:
export default userReducer;
Just get rid of that ()
Found out that it was case of wrong order in receiving the arguments when using redux-thunk.
// wrong argument order
const anAction = () => (getState, dispatch) => {...}
// correct one
const anAction = () => (dispatch, getState) => {...}
I'm trying to learn/ get my head around immutable so I can set it up with React/ Redux. Here's my store setup:
store.js
const store = createStore(
rootReducer,
applyMiddleware(thunk)
)
combine reducers:
import { combineReducers } from 'redux';
import { UIreducer } from './UI-reducer';
export default combineReducers({
UIreducer
});
example reducer:
import { fromJS } from 'immutable';
const initialState = fromJS({
test: false
});
export const UIreducer = (state = initialState, action) => {
switch (action.type) {
case 'TEST': {
return state.set('test', true)
}
default: return state
}
}
I'm fairly sure i've set the above two parts up correctly, the only thing that i'm not sure about is how to map the state to props in my component:
Here's how I normally do it:
const mapStateToProps = state => ({
UI: state.UIreducer
})
When using immutable it returns an object that immutable generates which doesn't look like a normal state object if I were to not use immutable. I tried after a bit of research to use .get with the state like so:
const mapStateToProps = state => ({
UI: state.get('UIreducer')
})
This however returned the error message:
state.get is not a function
Could someone please point out where i've gone wrong?
Remember the state is the combination of all reducers, so the state will look like the combined reducers object
export default combineReducers({
UIreducer
});
At mapStateToProps you should ask for state.UIreducer and at the component level UI.get('test')
Answering further questions:
where exactly would I write the UI.get('test')?
Anywhere in your component UI will be a prop for it, you can use it like this
const myComponent = (props) => {
return <div>props.UI.get('test')</div>
}
I would have thought the .get('test') would go in the mapStateToProps somehwere.
That depends of which properties of your state you want to pass/map to your component, you can also pass an specifict
const mapStateToProps = state => ({
test: state.UIreducer.get('test')
})
and inside of you component
const myComponent = (props) => {
return <div>props.test</div>
}
You could use redux-immutable and convert the initialState to an immutable object:
import {
combineReducers
} from 'redux-immutable';
import {
createStore
} from 'redux';
const initialState = Immutable.Map();
const rootReducer = combineReducers({UIreducer});
I am trying to initialise a basic store from a root reducer with initial state.
My root reducer
import Entity from "../api/Entity";
import { UPDATE_GROUPING } from "../constants/action-types";
import IAction from "../interfaces/IAction";
import IStoreState from "../interfaces/IStoreState";
const initialState:IStoreState = {
callsInProgress: false,
groupingOptions: ["Strategy", "User", "Date"],
groupingType: "Strategy",
numberOfCalls: 2,
items: [new Entity()],
};
const rootReducer = (state = initialState, action: IAction<object>) => {
switch (action.type) {
case UPDATE_GROUPING:
return { ...state, groupingType: action.payload};
default:
return state;
}
};
export default rootReducer;
When I create the store with the rootreducer as below
import { createStore } from 'redux';
import rootreducer from '../reducers/rootreducer';
const store = createStore(rootreducer);
export default store;
It works. The React components get initialised with the correct state for groupingType, groupingOptions etc.
However if I try and use a combineReducers() approach - even with just this single root reducer (should be identical) then when my components load, they do not have any initial state passed.
ie
import { createStore } from 'redux';
import reducers from '../reducers';
const store = createStore(reducers);
export default store;
My index.ts in the reducers folder which returns a combineReducers() call (the one which doesnt work)
import {combineReducers} from 'redux';
import rootreducer from './rootreducer';
// main reducers
export default combineReducers({
rootreducer
});
And lastly my component which hooks into redux and should import the state from the redux store
import updateGroupingType from "./actions/uiactions";
import './App.css';
import * as React from 'react';
import { connect } from 'react-redux';
import IStoreState from './interfaces/IStoreState';
interface IGroupingProps {
groupingOptions? : string[],
groupingType? : string,
updateGroupingAction? : any
}
class GroupingSelector extends React.Component<IGroupingProps, {}> {
constructor(props: IGroupingProps) {
super(props);
this.onGroupingChange = this.onGroupingChange.bind(this);
}
public render() {
if (this.props.groupingOptions == null)
{
return null;
}
return (
<div className="Grouping-selector">
<div className="Horizontal-panel-right Grouping-search-combo">
<select onChange={this.onGroupingChange}>
{this.props.groupingOptions.map((name, index)=>
<option key={index}>{name}</option>
)}
</select>
</div>
<div className="Content Horizontal-panel-right">
Group by
</div>
</div>);
}
private onGroupingChange(e: any) {
const { value } = e.target;
this.props.updateGroupingAction(value);
}
}
const mapStateToProps:any = (state: IStoreState) => {
return {
groupingOptions: state.groupingOptions,
groupingType: state.groupingType,
};
}
const mapDispatchToProps = (dispatch:any) => {
return {
updateGroupingAction: (groupingType:string) => dispatch(updateGroupingType(groupingType))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(GroupingSelector);
Why is my usage of combineReducers not working in the same way as when I use the single rootreducer?
From the doc
The combineReducers helper function turns an object whose values are different reducing functions into a single reducing function you can pass to createStore.
The resulting reducer calls every child reducer, and gathers their results into a single state object.
The state produced by combineReducers() namespaces the states of each reducer under their keys as passed to combineReducers()
When you are using rootReducer as a key inside of your combineReducers, it will create a state which shape will be
{ "rootReducer": YOUR_PREVIOUS_STATE}
You should use combineReducers only if you have different reducers for each key
Your root reducer should be key value pairs like,
export default combineReducers({
home:homeReducer
});
So that in your component, mapStateToProps() you will be able to access these values as,
const mapStateToProps = (state: any) => {
return {
users: state.home
};
};