17
votes

I'm working on my first React/Redux project and I have a little question. I've read the documentation and watched the tutorials available at https://egghead.io/lessons/javascript-redux-generating-containers-with-connect-from-react-redux-visibletodolist.

But I still have one question. It's about a login page. So I have a presentational component named LoginForm :

components/LoginForm.js

import { Component, PropTypes } from 'react'

class LoginForm extends Component {
   render () {
      return (
         <div>
            <form action="#" onSubmitLogin={(e) => this.handleSubmit(e)}>
               <input type="text" ref={node => { this.login = node }} />
               <input type="password" ref={node => { this.password = node }} />
               <input type="submit" value="Login" />
            </form>
         </div>
      )
   }

   handleSubmit(e) {
      e.preventDefault();
      this.props.onSubmitLogin(this.login.value, this.password.value);
   }
}

LoginForm.propTypes = {
   onSubmitLogin: PropTypes.func.isRequired
};

export default LoginForm;

And a container component named Login which pass data to my component. Using react-redux-router, I call this container (and not the presentationnal component) :

containers/Login.js

import { connect } from 'react-redux'
import { login } from '../actions/creators/userActionCreators'
import LoginForm from '../components/LoginForm'

const mapDispatchToProps = (dispatch) => {
   return {
      onSubmitLogin: (id, pass) => dispatch(login(id, pass))
   }
};

export default connect(null, mapDispatchToProps)(LoginForm);

As you can see, I'm using the connect method provide by redux to create my container.

My question is the following one :

If I want my Login container to use multiple views (for example : LoginForm and errorList to display errors), I need to do it by hand (without connect because connect take only one argument). Something like :

class Login extends Component {

   render() {
      return (
         <div>
            <errorList />
            <LoginForm onSubmitLogin={ (id, pass) => dispatch(login(id, pass)) } />
         </div>
      )
   }

}

Is it a bad practice ? Is it better to create another presentational component (LoginPage) which use both errorList and LoginForm and create a container (Login) which connect to LoginPage ?


EDIT: If I create a third presentational component (LoginPage), I'll have to pass data twice. Like this : Container -> LoginPage -> LoginForm & ErrorList. Even with context, it don't seems to be the way to go.

2

2 Answers

29
votes

I think that what you have in your second example is very close. You can create just one container component that's connected and render multiple presentational components.

In your first example, there actually isn't a separate container component:

import { connect } from 'react-redux'
import { login } from '../actions/creators/userActionCreators'
import LoginForm from '../components/LoginForm'

const mapDispatchToProps = (dispatch) => {
   return {
      onSubmitLogin: (id, pass) => dispatch(login(id, pass))
   }
};

// `LoginForm` is being passed, so it would be the "container"
// component in this scenario
export default connect(null, mapDispatchToProps)(LoginForm);

Even though it's in a separate module, what you're doing here is connecting your LoginForm directly.

Instead, what you can do is something like this:

containers/Login.js

import { connect } from 'react-redux'
import { login } from '../actions/creators/userActionCreators'
import LoginForm from '../components/LoginForm'
import ErrorList from '../components/ErrorList'

class Login extends Component {

   render() {
      const { onSubmitLogin, errors } = this.props;

      return (
         <div>
            <ErrorList errors={errors} />
            <LoginForm onSubmitLogin={onSubmitLogin} />
         </div>
      )
   }

}

const mapDispatchToProps = (dispatch) => {
   return {
      onSubmitLogin: (id, pass) => dispatch(login(id, pass))
   }
};

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

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

Note that the Login component is now being passed to connect, making it the "container" component and then both the errorList and LoginForm can be presentational. All of their data can be passed via props by the Login container.

-2
votes

I truly believe that you need to build all your components as Presentational Components. At the moment you need a Container Component, you might use {connect} over one of the existing Presentational and convert into a Container one.

But, that is only my view with short experience in React so far.