【问题标题】:React-Redux: Dispatching on Constructor -> props.store.state not updatedReact-Redux:在构造函数上调度-> props.store.state 未更新
【发布时间】:2017-02-23 23:57:12
【问题描述】:

我对 React 和 Redux 都很陌生,我只使用了大约 2 到 3 周来开发应用程序的 alpha 版本。

虽然大多数关于将 Redux 与 React 结合使用的教程似乎都非常复杂,但我找到了一个可以让我快速编写一些代码来在我的应用程序中尝试非常简单的场景的教程。

我目前似乎面临的主要问题是:我想单击图像并在另一个页面上显示所述属性的详细信息(路由到使用 react-router,在路径中传递 id - 澄清当前代码我使用的是 22 的硬编码 id,并且该 id 尚未在路径中传递)。我认为它就像单击应用程序一样直接,然后在构造函数或 componentWillMount 方法中我可以调用 this.props.foo(id) 然后使用 this.props.store.foo 获取属性,但看起来好像商店当时没有更新。但是,如果我在重定向之前在页面的 handleClick 方法中调用了 this.props.foo(id),那么它会起作用,但是在刷新时,商店会恢复默认值并导致错误。

我只是想知道我是否只是采取了完全错误的方法......或者只是错过了一些东西。

代码可能太多了,如果我应该删减它,请告诉我... 要查找的函数是:

handleImageClick() -> Results.js

constructor() -> BuyDetails.js 代码:

索引.js

let state = {
    results: [],
    selectedState:{},
};

let reducer = (state, action) => {
  console.log("in reducer" + action.type);
  switch (action.type) {
    case 'ADD_RESULTS': 
        console.log("in reducer add");
        console.log("in reducer results = " + action.results);
        var newState = Object.assign({}, state)
        newState.results = action.results
        console.log("in reducer add " + JSON.stringify(newState))
        return newState
    case 'GET_RESULTS': 
        console.log("in reducer get state = " + state.results[0].id);
        var newState = Object.assign({}, state)
        for (var result of state.results){
            if (result.id === action.id){
                console.log(result.img)

                newState.selectedState = result
                console.log(newState.selectedState.location.address)

            }
        }
        console.log(newState.selectedState.location.address)
        console.log(JSON.stringify(newState));
        return newState
    default: 
        return state
  }
}

let store = createStore(reducer, state)

let mapStateToProps = state => ({
  store: state
})

let mapDispatchToProps = dispatch => ({
  addResults: (results) => dispatch({type: 'ADD_RESULTS', results:results}),
  getSelectedResult: (id) => dispatch({type: 'GET_RESULTS', id:id}),
})

const ConnectedAppComponent = connect(
    mapStateToProps, mapDispatchToProps
)(App)

const ConnectedResultsComponent = connect(
    mapStateToProps, mapDispatchToProps
)(Results)

const ConnectedBuyDetailsComponent = connect(
    mapStateToProps, mapDispatchToProps
)(BuyDetails)

ReactDOM.render(
  <Provider store={store}>
      <Router history={hashHistory}>
        <Route path="/" component={ConnectedAppComponent}/>
        {/* add the routes here */}
        <Route path="/results" component={ConnectedResultsComponent}/>
        <Route path="/buyDetails" component={ConnectedBuyDetailsComponent}/>
      </Router>
  </Provider>, 
  document.getElementById('root')
);

Results.js

class Results extends Component{
   constructor(props) {
      super(props);
      this.state = {open: true, openProfile:false, anchorEl: null,dataSet:this.props.store.results};
      console.log(this.state.dataSet.length)
      console.log(this.state.dataSet[0].img)

   }
   handleTouchTap = (event) => {
    // This prevents ghost click.
    console.log("touch tap");
    event.preventDefault();
    const tempState = this.state;
    tempState.openProfile = true
    tempState.anchorEl = event.currentTarget
    this.setState(tempState)
    /*this.setState({
      openProfile: true,
      anchorEl: event.currentTarget,
    });*/
  };

  handleRequestClose = () => {
    const tempState = this.state;
    tempState.openProfile = false
    tempState.anchorEl = null
    this.setState(tempState)
   /*this.setState({
      openProfile: false,
    });*/
  };
  handleToggle = () => this.setState({open: !this.state.open});
  handleImageClick(){
      //This is where i could be doing this.props.getSelectedResult(22); and it would work but causes issues on refresh
      const path = `/buyDetails`
      this.context.router.push(path)
  }
  render() {
    return <MuiThemeProvider>
          <div className="Results" id="Results" style={styles}>
          <div>
            <Toolbar style={appBarStyle}>

              <IconButton iconClassName="material-icons"
                style={{bottom: '0',height:'auto'}}
                onClick={this.handleToggle}>
                menu
                {/*<FontIcon className="material-icons" color={grey900} onClick={this.handleToggle}>menu</FontIcon>*/}
              </IconButton>              
              <ToolbarGroup style={groupStyle}>
                <ToolbarSeparator style={seperatorMargin}/>
                 <FontIcon style={searchIconnStyle} className="material-icons">search</FontIcon>
                 <ToolBarSearchField />
              </ToolbarGroup>

              <ToolbarGroup>
                <ToolbarSeparator style={residentialSeperatorStyle}/>
                <FlatButton label="Residential" style={selectedToolBarButtonStyle}/>
                <ToolbarSeparator style={seperatorStyle}/>
                <FlatButton label="Commerical" style={toolBarButtonStyle}/>
                <ToolbarSeparator style={seperatorStyle}/>
                <FlatButton label="JoellyR" style={toolBarButtonStyle} onTouchTap={this.handleTouchTap}/>
                <Popover open={this.state.openProfile}
                  anchorEl={this.state.anchorEl}
                  anchorOrigin={{horizontal: 'right', vertical: 'bottom'}}
                  targetOrigin={{horizontal: 'right', vertical: 'top'}}
                  onRequestClose={this.handleRequestClose}>
                  <MenuItem value={1} primaryText="Price Range" />
                  <MenuItem value={2} primaryText="values" />
                </Popover>
              </ToolbarGroup>

            </Toolbar>
            <ToolBarFilterFields fieldNames={['Buy', 'Sell', 'Rent', 'Businesses', 'Mortgages']} displaySeperator={false}/>
            </div>
            <Drawer
              open={this.state.open}
              containerStyle={{top:'inherit', boxShadow:'(0,0,0,0)', border:'0px', borderRight:'1px solid', borderColor: 'rgba(0,0,0,0.3)'}}>
            </Drawer>
            <div style={this.state.open ? drawerExpanded : drawerCollapsed }>
              <Paper style={paperStyle}>
                <ToolBarFilterFields fieldNames={['Filters', 'Price', 'Bath', 'Beds', 'Type', 'Style']} displaySeperator={true}/>
                <ResultGridList dataSet={this.state.dataSet} onClick = {() => this.handleImageClick()}/>
              </Paper>
            </div>
          </div>
        </MuiThemeProvider>
  }
}
Results.contextTypes = {
  router: React.PropTypes.object
}

export default Results;

BuyDetails.js

class BuyDetails extends Component{
   constructor(props) {
      super(props); 


       //dispatching the action here
    this.props.getSelectedResult(22);

    //getting the selected object from the props.state ... but it will still be = {}
    this.state = {open: true, openProfile:false, anchorEl: null,data:this.props.store.selectedState};
   }
   componentWillMount() {



  }

   handleTouchTap = (event) => {
    console.log('in buy detail: ' + JSON.stringify(this.props.store.selectedState) + JSON.stringify(this.props.store.results));
    // This prevents ghost click.
    console.log("touch tap2");
    event.preventDefault();
    const tempState = this.state;
    tempState.openProfile = true
    tempState.anchorEl = event.currentTarget
    this.setState(tempState)
    /*this.setState({
      openProfile: true,
      anchorEl: event.currentTarget,
    });*/
  };

  handleRequestClose = () => {
    const tempState = this.state;
    tempState.openProfile = false
    tempState.anchorEl = null
    this.setState(tempState)
   /*this.setState({
      openProfile: false,
    });*/
  };
   handleToggle = () => this.setState({open: !this.state.open});

  render() {
    return <MuiThemeProvider>
          <div className="BuyDetails" id="BuyDetails" style={styles}>
          <div>
            <Toolbar style={appBarStyle}>

              <IconButton iconClassName="material-icons"
                style={{bottom: '0',height:'auto'}}
                onClick={this.handleToggle}>
                menu
                {/*<FontIcon className="material-icons" color={grey900} onClick={this.handleToggle}>menu</FontIcon>*/}
              </IconButton>              
              <ToolbarGroup style={groupStyle}>
                <ToolbarSeparator style={seperatorMargin}/>
                 <FontIcon style={searchIconnStyle} className="material-icons">search</FontIcon>
                 <ToolBarSearchField />
              </ToolbarGroup>

              <ToolbarGroup>
                <ToolbarSeparator style={residentialSeperatorStyle}/>
                <FlatButton label="Residential" style={selectedToolBarButtonStyle}/>
                <ToolbarSeparator style={seperatorStyle}/>
                <FlatButton label="Commerical" style={toolBarButtonStyle}/>
                <ToolbarSeparator style={seperatorStyle}/>
                <FlatButton label="JoellyR" style={toolBarButtonStyle} onTouchTap={this.handleTouchTap}/>
                <Popover open={this.state.openProfile}
                  anchorEl={this.state.anchorEl}
                  anchorOrigin={{horizontal: 'right', vertical: 'bottom'}}
                  targetOrigin={{horizontal: 'right', vertical: 'top'}}
                  onRequestClose={this.handleRequestClose}>
                  <MenuItem value={1} primaryText="Price Range" />
                  <MenuItem value={2} primaryText="values" />
                </Popover>
              </ToolbarGroup>

            </Toolbar>
            </div>
            <Drawer
              open={this.state.open}
              containerStyle={{top:'inherit', boxShadow:'(0,0,0,0)', border:'0px', borderRight:'1px solid', borderColor: 'rgba(0,0,0,0.3)'}}>
            </Drawer>
            <div style={this.state.open ? drawerExpanded : drawerCollapsed }>
              <Paper style={paperStyle}>
                <BuyDetailGridList data={this.props.store.selectedState}/>
              </Paper>
            </div>
          </div>
        </MuiThemeProvider>
  }
}
function isEmpty(obj) {
    for(var key in obj) {
        if(obj.hasOwnProperty(key))
            return false;
    }
    return true;
}
export default BuyDetails;

谢谢大家...提前:)

+++更新 - 仍然无法正常工作+++

这是我尝试的另一种方法的代码,它只是在 componentWillMount() 中调用调度,然后直接将 this.props.store.selectedState 传递给子组件。

BuyDetails.js

class BuyDetails extends Component{
   constructor(props) {
      super(props);

      this.state = {open: true, openProfile:false, anchorEl: null,data:{}};
      //console.log('in buy details '+ JSON.stringify(this.state.data));

   }
   componentWillMount() {

    //dispatching the action here... it is still this.props.store.selectedState is still = {}
    this.props.getSelectedResult(22);
  }

   handleTouchTap = (event) => {
    console.log('in buy detail: ' + JSON.stringify(this.props.store.selectedState) + JSON.stringify(this.props.store.results));
    // This prevents ghost click.
    console.log("touch tap2");
    event.preventDefault();
    const tempState = this.state;
    tempState.openProfile = true
    tempState.anchorEl = event.currentTarget
    this.setState(tempState)
    /*this.setState({
      openProfile: true,
      anchorEl: event.currentTarget,
    });*/
  };

  handleRequestClose = () => {
    const tempState = this.state;
    tempState.openProfile = false
    tempState.anchorEl = null
    this.setState(tempState)
   /*this.setState({
      openProfile: false,
    });*/
  };
   handleToggle = () => this.setState({open: !this.state.open});

  render() {
    return <MuiThemeProvider>
          <div className="BuyDetails" id="BuyDetails" style={styles}>
          <div>
            <Toolbar style={appBarStyle}>

              <IconButton iconClassName="material-icons"
                style={{bottom: '0',height:'auto'}}
                onClick={this.handleToggle}>
                menu
                {/*<FontIcon className="material-icons" color={grey900} onClick={this.handleToggle}>menu</FontIcon>*/}
              </IconButton>              
              <ToolbarGroup style={groupStyle}>
                <ToolbarSeparator style={seperatorMargin}/>
                 <FontIcon style={searchIconnStyle} className="material-icons">search</FontIcon>
                 <ToolBarSearchField />
              </ToolbarGroup>

              <ToolbarGroup>
                <ToolbarSeparator style={residentialSeperatorStyle}/>
                <FlatButton label="Residential" style={selectedToolBarButtonStyle}/>
                <ToolbarSeparator style={seperatorStyle}/>
                <FlatButton label="Commerical" style={toolBarButtonStyle}/>
                <ToolbarSeparator style={seperatorStyle}/>
                <FlatButton label="JoellyR" style={toolBarButtonStyle} onTouchTap={this.handleTouchTap}/>
                <Popover open={this.state.openProfile}
                  anchorEl={this.state.anchorEl}
                  anchorOrigin={{horizontal: 'right', vertical: 'bottom'}}
                  targetOrigin={{horizontal: 'right', vertical: 'top'}}
                  onRequestClose={this.handleRequestClose}>
                  <MenuItem value={1} primaryText="Price Range" />
                  <MenuItem value={2} primaryText="values" />
                </Popover>
              </ToolbarGroup>

            </Toolbar>
            </div>
            <Drawer
              open={this.state.open}
              containerStyle={{top:'inherit', boxShadow:'(0,0,0,0)', border:'0px', borderRight:'1px solid', borderColor: 'rgba(0,0,0,0.3)'}}>
            </Drawer>
            <div style={this.state.open ? drawerExpanded : drawerCollapsed }>
              <Paper style={paperStyle}>
                <BuyDetailGridList data={this.props.store.selectedState}/>
              </Paper>
            </div>
          </div>
        </MuiThemeProvider>
  }
}
function isEmpty(obj) {
    for(var key in obj) {
        if(obj.hasOwnProperty(key))
            return false;
    }
    return true;
}
export default BuyDetails;

【问题讨论】:

  • 您说您在路径中传递了 id,但您没有任何路由正在处理其中包含 id 的路径。它看起来像&lt;Route path="/:itemId/details" ... /&gt;
  • 对不起,我应该更清楚.. 我想在路径中传递 id,但目前我只是试图将 id 作为硬编码值传递...... 22
  • 还建议避免使用this.setState 引用this.state(如this.setState({open: !this.state.open});),因为状态操作通常是批处理以提高性能。正确的方法是使用this.setState((prevState, props) =&gt; { return someNewState; })
  • 哦,谢谢你的提示!一些代码是我试图在这里和那里遵循的点点滴滴......我可能需要回去做一些重构......谢谢! :)

标签: javascript reactjs redux react-router react-redux


【解决方案1】:

我不会获取详细信息组件中的项目,至少不会明确地获取。

考虑:

一个细节组件:

class DetailsComponent extends React.Component {
    // the item is now available in props.item
}

function mapStateToProps(state, props) {
    return {
        item: state.getSelectedItem()
    };
}

export default connect(mapStateToProps)(DetailsComponent);

一个列表组件:

class ListComponent extends React.Component {
    ...

    onImageClick = (item) => {
        this.props.setSelectedItem(item);
    }

    ...
}

这依赖于设置一些相关状态的set/getSelectedItem 操作。 details 组件在挂载时会自动抓取选中的项目。

要考虑的另一件事是,如果两个组件同时呈现(例如,在列表/详细样式 UI 中),则将选定状态提升到父状态(both 组件)。

class ParentComponent extends React.Component {
    ...

    onItemSelected = (item) => {
        this.setState({ selectedItem: item });
    }

    render() {
        return (
            <ListComponent onItemSelected={ this.onItemSelected }/>
            <DetailsComponent item={ this.state.selectedItem }/>
        );
    }
}

话虽如此,您发布了很多代码,但很难说出发生了什么。希望我在上面写的内容对您的问题有所帮助。

【讨论】:

  • 嗨 Tyler,感谢您的快速回复 :).. 虽然我已经尝试过这种方法并且它确实有效(你是对的)但我遇到这个问题的问题是 .. 在页面上重新加载状态消失了......虽然你的回复确实让我认为问题可能出在其他地方......也许redux商店需要持久性?但我试图避免这种情况......也许将对服务器的调用添加到 GET_RESULTS 减速器方法中?不确定
  • “也许将对服务器的调用添加到 GET_RESULTS 减速器方法中”——我认为在减速器中使用动作创建者不是一个好习惯。重新挂载根组件时更新状态或使用 React Router 的 onWorkspaceEnter 检查商店中是否有必要的信息,如果没有,则同步。
  • 另一种选择是使用 github.com/rt2zz/redux-persist... 之类的模块,尽管这不是必需的。
  • 是的,我试图避免持久性......所以我会看看 onWorkspace enter.. TYVM :)
猜你喜欢
  • 1970-01-01
  • 2017-08-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-11
  • 1970-01-01
  • 2019-01-10
  • 1970-01-01
相关资源
最近更新 更多