Airbnb clone, activate the modal

Join the 2022 Full-Stack Web Dev Bootcamp!


This post is part of a new series where we build a clone of Airbnb with Next.js. See the first post here.

Let’s first hide the form, which now loads by default on every page load.

We just need to default to false the hooks we defined in components/Layout.js.

Instead of:

const [showModal, setShowModal] = useState(true)
const [showLoginModal, setShowLoginModal] = useState(true)

We set:

const [showModal, setShowModal] = useState(false)
const [showLoginModal, setShowLoginModal] = useState(false)

Now, we are going to enable the modals when these event occur:

  • the Sign Up or Log in links are clicked in the nav bar, in components/Header.js
  • the Reserve button in the pages/houses/[id].js component is clicked after a user chooses a set of dates

It’s just 3 different places, but we can immediately spot a problem: the state of the modals is being centralized and we’d need to pass around both the state and the functions to update it, too much.

And we’re just starting out.

Soon we’ll have to manage the user logins, and that would also need more state management.

So, we’re going to add one library to our project, one library that helps us manage the state easily.

There are solutions in React that are quite complicated, and I’m sure they are useful in many scenarios, but I like to keep my code as simple as possible.

Simple is understandable.

Simple is beautiful.

Complexity should be avoided at all costs, and if possible hidden away in libraries that expose a simple interface to us.

It’s the case of this library, which is called easy-peasy.

Go take a look at their website https://easy-peasy.now.sh/ and then come back.

First of all, stop the Next.js server and run

npm install easy-peasy

to install the library.

Then restart the Next.js server with npm run dev.

Now, first of all we need to create a store. The store is the place where we’ll store our state, and the functions needed to modify it.

Create the store in the file store.js in the root of the project, with this content:

store.js

import { createStore, action } from 'easy-peasy'

export default createStore({})

We’ll add more things to this file later.

Now we need to do one thing - we need to wrap all the Next.js app into a component provided by easy-peasy, and the way Next.js provides us to do it is to create a file called _app.js in the pages folder.

Open pages/_app.js, which now has this content:

pages/_app.js

import App from 'next/app'
import '../styles/globals.css'

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp

Now we’re going to import the store we defined in store.js, and we also import the StoreProvider component from easy-peasy.

With this component, we wrap the default Component and we pass the store as a prop to it:

pages/_app.js

import '../styles/globals.css'
import { StoreProvider } from 'easy-peasy'
import store from '../store'

function MyApp({ Component, pageProps }) {
  return (
    <StoreProvider store={store}>
      <Component {...pageProps} />
    </StoreProvider>
  )
}

export default MyApp

This operation makes now our store available in every component of the app.

So let’s now centralize the state we added to components/Layout.js in the last lesson, to the store.js file.

If you think we wasted some time in the last lesson, we didn’t - most of the times those implementations are iterative. You first try the simplest solution, and then move on to more complex scenarios as the needs evolve. Now we know what things we need.

store.js

import { createStore, action } from 'easy-peasy'

export default createStore({
  modals: {
    showModal: false,
    showLoginModal: false,
    showRegistrationModal: false,
    setShowModal: action((state) => {
      state.showModal = true
    }),
    setHideModal: action((state) => {
      state.showModal = false
    }),
    setShowLoginModal: action((state) => {
      state.showModal = true
      state.showLoginModal = true
      state.showRegistrationModal = false
    }),
    setShowRegistrationModal: action((state) => {
      state.showModal = true
      state.showLoginModal = false
      state.showRegistrationModal = true
    })
  }
})

We defined a modals object with some properties, and 4 actions, which we’ll use in our app components to change the state.

Let’s start from the Header.js component. When our Log in and Sign up buttons are clicked, we want to activate the correct modal.

In there, we import useStoreActions to be able to access the store functions:

import { useStoreActions } from 'easy-peasy'

and inside the component we initialize those actions to be used:

const setShowLoginModal = useStoreActions(
  (actions) => actions.modals.setShowLoginModal
)
const setShowRegistrationModal = useStoreActions(
  (actions) => actions.modals.setShowRegistrationModal
)

Now we can call setShowLoginModal and setShowRegistrationModal as regular functions, and this is what we’re going to do:

<nav>
  <ul>
    <li>
      <a href="#" onClick={() => setShowRegistrationModal()}>
        Sign up
      </a>
    </li>
    <li>
      <a href="#" onClick={() => setShowLoginModal()}>
        Log in
      </a>
    </li>
  </ul>
</nav>

Great! Now switch to the components/Layout.js file. In there, we import the useStoreState and useStoreActions from easy-peasy.

import { useStoreState, useStoreActions } from 'easy-peasy'

useStoreState is new to us, and we’ll use it to access the store state properties.

Inside the Layout component function body, let’s initialize a few variables:

const showModal = useStoreState((state) => state.modals.showModal)
const showLoginModal = useStoreState((state) => state.modals.showLoginModal)
const showRegistrationModal = useStoreState(
  (state) => state.modals.showRegistrationModal
)

const setHideModal = useStoreActions((actions) => actions.modals.setHideModal)
const setShowRegistrationModal = useStoreActions(
  (actions) => actions.modals.setShowRegistrationModal
)
const setShowLoginModal = useStoreActions(
  (actions) => actions.modals.setShowLoginModal
)

The first 3 are properties, which we’ll use to determine if modals should be shown or not, just like we did before using the properties generated using the useState hook:

{
  showModal && (
    <Modal close={() => setHideModal()}>
      {showLoginModal && (
        <LoginModal
          showSignup={() => {
            setShowRegistrationModal()
          }}
        />
      )}
      {showRegistrationModal && (
        <RegistrationModal
          showLogin={() => {
            setShowLoginModal()
          }}
        />
      )}
    </Modal>
  )
}

See, I called setHideModal(). Before I had setShowModal(false), but I think setHideModal() is clearer. We could have passed a parameter as part of our easy-peasy action, too.

And instead of calling

setShowRegistrationModal(true)
setShowLoginModal(false)

I called

setShowRegistrationModal()

because we abstract away all the logic in the store. We don’t need to manage all the details, we just tell it to show the registration modal.

Same for the login modal.

The other part where we’ll show a modal, as we said, is the Reserve button in the pages/houses/[id].js component.

Users click the button when they finally chose the dates for the stay, and we can go on with the purchase flow.

So let’s now switch to the pages/houses/[id].js file.

In there, we first import useStoreActions:

import { useStoreActions } from 'easy-peasy'

pages/houses/[id].js

and in the component body we initialize the setShowLoginModal action:

const setShowLoginModal = useStoreActions(
  (actions) => actions.modals.setShowLoginModal
)

and finally we call it when the button is clicked:

<button
  className="reserve"
  onClick={() => {
    setShowLoginModal()
  }}
>
  Reserve
</button>

Awesome! Our modal should be correctly working now. We’re ready to start implementing the registration functionality now.

Oh, one thing to note: I changed the links I previously defined as <a href="javascript:;" ... to <a href="#" ... because I noticed that React complained about using javascript: URLs in the DevTools console, about them being deprecated (although they are a perfectly find JavaScript feature). A reminder to always check the DevTools if React tells us something is wrong.

See the code on GitHub

Next part: Send registration data to the server

Want to become a better Web Developer? Join the 2022 Web Development Bootcamp!

⭐️⭐️⭐️ Join the 2022 Web Development Bootcamp ⭐️⭐️⭐️