<div id="root"></div>
body {
  height: 100vh;
  margin: 0;
  display: grid;
  place-items: center;
}
.box {
  width: 300px;
  h1 {
    font-size: 20px;
    margin: 0 0 1rem 0;
  }
}

button {
  margin: 3px;
}
View Compiled
const FormInput = ({
  name,
  type,
  placeholder,
  onChange,
  className,
  value,
  error,
  children,
  label,
  ...props
}) => {
  
  return (
    <React.Fragment>
      <label htmlFor={name}>{label}</label>
      <input
        id={name}
        name={name}
        type={type}
        placeholder={placeholder}
        onChange={onChange}
        value={value}
        className={className}
        style={error && {border: 'solid 1px red'}}
      />
      { error && <p>{ error }</p>}
    </React.Fragment>
  )
}

FormInput.defaultProps = {
  type: "text",
  className: ""
}

FormInput.propTypes = {
  name: PropTypes.string.isRequired,
  type: PropTypes.string,
  placeholder: PropTypes.string.isRequired,
  type: PropTypes.oneOf(['text', 'number', 'password']),
  className: PropTypes.string,
  value: PropTypes.any,
  onChange: PropTypes.func.isRequired
}

class LoginPage extends React.Component {
  state = {
    user: {
      username: "",
      password: ""
    },
    errors: {},
    submitted: false
  };

  handleChange = event => {
    const { user } = this.state;
    user[event.target.name] = event.target.value;
    this.setState({ user });
  };

  onSubmit = () => {
    const {
      user: { username, password }
    } = this.state;
    let err = {};

    if (!username) {
      err.username = "Enter your username!";
    }

    if (password.length < 8) {
      err.password = "Password must be at least 8 characters!";
    }

    this.setState({ errors: err }, () => {
      if (Object.getOwnPropertyNames(this.state.errors).length === 0) {
        this.setState({ submitted: true });
      }
    });
  };

  render() {
    const {
      submitted,
      errors,
      user: { username, password }
    } = this.state;
    return (
      <React.Fragment>
        {submitted ? (
          <p>Welcome onboard, {username}!</p>
        ) : (
          <React.Fragment>
            <h3>Login!</h3>
            <FormInput
              label="Username"
              name="username"
              type="text"
              value={username}
              onChange={this.handleChange}
              placeholder="Enter username..."
              error={errors.username}
              required
              className="input"
            />

            <FormInput
              label="Password"
              name="password"
              type="password"
              value={password}
              onChange={this.handleChange}
              placeholder="Enter password..."
              error={errors.password}
              className="input"
              required
            />

            <Button
              type="submit"
              label="Submit"
              className="button"
              handleClick={this.onSubmit}
            />
          </React.Fragment>
        )}
      </React.Fragment>
    );
  }
}

const Button = (props) => (
  <button
    type={props.type}
    className={props.className}
    onClick={props.handleClick}
  >
    {props.label}
  </button>
)


class App extends React.Component {
  
  render() {
    return (
      <div className="box">
        <LoginPage />
      </div>
    )
  }
}


ReactDOM.render(
  <App />,
  document.getElementById('root')
);
View Compiled

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.4/css/bulma.min.css

External JavaScript

  1. https://unpkg.com/react@16.7.0-alpha.0/umd/react.production.min.js
  2. https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.production.min.js
  3. https://cdn.jsdelivr.net/npm/immer/dist/immer.umd.js
  4. https://cdnjs.cloudflare.com/ajax/libs/node-uuid/1.4.8/uuid.min.js
  5. https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.7.2/prop-types.min.js