Prevent react component from rendering twice when

2019-01-31 18:26发布

I have a React component that dispatches a redux state change in its componentWillMount function. The reason is that when the component is loaded, it needs to get the id from the url (powered by react-router), and trigger an action that sets up the state with that id's data.

Here is the component:

class Editor extends React.Component {

    componentWillMount() {
        const { dispatch, params } = this.props
        dispatch(editItem(params.id))
    }

    render() {
        const item = this.props.item
        console.log("Editing", item)
    }
}

export default connect(state => ({item: state.item}))(Editor)

Here's the catch: render is getting called twice. item is undefined on the first call, and valid on the second. Ideally, it should only be called once this.props.item actually exists (after the editItem action has been dispatched and run).

According to the React docs: "If you call setState within this method, render() will see the updated state and will be executed only once despite the state change."

In redux, dispatch is the equivalent of calling setState, as it results in a state change. However, I'm guessing something in the way connect works is still causing render to be called twice.

Is there a way around this besides adding a line like if (!item) return; ?

标签: reactjs redux
3条回答
Rolldiameter
2楼-- · 2019-01-31 19:06

What does editItem do? Does it add item to the redux state or is it there already?

If it is adding I imagine what is happening is that a render cycle happens with the current props, ie item being blank. Then it gets rendered again when the props have changed, via setting the item.

One approach to fixing this sort of thing is to create a higher order component that wraps Editor and calls the dispatch action the rendering though is set either to a loading screen or and empty div until item is set. That way you can be assured that Editor will have an item.

But without knowing what editItem does it's sort of hard to know. Maybe you could paste the code for that?

查看更多
祖国的老花朵
3楼-- · 2019-01-31 19:09

It looks like there's already an issue in the react-redux library.

https://github.com/rackt/react-redux/issues/210

查看更多
够拽才男人
4楼-- · 2019-01-31 19:12

One thing you might do is create a higher order component that handles the basic pattern of loading a different component (or no component) before the required props are loaded.

export const LoaderWrapper = function(hasLoaded, Component, LoaderComponent, onLoad) {
    return props => {
        if (hasLoaded(props)) {
            return <Component {...props} />
        }
        else {
            if (onLoad) onLoad(props)

            return { LoaderComponent ? <LoaderComponent /> : null }
        }
    }
}

Then you can wrap your component before connecting it to get the desired behaviour.

export default connect(state => ({item: state.item}))(LoaderWrapper(
    ((props) => !!props.item),
    Editor,
    null,
    (props) => props.dispatch(editItem(props.params.id))
))

You might want to add some currying magic to make sure you can compose these kinds of wrapper functions more nicely. Take a look at recompose for more info.

查看更多
登录 后发表回答