如何从子组件中调用调度操作?

时间:2018-05-10 17:54:16

标签: reactjs redux react-redux

我有一个App组件,一个Login组件。 在我的登录页面中,当用户成功提供其凭证时,API会将用户数据作为响应返回。我想将该用户数据存储为全局状态。这就是我使用redux的原因。

我的 index.js 文件内容:

const saveUserData = (state = {
  user: {}
}, action) => {
  switch (action.type) {
    case "UPDATE_USER":
      state = {
          ...state,
          user: action.payload
      };
      break;
  }
  return state;
};

const store = createStore(saveUserData);

store.subscribe( () => {
  console.log(state.getState());
});

render(
    <Provider store={store}><App /></Provider>
    , window.document.getElementById('app'));

在我的 App.js 文件中:

class App extends React.Component {
  render() {
    return (
        <BrowserRouter>
          <Root>
            <Route exact path="/" component={Home}/>
            <Route exact path="/login" component={Login}/>
            <Route exact path="/register" component={Register}/>
            <Route exact path='/books' component={Books}/>
            <Route path='/books/:id' component={BookDetail}/>
          </Root>
        </BrowserRouter>
    );
  };
}

const mapStateToProps = (state) => {
  return {
    userData: state.saveUserData
  }
};

const mapDispatchToProps = (dispatch) => {
  return {
    updateUser: (user) => {
      dispatch({
        type: "UPDATE_USER",
        payload: user
      })
    }
  }
};

export default connect(mapStateToProps, mapDispatchToProps)(App)

在我的 Login.js 文件中:

handleSubmit(event) {
    event.preventDefault();
    if (!event.target.checkValidity()) {
      return;
    }

    const {email, password, remember_me} = this.state.formData;
    const url = api.api_url + "auth/login";

    fetch(url, {
      method: 'POST',
      headers: new Headers({
        Accept: 'application/json',
        'Content-Type': 'application/json',
      }),
      body: JSON.stringify({
        email: email,
        password: password,
        remember_me: remember_me
      }),
    })
    .then(res => res.json())
    .catch(error => console.error('Error:', error))
    .then(response => {
      if (typeof response.access_token == "undefined")
        this.setState({display_errors: true, errors: response.error.user_authentication});
      else{
        localStorage.setItem('jwtToken', response.access_token);
        this.props.updateUser(response.user); // Here I want to call the dispatcher to save the response user data in global store
        this.props.history.push({
          pathname: '/',
          state: {message: response.message}
        });
      }
    });
  }

在登录组件中,我想用App调用updateUser组件的调度程序方法this.props.updateUser(response.user);。但它不起作用。

有谁知道如何实现这一目标?您可以在github

中查看完整代码

5 个答案:

答案 0 :(得分:1)

这是我怎么做的。首先,您不需要将app.js连接到商店,因为它没有使用任何状态或调度操作。使其成为一个功能组件。

const App = () => {
  render() {
    return (
        <BrowserRouter>
          <Root>
            <Route exact path="/" component={Home}/>
            <Route exact path="/login" component={Login}/>
            <Route exact path="/register" component={Register}/>
            <Route exact path='/books' component={Books}/>
            <Route path='/books/:id' component={BookDetail}/>
          </Root>
        </BrowserRouter>
    );
  };
}

export default App;

最佳做法是在switch语句中添加默认大小写。

const saveUserData = (state = {
  user: {},
  display_errors: false,
  errors: {}
}, action) => {
  switch (action.type) {
    case "UPDATE_USER":
      state = {
          ...state,
          user: action.payload
      };
     case "DISPLAY_ERRORS":
      state = {
          ...state,
          errors: action.payload,
          display_errors: true
      };
     default: 
     return state;
  } 
};

创建一个单独的操作文件,并在actions.js

中移动您的获取请求
export default updateUser = (email, password, remember_me) => dispatch => {
fetch(url, {
      method: 'POST',
      headers: new Headers({
        Accept: 'application/json',
        'Content-Type': 'application/json',
      }),
      body: JSON.stringify({
        email: email,
        password: password,
        remember_me: remember_me
      }),
    })
    .then(res => res.json()).
    .then(response => {
  if (typeof response.access_token == "undefined")
    dispatch({type: DISPLAY_ERRORS, payload: response.error.user_authentication });
  else{
    localStorage.setItem('jwtToken', response.access_token);

  }
});
    .catch(error => console.error('Error:', error))

}

然后您可以导入此操作文件,并可以访问您要使用的updateUser。

import { updateUser } from "action.js";

//now you have access to this function 
this.props.updateUser(email, password, remember_me);
const mapStateToProps = state => ({
  //state.....
})
export default connect(mapStateToProps, { updateUser })(Login);

答案 1 :(得分:0)

理想情况下,您应将updateUser方法存储在操作文件中,并在需要使用它的地方导入该方法。

<强> actions.js

export const updateUser = (user) => {
  dispatch({
    type: "UPDATE_USER",
    payload: user
  })
}

然后,无论你想使用它,都可以使用:

import { updateUser } from 'path/to/actions.js'
import { bindActionCreators } from 'redux'; 

...

const mapDispatchToProps = (dispatch) => {
  return bindActionCreators({
    updateUser
  }, dispatch);
};

如果您发现自己有大量的操作,那么当您的应用扩展其大小和复杂性时,您可能会采用这种方法,这种方法可以帮助您正确管理您的操作。

始终考虑 Separation of Concerns - 您的 actions.js 文件的目的是包含您的所有操作,而不是其他内容,并且放置得更好这里比在 app.js 文件中。但是,我个人还有一个 actions 文件夹,并在该目录的 user.js 文件中包含此方法。

我希望这会有所帮助。

修改

我已将bindActionCreators添加到mapDispatchToProps。抱歉,我这样做已经有一段时间了 - 我亲自从我的 app.js 导出store.dispatch并导入并使用我需要的地方,而不是使用mapDispatchToProps 。如果您愿意,我可以进一步解释,但实际上最好使用提供的mapDispatchToProps方法。

答案 2 :(得分:0)

正如上面的用户所说,最好考虑分离关注点。这就是我要做的事情:

  • 为每个范围创建一套完整的redux生命周期文件,例如login。这包括行动,传奇和减速器。
  • 针对您的案例login中的每个范围,我会将其链接发送给container,该global state可以访问connect() connect()。只有那个组件可以访问调度,它的子组件只应该用它来发送它们使它们变得愚蠢并保持真实的来源。

注意:只要您使用redux生命周期,调用动作创建者的位置无关紧要,只要它具有{{{},它就可用于您的全局状态。 1}}

答案 3 :(得分:0)

您可以在app.js中添加和更改以下内容

import { bindActionCreators } from 'redux';
import * as actionCreators from "../yourActionFile"

class App extends React.Component {

   handleSubmit(e){
       e.preventDefault();
       this.props.updateUser(("yourArgumentsHere"))
   }


          render() {
            return (
                <BrowserRouter>
                  <Root>
                    <Route exact path="/" component={Home}/>
                    <Route exact path="/login" component={Login}/>
                    <Route exact path="/register" component={Register}/>
                    <Route exact path='/books' component={Books}/>
                    <Route path='/books/:id' component={BookDetail}/>
                  </Root>
                </BrowserRouter>
            );
          };
        }

        const mapStateToProps = (state) => {
          return {
            userData: state.saveUserData
          }
        };

        const mapDispatchToProps = (dispatch) => {
        return {
          updateUser: bindAtionCreators(actionCreators.updateUser, dispatch)
          //the logic for updating your store or "global state" should be done in the reducer
          }
        }
      };


      export default connect(mapStateToProps, mapDispatchToProps)(App)

您可以将handleSubmit函数作为props传递给子项,也可以将mapDispatchToProps和handleSubmit添加到子组件。无论哪种方式,如果您依赖商店购买应用中的所有动态数据,一旦商店被reducer更新,更改就会逐渐减少。

答案 4 :(得分:0)

首先要做的一些事情。

saveUserData似乎是你的减速器,为什么它应该在props中曝光?只有使用mapStateToProps的道具才能使用与您的组件相关的状态数据。因此,例如,如果您的状态中有currentUser的用户数据实际用于您的组件,那么您应该这样做 -

const mapStateToProps = (state) => {
  return {
    currentUser: state.currentUser
  };
};

现在,在组件内部,您可以 - this.props.currentUser来获取数据。

下一个重要的一点是,您需要使用bindActionCreators中的redux。在actions.js中定义操作,然后在组件文件中定义 -

import {updateUser} from 'path/to/actions.js'
import {bindActionCreators} from 'redux';
...

const mapDispatchToProps = (dispatch) => {
  return bindActionCreators({updateUser}, dispatch);
};

最后 -

export default connect(mapStateToProps, mapDispatchToProps)(Login);

修改

检查了你的github回购。我认为第一个问题是将Login组件导入{Login} App.js,而import Login应该是Login.js,而不是从Route

导出为默认值

第二件事是使用 <Route exact path="/login" render={() => (<Login/>)}/> 的render属性中的组件,如下所示 - SELECT `mc_ee_page_id` as Page, IF ( mc_ee_chp_id = 1, ( CASE WHEN (is_marked = 0 and mk_no_mark = 1 ) THEN "Y" WHEN (is_marked = 1 and mk_no_mark = -1) THEN "N" WHEN (is_marked = 1 and mk_no_mark = 1 ) THEN "O" ELSE "X" END ) , "" ) AS chp_1 , IF ( mc_ee_chp_id = 2, ( CASE WHEN (is_marked = 0 and mk_no_mark = 1 ) THEN "Y" WHEN (is_marked = 1 and mk_no_mark = -1) THEN "N" WHEN (is_marked = 1 and mk_no_mark = 1 ) THEN "O" ELSE "X" END ) , "" ) AS chp_2 , IF ( mc_ee_chp_id = 3, ( CASE WHEN (is_marked = 0 and mk_no_mark = 1 ) THEN "Y" WHEN (is_marked = 1 and mk_no_mark = -1) THEN "N" WHEN (is_marked = 1 and mk_no_mark = 1 ) THEN "O" ELSE "X" END ) , "" ) AS chp_3 , : : IF ( mc_ee_chp_id = 12, ( CASE WHEN (is_marked = 0 and mk_no_mark = 1 ) THEN "Y" WHEN (is_marked = 1 and mk_no_mark = -1) THEN "N" WHEN (is_marked = 1 and mk_no_mark = 1 ) THEN "O" ELSE "X" END ) , "" ) AS chp_12 FROM `maxcare_mc_ee_hw` WHERE `stud_id` = '3312' AND `mc_ee_level_id` = '1' GROUP BY mc_ee_chp_id, mc_ee_page_id ORDER BY mc_ee_page_id asc LIMIT 500

这解决了我的问题,所以应该为你做同样的事情。