Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

input element loses focus after typing #23

Closed
HannaHoJ opened this issue Jun 12, 2017 · 9 comments
Closed

input element loses focus after typing #23

HannaHoJ opened this issue Jun 12, 2017 · 9 comments

Comments

@HannaHoJ
Copy link

Hey i'm trying to use your valueLink approach to validate my user inputs.
The issue i get is that the input elements lose focus when i type one character. So i have to click in the form a second time to write again. This happens after each character.
But there are good news. The validation itself works perfect.
I'm new to react, so it is also possible that i missed a major point.

@gaperton
Copy link

Could you share your sources, please? Examples in this repo works fine.

@HannaHoJ
Copy link
Author

I basically followed your tutorial on these sites:
https://medium.com/@gaperton/managing-state-and-forms-with-react-part-1-12eacb647112
https://medium.com/@gaperton/react-forms-with-value-links-part-2-validation-9d1ba78f8e49

And thats the component where i've used it in.

import React from 'react'
import { withRouter } from 'react-router'
import { graphql } from 'react-apollo'
import gql from 'graphql-tag'
import PropTypes from 'prop-types'
import { Alert, Label } from 'reactstrap';
import Link, { LinkedComponent } from 'valuelink'
import { Input, TextArea, Checkbox } from 'valuelink/tags'

class CreateUser extends LinkedComponent {

  static propTypes = {
      router: PropTypes.object.isRequired,
      createUser: PropTypes.func.isRequired,
      signinUser: PropTypes.func.isRequired,
      data: PropTypes.object.isRequired,
  }

  state = {
      email: this.props.location.query.email || '',
      password: '',
      name: '',  
  }

  handleUserInput = (e) =>{
    const name = e.target.name;
    const value = e.target.value;
    this.setState({[name]: value});
  }

  handleSubmit(event) {
      event.preventDefault();
  }
  isSubmittable() {
      return this.state.email && this.state.password && this.state.name;
  }

 
  render() {
 
    const FormInput = ({ label, ...props }) => (
      <span className="form-group" >
        <Label className='form-label'> { label } </Label>
          <Input className='from-control' { ...props } />
          <div className="error-placeholder">
            { props.valueLink.error || '' }
          </div>
      </span>
    );

    const nameLink=Link.state(this, 'name')
      .check( x => x.length >= 2, 'You forgot to type a name')
      .check( x => x.indexOf( ' ' ) < 0, "The name you choose shouldn't contain spaces");

    const emailRegexPattern = /^([\w.%+-]+)@([\w-]+\.)+([\w]{2,})$/i;
    const emailLink=Link.state(this, 'email')
      .check( x => x, 'You forgot to enter your email')
      .check( x => x.match(emailRegexPattern), "Please enter a valid email adress");

    const passwordLink=Link.state(this, 'password')
      .check( x => x.length >= 6, 'Your password should be min 6 characters long')
      .check( x => x.match(/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6,}$/), 'Your Password should contain at least one digit');


    if (this.props.data.loading) {
      return (<div>Loading</div>)
    }

    // redirect if user is logged in
    if (this.props.data.user) {
      console.warn('already logged in')
      this.props.router.replace('/')
    }

    return (
      <div className='w-100 pa4 flex justify-center'>
        <div style={{ maxWidth: 400 }} className=''>
          <form onSubmit={this.handleSubmit}>
            <div>
              <div>
                <FormInput label="Name" className="w-100 pa3 name-from" type="text" placeholder="name" valueLink={ nameLink } />
                <FormInput label="Email" className="w-100 pa3 email-from" type="email" placeholder="[email protected]" valueLink={ emailLink } />
                <FormInput label="Password" className="w-100 pa3 pwd-from" type="password" placeholder="password" valueLink={ passwordLink } />
              </div>
              <button type="submit" disabled={(this.isSubmittable() ? "" : "disabled")} className={'pa3 bn ttu pointer' + (this.isSubmittable() ? " bg-black-10 dim" : " black-30 bg-black-05 disabled")} onClick={this.createUser}>Sign up</button>
          </div>
        </form>
      </div>
    </div>
  )
}

createUser = () => {
  const { email, password, name } = this.state

  this.props.createUser({ variables: { email, password, name } })
      .then((response) => {
          this.props.signinUser({ variables: { email, password } })
              .then((response) => {
                  window.localStorage.setItem('graphcoolToken', response.data.signinUser.token)
                  this.props.router.replace('/')
              }).catch((e) => {
                  console.error(e)
                  this.props.router.replace('/')
              })
      }).catch((e) => {
          console.error(e)
          this.props.router.replace('/')
      })
  }
}

const createUser = gql `
  mutation ($email: String!, $password: String!, $name: String!) {
    createUser(authProvider: {email: {email: $email, password: $password}}, name: $name) {
      id
    }
  }
`
const signinUser = gql `
  mutation ($email: String!, $password: String!) {
    signinUser(email: {email: $email, password: $password}) {
      token
    }
  }
`
const userQuery = gql `
  query {
    user {
      id
    }
  }
`
export default graphql(createUser, { name: 'createUser' })(
    graphql(userQuery, { options: { forceFetch: true } })(
        graphql(signinUser, { name: 'signinUser' })(
            withRouter(CreateUser))
    )
)

@gaperton
Copy link

I see. Try to move this out of the render() function. You create the new component every time you render the stuff, which prevents react from understanding that this is the same component and (likely) because of that it cannot preserve the focus.

const FormInput = ({ label, ...props }) => (
  <span className="form-group" >
    <Label className='form-label'> { label } </Label>
      <Input className='from-control' { ...props } />
      <div className="error-placeholder">
        { props.valueLink.error || '' }
      </div>
  </span>
);

@gaperton
Copy link

Also, you can use this.linkAt( 'name' ) instead of Link.state(this, 'name'), which looks nicer. That's the point of using LinkedComponent. Link.state(this, 'name') works in every component, not just Linked one.

@gaperton
Copy link

gaperton commented Jun 13, 2017

Validation can be refactored like this:

  getValidatedLinks(){
      const links = this.linkAll(); // or this.linkAll( 'attr1', 'attr2', ... ) if you need just the part of the state
      
      links.name
          .check( x => x.length >= 2, 'You forgot to type a name')
          .check( x => x.indexOf( ' ' ) < 0, "The name you choose shouldn't contain spaces");

      const emailRegexPattern = /^([\w.%+-]+)@([\w-]+\.)+([\w]{2,})$/i;

      links.email
          .check( x => x, 'You forgot to enter your email')
          .check( x => x.match(emailRegexPattern), "Please enter a valid email adress");

      links.password
          .check( x => x.length >= 6, 'Your password should be min 6 characters long')
          .check( x => x.match(/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6,}$/), 'Your Password should contain at least one digit');

      return links;
  }

  render() {
    const links = this.getValidatedLinks();
    ...

@gaperton
Copy link

gaperton commented Jun 13, 2017

Let me know if you'll have any further problems. I think I will update the tutorials to reflect the last version of the API.

@gaperton
Copy link

Also, in a real application you might want to create generic validation functions so they can be reused across the forms. Like this:

// Do this once...
const emailRegexPattern = /^([\w.%+-]+)@([\w-]+\.)+([\w]{2,})$/i;
const isEmail = x => x.match(emailRegexPattern);
isEmail.error = "Please enter a valid email adress";

// and then just 
const emailLink = this.linkAt( 'email' ).check( isEmail );

Much better, right? :)

@gaperton
Copy link

gaperton commented Jun 13, 2017

Or, simply

function shouldBeEmail( link ){
    link.check( x => x, 'You forgot to enter your email')
          .check( x => x.match(emailRegexPattern), "Please enter a valid email adress");
    return link;
}

There are a lot of ways to make it look nicer than it is.

@gaperton
Copy link

Please, reopen if the suggestion about FormInput won't help. I'm pretty sure it will.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants