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!
Related
Solution(updated):
I thought any action would cause react-redux-connect to call the mapState functions but when an action doesn't change anything then this is not the case.
I have a localStorage module that dispatches actions but don't change state, instead thy will write to localStorage. The module has selectors that are used in the containers but they won't get called until the state actually changes so the UI would only show correctly after another action was dispatched that would change the state.
Problem
When I put the store on window (window.store=store), add a console.log in the mapStateToProps, then in the console I dispatch an action: store.dispatch({type:'some action'}) then the console.log of the mapStateToProps does not show.
I do memoize the result but the mapStateToProps should be called see here
Full code is here and running example here (you can open a console clicking on 'console' link in the right bottom of the screen).
package.json
store.js:
import { createStore } from 'redux';
export default (initialState, reducer) => {
const store = createStore(
reducer,
initialState,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()
);
window.store = store;
return store;
};
app.js
import React from 'react';
import { connect } from 'react-redux';
import './App.css';
import createStore from './store';
import { Provider } from 'react-redux';
import initCounter from './components/Counter';
import {
createWrapper,
memoize,
} from './components/#common';
const COUNTER = 'COUNTER';
const selectCounterState = state => state.myCounter;
const counter = initCounter({
actionWrapper: createWrapper(COUNTER, 'counter1'),
selectors: { myState: selectCounterState },
connect,
memoize,
});
const initialState = {
myCounter: counter.initialState,
};
const reducer = (state = initialState, action) => {
if (action.emittedBy === COUNTER) {
return {
...state,
myCounter: counter.reducer(
selectCounterState(state),
action.payload
),
};
}
return state;
};
const store = createStore(initialState, reducer);
const Counter = counter.container;
const App = () => (
<Provider store={store}>
<Counter id="counter1" parentId={[]} />
</Provider>
);
export default App;
component/Counter/index:
import component from './component';
const INCREASE = 'INCREASE';
const reducer = (state, action) => {
if (action.type === INCREASE) {
return { ...state, count: state.count + 1 };
}
return state;
};
const makeState = memoize =>
memoize((id, parentId, { count }) => ({
id: parentId.concat(id),
parentId,
count,
}));
const mapStateToProps = ({ myState }, memoize) => () => {
const newState = makeState(memoize);
return (state, ownProps) =>
console.log('in map state to props', new Date()) ||
newState(
ownProps.id,
ownProps.parentId,
myState(state)
);
};
export default ({
actionWrapper,
selectors,
connect,
memoize,
}) => {
const actions = {
increase: ({ id }) =>
actionWrapper({
type: INCREASE,
id,
}),
};
const container = connect(
mapStateToProps(selectors, memoize),
actions
)(component);
return {
container,
reducer,
initialState: { count: 0 },
};
};
components/counter/component.js:
import React from 'react';
export default props => (
<div>
<button onClick={() => props.increase(props)}>
add
</button>
{props.count}
</div>
);
This problem was caused because I had a localStorage module that did dispatch actions but did not change the state, instead it would write to localStorage.
The module had selectors that would get the right data and the containers would use them to build the correct state but since the dispatched action did not change the state in the redux store react-redux would skip calling my mapState functions (probably memoizing state in Provider).
The solution is to let the root reducer return a new state reference {...state} so any action would cause the mapState functions to be called.
Your example codepen works just fine, you just have to trigger an action that gets past your top level guard and is of the expected structure, as to not cause any followup errors:
Post this into the console of your codepen:
store.dispatch({emittedBy: "COUNTER", type: "COUNTER -> INCREASE", id: "counter1", payload: {type: "INCREASE", id: ["counter1"]}})
I'm just starting with React and Redux and stumbled upon something I can't figure out by myself - I think that Redux state is not changing and it's causing (some of) errors. I'm checking state with use of remote-redux-devtools#0.5.0.
My code:
Categories.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { getCategories } from '../../actions/categories';
export class Categories extends Component {
static propTypes = {
categories: PropTypes.array.isRequired
};
componentDidMount() {
this.props.getCategories();
}
render() {
return (
<div>
Placeholder for categories.
</div>
)
}
}
const mapStateToProps = state => ({
categories: state.categories.categories
});
export default connect(mapStateToProps, { getCategories })(Categories);
../../actions/categories.js:
import axios from "axios";
import { CATEGORIES_GET } from "./types";
export const getCategories = () => dispatch => {
return axios
.get("/api/notes/categories/")
.then(res => {
dispatch({
type: CATEGORIES_GET,
payload: res.data
});
})
.catch(err => console.log(err));
};
reducers/categories.js:
import { CATEGORIES_GET } from '../actions/types.js';
const initialState = {
categories: []
};
export default function (state = initialState, action) {
switch (action.type) {
case CATEGORIES_GET:
return {
...state,
categories: action.payload
};
default:
return state;
}
}
store.js:
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'remote-redux-devtools';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware)));
export default store;
reducers/index.js
import { combineReducers } from "redux";
import categories from './categories';
export default combineReducers({
categories,
});
Using remote-redux-devtools, I've never seen anything in my state. Currently this code above gives me 3 errors, two of them being
this.props.getCategories is not a function
My guess is that because there is some issue with Categories class, it's not passing anything to state and it could be root cause of errors. I had one more error, connected to Categories not being called with attributes, but for debug purposes I put empty array there - one error dissapeared, but that's it. I've also tried adding constructor to Categories and called super(), but did not help also.
I believe your issue is that you're exporting your Categories class twice, once connected, the other not.
If you remove export from export class Categories extends Component, does it work as expected?
When you're mapping the state in a component, you must access the desired variable through a reducer.
So instead of:
const mapStateToProps = state => ({
categories: state.categories
});
You must use:
const mapStateToProps = state => ({
categories: state.categories.categories
});
Your props don't have getCategories method, because you didn't pass it as a function to connect.
A better approach is to define only the action code in your actions file and then use mapDispatchToProps.
../../actions/categories.js
import axios from "axios";
export const getCategories = () => {
axios
.get("/api/notes/categories/")
.then(res => res.data);
})
.catch(err => console.log(err));
};
Categories.js
import { getCategories } from '../../actions/categories'
import { CATEGORIES_GET } from "./types";
const mapDispatchToProps = dispatch => {
return {
getCategories: () => dispatch(() => { type: CATEGORIES_GET, payload: getCategories() }),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Categories);
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
};
};
Our project is mainly written in AngularJS 1.5, but we are transitioning over to ReactJS. We're using react2angular to allow our AngularJS project to consume React components. Our project is also using ngRedux to store certain data. While I'm able to save certain data using Angular, I am unable to access the Redux store data via the mapped props.
this.props.interval in CardController.js is undefined. I am able to get store data using this.props.store.getState(), but React is not able to detect if there has been a change in the Redux store in componentWillReceiveProps, so I don't think that's an option.
CardContainer.js
import { connect } from 'react-redux';
import CardController from './CardController';
const mapStateToProps = (state) => {
return {
interval: state.interval
};
};
const mapDispatchToProps = null;
const ScorecardContainer = connect(
mapStateToProps,
mapDispatchToProps,
)(CardController);
export default CardContainer;
CardController.js
import React from 'react';
import PropTypes from 'prop-types';
import Card from './Card';
export default class CardController extends React.Component {
constructor(props) {
super(props);
this.state = {
selected: this.props.interval,
state: this.props.$scope.filterParams,
};
}
render() {
return (
<div className="CardController">
<Card
selected={this.state.selected}
/>
</div>
);
}
}
CardController.propTypes = {
interval: PropTypes.string,
};
reducers.js
import * as actionTypes from './actionTypes';
const initialState = {
interval : 'week'
};
export const setInterval = (state, action) => {
return {
...state,
interval : action.interval
};
};
export const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.SET_INTERVAL :
return setInterval(state, action);
default :
return state;
}
};
export default reducer;
Turns out that the problem was that I forgot to drill down. Instead of state.interval, it should have been state.card.interval.