DEV Community

Andrew Simmons
Andrew Simmons

Posted on

Handling the Most basic Login with React and Sinatra

Introduction

In this article I will be explaining how to handle a basic login that only uses a username for authentication. Part of the purpose is to be able to mock having a currentUser on the front end without getting into real authentication. For this project we will be using React on the front-end and Sinatra on the server side.

Handling a Successful Login

Setting Up The Basic Route Server-Side

First we're going to set up the route for our server to perform a GET request. Since we're going to be authenticating the user by the username it's quite simple:
In my application the user has_many trips, which we will want access to later.

get "/users/:username" do
    user = User.find_by_username(params[:username])
    user.to_json(include: [:trips])
  end
Enter fullscreen mode Exit fullscreen mode

Setting up the Login Client-Side

We are going to keep it simple here as well, just a basic input with a submit button. First we're going to set up our basic functions for handling the submit and change for the form data. Don't worry about findCurrentUser, we'll get to that in a bit.

const [userLogin, setUserLogin] = useState("")
const handleSubmit = (e) => {
  e.preventDefault()
  findCurrentUser(userLogin)
}
const handleChange = e => {
  setUserLogin(e.target.value)
}
Enter fullscreen mode Exit fullscreen mode

Using our state we can set up our form for a very basic login:

<div>
  <form onSubmit={handleSubmit}>
     <label htmlFor="login" value="Username">Username:   </label><br/>
     <input type="text" name="login" value={userLogin} onChange={handleChange} autoFocus={true}/>
     <input type="submit" value="Login"/>
   </form>
</div>
Enter fullscreen mode Exit fullscreen mode

an input field with a username label and a submit button

Making the GET Request

Now to set up the fetch, we're going to be using async and await to help our code look a little cleaner and easier to understand:
I'm using the useHistory hook from react-router-dom to redirect the user to their trips page after a successful login

async function findCurrentUser(username) {
      const response = await fetch(`${baseUrl}/users/${username}`)
      const user = await response.json()
      changeUser(user)
      history.push(`/users/${user.id}/trips`)
      }
    }
Enter fullscreen mode Exit fullscreen mode

We are also bringing changeUser from our App component via props to handle the state of currentUser:

function App() {
  const [currentUser, setCurrentUser] = useState(null)

  const changeUser = (user) => {
    setCurrentUser(user)
  }
return (
 <Route exact path="/login">
     <Login changeUser={changeUser}/>
 </Route>
)
}
Enter fullscreen mode Exit fullscreen mode

With all of that set up someone should be able to successfully login!
the login page from earlier with traveladdict in the username field
Success!!!
a new page with a list of trips for the user that just logged in. The nav bar also changed a few of it's options

Failed Login Attempt

Handling Failed Attempt Server-Side

But what happens when someone tries to login, and they don't already have an account?
an error message that says:Unhandled Rejection (TypeError): Cannot read property 'id' of null

First let's look at how to handle this error on the server-side. Sinatra has a helper method status that we are going to take advantage of. It allows us to change the HTTP response status code. 401 is the status code for unauthorized/unauthenticated, which seems to fit what we want to return. So if the user exist, return the user, else change the status code to 401 and return an error message.

get "/users/:username" do
    user = User.find_by_username(params[:username])
      if user
        user.to_json(include: [:trips])
      else
        status 401
        { errors: "user doesn't exist" }.to_json
      end
  end
Enter fullscreen mode Exit fullscreen mode

Handling Failed Attempt Client-Side

Now that we are changing the status code on a failed attempt, we can use that to deal with the client side of things. We can use response.status to access the status code, and if it equals 401, do nothing, otherwise perform the actions of a successful login.

async function findCurrentUser(username) {
      const response = await fetch(`${baseUrl}/users/${username}`)
      if (response.status === 401) {
        return null
      } else {
        const user = await response.json()
        changeUser(user)
        history.push(`/users/${user.id}/trips`)
      }
    }
Enter fullscreen mode Exit fullscreen mode

Let's give it a try now:
the login screen that was displayed earlier with the console open to the right. In the console we see a failed GET request with a 401 status code

Great! Now we're no longer getting an error when someone has a failed login attempt, but there's one problem, when our user tries to login, it looks like nothing is happening for them. Let's fix that by giving our user an error message.

Displaying an Error message

To handle the display of an error message, we are going to handle it with useState:
const [error, setError] = useState(null)
And we're going to change our fetch just a little bit, instead of just returning null, we're going to set an error message:

    async function findCurrentUser(username) {
      const response = await fetch(`${baseUrl}/users/${username}`)
      if (response.status === 401) {
        setError("That user doesn't exist, try again or sign up for an account!")
      } else {
        const user = await response.json()
        changeUser(user)
        history.push(`/users/${user.id}/trips`)
      }
    }
Enter fullscreen mode Exit fullscreen mode

Now that we have our error message all we need to do is display it, and since we are using state and setting it to null on render it will only display when it has been changed from null.

<div>
      <form onSubmit={handleSubmit}>
        <h3 style={{color:"red"}}>{error}</h3>
        <label htmlFor="login" value="Username">Username:</label><br/>
        <input type="text" name="login" value={userLogin} onChange={handleChange} autoFocus={true}/>
        <input type="submit" value="Login"/>
      </form>
    </div>
Enter fullscreen mode Exit fullscreen mode

So that it stands out I've put it as an h3 with text color of red.

Conclusion

That's the basics of how to handle both a successful and failed login attempts with just a username, now you can try to figure out how to handle what's going to get displayed based on the current user being logged in or not. Good luck!

Extras

For more information on some of the things I mentioned checkout:
useHistory Hook

Discussion (0)