Redux: Updating the store with simultaneous dispatch - reactjs

I am trying a simple example of rendering 2 components via dispatching an action on componentWillMount() which updates the store.
Initial State:
export default {
dashboards: [],
dashboardContent: []
};
2 Reducers:
export default function dashboardContentReducer(state = initialState.dashboardContent, action) {
switch(action.type) {
case types.LOAD_DASHBOARD_CONTENT_SUCCESS:
return action.dashboardContent;
default:
return state;
}
}
and
export default function dashboardReducer(state = initialState.dashboards, action) {
switch(action.type) {
case types.LOAD_DASHBOARDS_SUCCESS:
return action.dashboards;
default:
return state;
}
}
Here's where things get a little weird.
I am able to dispatch the action to call these reducers, but only 1 of them will function to update the redux store. I do so as follows:
class NavigationBar extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.dispatch(dashboardActions.loadDashboards());
}
render() {
return (
<div className="rc-navigationBar">
<h1>Navigation!</h1>
{this.props.dashboards.map((dashboard, index) => {
return <h1 key={index}>{dashboard.title}</h1>
})}
</div>
);
}
}
and for the other:
class ContentPage extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.dispatch(dashboardContentActions.loadDashboardContent(extractIdFromRoute()));
}
render() {
return (
<div>
<h1>Content!</h1>
{this.props.dashboardContent.map((content, index) => {
return <h1 key={index}>{content.application}</h1>;
})}
</div>
);
}
}
When I simultaneously try to modify the store, I get this error:
Uncaught (in promise) Error: A state mutation was detected between dispatches, in the path 'dashboards.1.filter.Pivot.ancestorOrigins'. This may cause incorrect behavior.
What am I doing wrong here?

You are returning it in a wrong way. It should be like this -
export default function dashboardContentReducer(state = default, action) {
switch(action.type) {
case types.LOAD_DASHBOARD_CONTENT_SUCCESS:
return Object.assign({}, state, { dashboardContent:action.dashboardContent });
default:
return state;
}
}

Related

Only Mapping Nested Object of State to Props won't update Component

I use mapStateToProps to get an nested Object from an object by Id. The problem is, the props don't get updated and componentDidUpdate won't fire when the redux store state changes.
Here are my reducers:
export const programmReducers = (state = initialState, action) => {
let programms = state.programms;
switch (action.type) {
case actionTypes.FETCH_CATEGORIES:
return Object.assign({}, state, {
categories: action.payload
})
case actionTypes.FETCH_PROGRAMM:
programms[action.payload.id] = action.payload;
console.log(programms);
return {
...state,
programms: Object.assign({}, programms)
}
case actionTypes.FETCH_PROGRAMM_COMPONENTS:
programms[action.programmId].components = action.payload;
console.log('Added Components')
return {
...state,
programms: Object.assign({}, programms)
}
case actionTypes.FETCH_PROGRAMM_SECTIONS:
programms[action.programmId].sections = action.payload;
console.log('Added Sections')
return {
...state,
programms: Object.assign({}, programms)
}
default:
return state
}
}
Here is my components:
class ProgrammPage extends Component {
static async getInitialProps({ store, query: {id} }) {
if (!store.getState().programm.programms[id]) {
console.log('Programm not! found');
await store.dispatch(loadProgramm(id));
await store.dispatch(loadProgrammComponents(id));
} else {
console.log('Programm found')
}
return {
programmId: id
}
}
constructor(props) {
super(props);
if (this.props.user) {
console.log('Loading init!');
this.props.loadProgrammComponents(this.props.programmId)
this.props.loadProgrammSections(this.props.programmId);
}
}
componentDidUpdate(prevProps) {
console.log('Update')
if (!prevProps.user && this.props.user) {
console.log('Loading update');
this.props.loadProgrammComponents(this.props.programmId);
this.props.loadProgrammSections(this.props.programmId);
}
}
render() {
return (
<div>
<h1>Programm</h1>
<h2>{this.props.programm.name}</h2>
<h2>{this.props.programm.id}</h2>
<h3>Components: {this.props.programm.components ? this.props.programm.components.length : 'None'}</h3>
<h3>Sections: {this.props.programm.sections ? this.props.programm.sections.length : 'None'}</h3>
<br></br>
<h1>User: { this.props.user ? this.props.user.uid : 'None'}</h1>
<button onClick={() => this.props.loadProgramm('ProgrammLevel2')}>Load Programm</button>
<button onClick={() => this.props.loadProgrammComponents(this.props.programmId)}>Load Components</button>
</div>
)
}
}
function mapStateToProps(state, ownProps) {
return {
programm: state.programm.programms[ownProps.programmId],
// programms: state.programm.programms <--- Fixed the problem
user: state.auth.user
}
}
const mapDispatchToProps = dispatch => bindActionCreators({
loadProgramm,
loadProgrammComponents,
loadProgrammSections
}, dispatch)
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProgrammPage)
When the Reducer for FETCH_PROGRAMM_COMPONENTS or FETCH_PROGRAMM_SECTIONS changes the redux state, componentDidUpdate isn't called and the component doesn't dispay the changes.
The problem seems to be related to the mapStateToPropsmethod, because, when I add programms: state.programm.programms everything works fine. However I don't need the whole programms object.
Why are doesn't the component recognize that the programm has updated when I map only a nested object to my props?
Your problem is within the programmReducers, your component doesn't rerender because you don't change the state.
After changing mapStateToProps you need to make changes in your component.
The next code probably breaks when you change programms: state.programm.programms to programm: state.programm.programms[ownProps.programmId]
export const programmReducers = (state = initialState, action) => {
let programms = state.programms;
...
}
So I'm guessing your reducers aren't doing what you intended.

Cannot update during an existing state transition error

EDIT: I solve my issue and it is working for me now - also edited my code to reflect new changes.
I am getting this error and I am not sure what is the cause of this error.
I cannot show code as it is company's material, so I will try my best to describe it:
App.js:
`class App extends React.Component {
constructor(props) {
super(props);
}
render () {
return (
<div>
<Header />
<RouteList />
<Footer />
</div>
)
}
}`
My <RouteList /> is a a stateless function that returns all Routes for the web-application.
Header.js:
class Header extends React.Component {
constructor (props) {
super(props);
this.changeHeader = this.changeHeader.bind(this);
}
changeHeader(headerType) {
this.props.actions.changeHeader(headerType)
}
GetHeader() {
// if-else statement which will return a different sub header class
const HeaderType = this.props.renderHeader.headerType
if (headerType == 'abc') {
<aHeader changeHeader={this.changeHeader} />
} [...] {
// Final else block return something
}
}
render () {
return (
<div>{this.GetHeader()}</div>
)
}
}
function mapStateToProps(state, ownProps) {
return { renderHeader: state.renderHeader};
}
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(headerActions, dispatch) };
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Header));
this.props.action.changeHeader(headerType) is an if-else statement which depending on what the value of headerType is, will fire a different action.
state.renderHeader is declared in my rootReducer.
I pass changerHeader() into individual header.js which are stateless (i.e. aHeader.js, bHeader.js...). When a navlink is clicked it will invoke the method and also route the page to another UI. This is how i embed the method into the navlink: onClick={changeHeader(input')}.
rootReducer.js
const rootReducer = combineReducers({renderHeader});
export default rootReducer;
The renderHeader is the renderHeaderReducer.
headerAction.js
export function changeHeader(headerType) {
if (headerType == "abc") {
return {type: type, headerType: "abc"}
} [...] {
// something default
}
}
renderHeaderReducer.js
export default function renderHeaderReducer(state = initialState, action) {
switch(action.type) {
case "abc":
return (Object.assign({}, ...state, {headerType: action.headerType}));
[...];
default:
return state;
}
}
At this point when the link is clicked, the web browser should refresh, leaving the Header in place but modifying the part. However my website goes into an infinite loop, and the error is:
Error: Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.
When I did a console.log to see what is going on, it seems to be looping over all the various options that i defined which will render Header.js
It turns out that the main problem was when i called my onClick method.The infinite loop that bugged my code was a result of the onClick function firing even without being clicked.
Original: onClick={this.changeHeader('abc')}
New: onClick={() => changeHeader('abc')}
Refer to this post for an explanation.
Thank you.
time for some pseudo code :)
From what I understand, you have a Header Component which is connected to a rootReducer component which contains the header for which Router Link you are on.
I have some similar code in my application where we use individual components dispatch action to update the rootReducer header. The header just listens for updates using redux and re-renders itself.
class Header extends React.Component {
// render the header
render() {...}
}
const mapStateToProps = (state) => {
return {
header: state.rootReducer.header
}
}
export default withRouter(connect(mapStateToProps, {})(Header));
the component
class MySpecialRouteComponent extends React.Component {
componentDidMount() {
this.props.changeHeader("Special Component")
}
render() {...}
}
const mapStateToProps = (state) => {
return {
...whatever
}
}
export default withRouter(connect(mapStateToProps, {changeHeader})(MySpecialRouteComponent));
you shouldn't make render do the setState in React ever!
I'll just show how I would set everything up to handle this situation.
redux/header-actions.js (call these action creators from your components):
export const changeHeader = (headerType) => {
return {
type: 'CHANGE_HEADER',
payload: {
headerType: headerType
}
}
}
redux/header-reducers.js (note: this will be handled when you call the action):
const INITIAL_STATE = {
headerType: 'header-a'
};
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case 'CHANGE_HEADER':
return changeHeader(state, action.payload);
default:
return state;
}
}
const changeHeader = (state, payload) => {
// this is where your app will update the state, to indicate
// which header should be displayed:
return {
...state,
headerType: payload.headerType
}
}
redux/index.js:
import headerReducers from './reducers/header-reducers';
import { combineReducers } from 'redux';
const allReducers = combineReducers({
header: headerReducers
});
export default allReducers;
Now you can set up your header.js component like this:
import { changeHeader } from '../redux/header-actions';
class Header extends Component {
render() {
return (
<div>{this.renderHeader()}</div>
);
}
renderHeader() {
if (this.props.headerType === 'header-a')
return <aHeader changeHeader={this.props.changeHeader} />
else
return <bHeader changeHeader={this.props.changeHeader} />;
}
}
function mapStateToProps(store, ownProps) {
return {
headerType: store.header.headerType
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
changeHeader: changeHeader
},
dispatch);
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Header));
Then in, for example, aHeader.js:
class aHeader {
constructor() {
super();
this.changeHeader = this.changeHeader.bind(this);
}
render() {
return <div onClick={this.changeHeader}>header a</div>;
}
changeHeader() {
this.props.changeHeader('header-b'); // call the action creator here
}
}

how to get data from redux in componentDIdMount?

In console nothing, where can be a mistake ?
Need to get this.props.about and check empty or not.
reducer.js
export default function details(state = initialState, action) {
switch(action.type) {
case DETAILS_SUCCESS:
return { ...state, details: action.payload, error: '' };...
Container.js
class HeaderContainer extends Component {
render() {
const { details } = this.props, { deTails } = this.props.HeaderAction;
return <div><Header deTails={deTails} about={details.details} error={details.error} /></div>
}
}
function mapStateToProps(state) {
return {
details: state.details,
}
}
function mapDispatchToProps(dispatch) {
return {
HeaderAction: bindActionCreators(HeaderAction, dispatch),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(HeaderContainer);
Component.js
componentDidMount() {
console.log(this.props.about);
}
You won't receive updated state as in props in componentDidMount rather you can use:
componentWillReceiveProps(nextProps){ // this is UN_SAFE
}
or
static getDerivedStateFromProps(nextProps, prevState) { // this is recommended
}

How to pass internal state to global state using Redux

I'm using Redux in an application for the first time and having trouble understanding how to pass a component's internal state to the global state object.
export default class ComponentOne extends Component {
constructor() {
this.state = {
number: 0
}
handleNumber = (e) => {
this.setState({
number: e.target.value
})
}
render() {
console.log(this.state.number)
return (
<div>
<input onChange={this.handleNumber} type="number">
</div>
)
}
}
}
function mapStateToProps(state) {
return {
number: state
}
}
export default connect(mapStateToProps, { HANDLE_NUMBER_CHANGE })(ComponentOne);
My Actions & Reducers:
const HANDLE_NUMBER_CHANGE = state => {
return {
type: 'HANDLE_NUMBER_CHANGE'
}
}
export default (state = 0, action) {
switch(action.type) {
case 'HANDLE_NUMBER_CHANGE':
//Im lost here - trying to save internal state
default:
return state;
}
}
My store is set up properly, using redux-thunk for middleware.
When I log store.getState() - it is logging 0 regardless of my components internal state.
Can anybody explain how this works?
When you have global state you dont need to save it to the local state. It is accessible to the component as this.props.value.
The way to set global state is by passing the value to the action creator, which returns it in the action. The reducer gets it in the action object and saves it.
There are many simple examples available. Here is one.
Here is your code after changes:
(I didn't run it - there might be errors, but I believe that you will be able to fix them by yourself; I have divided the code between several files - this is how usually how this is done. Look in the example in the above link if you have problems)
// file: src/components/ComponentOne.js
import React from 'react';
import { connect } from 'react-redux';
import { handleNumber } from '../actions';
class ComponentOne extends Component {
constructor(props) {
super(props);
this.handleNumber = this.handleNumber.bind(this);
}
render() {
console.log(this.state.number)
return (
<div>
<input onChange={(e) => this.props.handleNumber(e.target.value)} type="number" />
</div>
);
}
}
function mapStateToProps(state) {
return {
number: state
}
}
export default connect(mapStateToProps, { handleNumber })(ComponentOne);
// end of file
/// separate file: src/reducers/index.js ////
import { combineReducers } from 'redux';
import dataReducer from './dataReducer';
export default combineReducers({
number: dataReducer
});
// end of file
// separate file: src/reducers/dataReducer.js
const DataReducer = (state = 0, action) => {
switch(action.type) {
case 'HANDLE_NUMBER_CHANGE':
return action.payload;
default:
return state;
}
};
export default DataReducer;
// end of file
// separate file: src/actions/index.js
export function handleNumber(value) {
return ({
type: 'HANDLE_NUMBER_CHANGE',
payload: value
});
}
I don't see the logic in making your internal state equal your store. I'm not saying you're wrong, but it doesn't seem to fit within the redux paradigm. However...
Action should be...
export function HANDLE_NUMBER_CHANGE = number => {
return {
type: 'HANDLE_NUMBER_CHANGE'
payload: number
}
}
Reducer should look like...
export default (state = {number: 0}, action) {
switch(action.type) {
case 'HANDLE_NUMBER_CHANGE':
return (state = {
...state,
number: action.payload,
});
Lastly, you'll need to call a dispatch from your onChange function.
dispatch(HANDLE_NUMBER_CHANGE(e.target.value).
If you do not pass the value to the action, there is no way for the reducer to add it to the store.
If you are managing your ComponentOne state using redux then you dont need
react state.
ComponentOne
export default class ComponentOne extends Component {
constructor() {
handleNumber = (e) => {
this.props.updateNumber(e.target.value);//call dispatch method
}
render() {
return (
<div>
<input onChange={this.handleNumber} type="number">
</div>
)
}
}
}
function mapStateToProps(state) {
return {
number: state.number //map updated number here
}
}
function mapDispatchToProps(state) {
return {
updateNumber(number){
dispatch({type: 'HANDLE_NUMBER_CHANGE',number});//dispatch action
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ComponentOne);
reducers:
export default (state = 0, action) {
switch(action.type) {
case 'HANDLE_NUMBER_CHANGE':
return {
...state,number : actio.number//update number here
}
default:
return state;
}
}

React-redux component does not rerender on store state change

I'm stating to learn react and redux today, yet I cannot figure out how to force component to rerender after state change.
Here is my code:
const store = createStore(loginReducer);
export function logout() { return {type: 'USER_LOGIN'} }
export function logout() { return {type: 'USER_LOGOUT'} }
export function loginReducer(state={isLogged:false}, action) {
switch(action.type) {
case 'USER_LOGIN':
return {isLogged:true};
case 'USER_LOGOUT':
return {isLogged:false};
default:
return state;
}
}
class App extends Component {
lout(){
console.log(store.getState()); //IT SHOW INITIAL STATE
store.dispatch(login());
console.log(store.getState()); //IT SHOWS THAT STATE DID CHANGE
}
////THIS IS THE PROBLEM,
render(){
console.log('rendering')
if(store.getState().isLogged){
return (<MainComponent store={store} />);
}else{
return (
<View style={style.container}>
<Text onPress={this.lout}>
THE FOLLOWING NEVER UPDATE :( !!{store.getState().isLogged?'True':'False'}</Text>
</View>
);
}
}
The only way i could trigger update is by using
store.subscribe(()=>{this.setState({reload:false})});
inside constructor, so that i manually trigger an update state of component to force rerender.
but how can i link both store and component states ?
Your component is only going to re-render if its state or props are changed. You are not relying on this.state or this.props, but rather fetching the state of the store directly within your render function.
Instead, you should use connect to map the application state to component props. Component example:
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
export class App extends React.Component {
constructor(props, context) {
super(props, context);
}
render() {
return (
<div>
{this.props.isLoggedIn ? 'Logged In' : 'Not Logged In'}
</div>
);
}
}
App.propTypes = {
isLoggedIn: PropTypes.bool.isRequired
};
function mapStateToProps(state, ownProps) {
return {
isLoggedIn: state.isLoggedIn
};
}
export default connect(mapStateToProps)(App);
In this very simplified example, if the store's isLoggedIn value changes, it will automatically update the corresponding prop on your component, which will cause it to render.
I suggest reading the react-redux docs to help you get started:
https://redux.js.org/basics/usage-with-react
I ended up here because I had written a bad reducer. I had:
const reducer = (state=initialState, action) => {
switch (action.type) {
case 'SET_Q':
return Object.assign(state, { // <- NB no {}!
q: action.data,
})
default:
return state;
}
}
I needed:
const reducer = (state=initialState, action) => {
switch (action.type) {
case 'SET_Q':
return Object.assign({}, state, { // <- NB the {}!
q: action.data,
})
default:
return state;
}
}

Resources