1

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.

7
  • You say that state.renderHeader is declared in your rootReducer; however, you should not call reducer functions from your code. Instead, you should call actions, which in turn will trigger the appropriate reducer function to modify your state. Commented Nov 1, 2018 at 6:09
  • @sme i called the action this.props.actions.changeHeader(headerType) Commented Nov 1, 2018 at 6:13
  • Are you calling the renderHeader function anywhere? I'm still trying to figure out if this is having an effect on your code. but again, if its a reducer function, it shouldn't be in your mapStateToProps() function. Commented Nov 1, 2018 at 6:15
  • @sme, i am not calling my renderHeader function anywhere else. I thought mapStateToProps() function was supposed to map the reducer to the props? Commented Nov 1, 2018 at 6:16
  • mapStateToProps should only be used to pass properties from your redux store (state), or properties passed directly to the component via ownProps. The action creators should be in mapDispatchToProps(), which from what I see is correct in your code. Once calling an action creator function, it will create the action, and then automatically call the appropriate reducer based on whatever type property the action contains. You don't need to manually call any reducers, nor do you need to pass them to any component Commented Nov 1, 2018 at 6:18

2 Answers 2

2

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!

Sign up to request clarification or add additional context in comments.

8 Comments

can you explain why you put the {changeHeader} in the position of the mapDispatchToProps?
The current header that is shown should be saved in the Redux store. You should have an action creator, changeHeader(headerType), that will create an action to change the header type in the store. The reducer will then update the store with the headerType that you passed to the action creator.
I added more edits to my code showing the action and reducers
@sme is right. the changeHeader is an action creator which dispatches and updates the state in the root reducer. And even after the update w/ more code AKJ, I feel my approach is better suited for rendering headers at least in this case. No harsh feelings :)
@dixitk13 no offense taken, i really want to learn to be a better coder and you and sme have both been amazing help. Can you pls elaborate your code more? I want to see how you structure yours. I am trying sme's one now to see if it works for me. And how come i shouldnt let render do setState? What are the pros & cons?
|
1

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
    }
}

12 Comments

btw there's a typo in the allReducers where it must be headerReducers instead of header-reducer. Also for this specific approach OP is following, if tomorrow he has another prop in the header which updates in his route every time hypothetically, won't his header undergo several re-renders? since whenever a render call happens, an extra render call happens in the header component since the action is dispatched?
I have a question, why didn't you use Object.assign({}, payload.headerType)? I am new to ReactJS and i am trying to understand if using Object.assign() is always needed. The tutorial i follow on Pluralsight says i should. Thanks man
@dixitk13 Its not a typo, in header-reducer.js I am exporting a default, unnamed function, so you can call it whatever you want when importing it.
I meant in redux/index.js. Since you imported headerReducers shouldn't you be using header: headerReducers?
@AKJ If you want to modify an object in the store/state, you should use object.assign. But its not necessary here, because I am completely overwriting the old headerType value with a new one, and not directly manipulating the state.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.