Давайте создадим: приложение для блога с Next.js, React Hooks и учебным курсом по серверной части Firebase

Я собираюсь начать новый проект. С каждым новым проектом всегда возникает вопрос, какой технический стек использовать. В моем новом проекте я знаю, что SEO будет иметь большое значение, поэтому мне нужно выяснить, как с этим бороться. Еще я хочу написать фронтенд на React. Еще мне нужен бэкенд. Что мне делать: использовать серверную часть Express.js, Rails, Flask или Django? Есть ли способ, как я могу полностью отказаться от написания бэкенда? На эти вопросы многие из нас пытаются ответить, прежде чем приступить к новому проекту.

Next.js

Если вы не слышали об этом раньше, Next.js - это фреймворк React, который позволяет вам писать приложение React, которое отображает статический исходный код. Многие из поисковых роботов становятся все лучше и лучше при чтении файлов JavaScript, но им легче придерживаться традиционного источника HTML. Если вы хотите хорошего SEO, вы должны максимально упростить жизнь сканерам.

Next.js также обрабатывает маршрутизацию, основанную на структуре папок, и многое другое. Вы можете прочитать больше на www.nextjs.org

Перехватчики React
, представленные в React 16.8, позволяют использовать состояние и другие функции React без написания класса. Это новый предпочтительный способ написания кода React. Это не означает, что вы должны продолжить и реорганизовать все существующие классы React.

В Facebook есть десятки тысяч компонентов, написанных как классы, и они не будут их переписывать. Однако весь их новый код основан на хуках, и ваш код React должен быть таким же.

Firebase
Firebase - это, по сути, законченная, готовая к использованию серверная часть, к которой вы подключаете свое приложение. Он содержит невероятное количество встроенных продуктов, от баз данных, аутентификации, хостинга и аналитики. В этом сообщении блога мы рассмотрим аутентификацию и базы данных. Вы можете узнать больше на www.firebase.com.

Оглавление

Исходный код этого приложения можно найти по адресу
https://github.com/Devalo/Next.js-blog-app-

Установка и настройка

Node и Next.js
Убедитесь, что у вас установлена ​​более новая версия Node.
Все, что нам нужно сделать, чтобы создать новое приложение Next.js, - это написать:

$ npx create-next-app

Это позаботится обо всем за нас.

Firebase
Перейдите на www.firebase.com и войдите в свою учетную запись Google. После входа в систему нажмите Добавить проект.

Мы даем проекту имя BlogApp и нажимаем «Продолжить».

Google Analytics пока нам не нужен, поэтому давайте отключим его и нажмите
«Создать проект». Когда проект будет готов, мы нажмем «продолжить».

Так что же это за проект? Здесь вы можете добавить в свое приложение все различные приложения Firebase. Будь то веб-приложение, приложение для iOS / Android или что угодно, вы можете настроить все в своем проекте Firebase. Наше приложение будет веб-приложением.

Нажимаем на кнопку, которую я обвел. Это создаст для нас веб-приложение в нашем проекте Firebase.

В этом проекте название не имеет значения. Назовем это просто блогом. После того, как вы дадите приложению имя, нажмите Зарегистрировать приложение. Просто оставьте окно открытымb после этого. Далее мы настроим наше приложение Next.js и подключим его к нашему приложению Firebase.

Создание нашего приложения Next.js

Как и в случае с предпочтительным способом создания приложения React, мы можем использовать почти ту же команду для создания нового приложения Next.js.

# npx create-next-app nextjs-blog
npx: installed 1 in 1.039s
? Pick a template › — Use arrow-keys. Return to submit.
❯ Default starter app
 Example from the Next.js repo

Выберите начальное приложение по умолчанию, и пусть npx сделает свое дело. После этого вы можете cd в свое приложение и запустить сервер с помощью yarn dev.
Если вы запустите новую вкладку браузера на localhost: 3000, вы увидите приветствие Next.js страница:

Это означает, что наше приложение Next.js запущено и работает.

Структура папки

Давайте откроем проект в нашем любимом текстовом редакторе и посмотрим на структуру папок. игнорируя скрытые папки и стандартные файлы узлов, у нас остается две папки:

pages/
- api/
  - hello.js
- index.js
public/
- favicon.ico
- vercel.svg

Общая папка - это место, куда вы помещаете статические файлы в файлы стилей, изображения и т. Д.

Папка pages является частью системы маршрутизации Next.js.
Если вы создадите новую папку под страницами /, скажем, страницами / example, будет создан маршрут к URL-адресу
localhost: 3000 / example. Довольно аккуратно. Next.js позаботится обо всем за кулисами.

Папка api / - это просто папка с примером того, как вы можете создать конечную точку api. Он нам не нужен, поэтому можете удалить его или проигнорировать.

Однако мы создадим две новые папки в корневом каталоге. - компоненты и config. Наша текущая структура папок выглядит так:

components/
config/
pages/
public/

index.js
Поскольку это демонстрационное приложение, мы будем делать большую часть нашей работы внутри этого файла. Давайте заменим все его содержание следующим:

// pages/index.js
import Head from 'next/head';
const Home = () => {
  return (
    <div>
      <Head>
        <title>Blog App</title>
      </Head>
      <h1>Blog</h1>
    </div>
  )
}
export default Home;

В Next.js есть горячая перезагрузка. После сохранения файла Next.js автоматически перезагрузит ваше приложение. Компонент Head - это встроенный компонент Next.js, который обрабатывает все, что вы обычно пишете в обычном теге html head. Если вы перейдете на localhost: 3000, у вас останется:

Подключите наше приложение к Firebase

Если мы вернемся на сайт Firebase, у нас будет перечислено несколько конфигураций. Мы создадим новый файл в нашем каталоге config /. Назовем его fire-conf.js. Вставьте конфигурацию в файл.

Затем давайте установим библиотеку firebase.

$ npm install firebase 

и дорабатываем наш конфигурационный файл:

// config/fire-config.js
import firebase from 'firebase';
const firebaseConfig = {
  apiKey: "YOUR API KEY",
  authDomain: "YOUR AUTH DOMAIN",
  databaseURL: "YOUR DATABASE URL",
  projectId: "YOUR PROJECT ID ",
  storageBucket: "YOUR STORAGE BUCKET",
  messagingSenderId: "YOUR SENDER ID ",
  appId: "YOUR APP ID"
};
try {
  firebase.initializeApp(firebaseConfig);
} catch(err){
  if (!/already exists/.test(err.message)) {
    console.error('Firebase initialization error', err.stack)}
}
const fire = firebase;
export default fire;

Мы не должны жестко кодировать подобные вещи в производственном приложении. Мы обязательно должны использовать переменные среды. Однако для этой демонстрационной цели этого достаточно.

Мы импортируем firebase и инициализируем ее нашим config. Вот и все. Мы подключены!

Написать в Cloud Firestore

Не было бы приложения, если бы мы не могли добавлять записи в блог. Давайте сначала рассмотрим это. Вернувшись на веб-сайт firebase, вы можете выбрать из множества продуктов. Мы выберем Cloud Firestore в качестве нашей базы данных. Это база данных noSQL, и она будет работать прямо из коробки.

Настроить Cloud Firestore

Нажмите Cloud Firestore.

И нажмите Создать базу данных.

Щелкните Начать в тестовом режиме. Это не то, что вы сделали бы в производственной среде, но это сработает для того, чтобы все заработало. Нажав «Далее», вы выбираете ближайший к вам сервер (или ближайший к тому месту, где может находиться ваша пользовательская база) и нажимаете Готово.

Наша база данных настроена. Пора писать ему.
Внутри нашей папки components / мы создадим файл с именем CreatePost.js и запрограммируем наш компонент формы для написания новых сообщений.

Мы собираемся использовать хуки для захвата полей ввода формы. Настройка хука выглядит так:

const [hook, setHook] = useState()

У нас есть две переменные, где в данном случае hook - это наше фактическое значение, setHook - это функция для изменения нашего значения, а значение по умолчанию будет сохранено внутри useState () квадратные скобки. Давайте попробуем это, написав небольшую форму и распечатав данные формы на нашей консоли:

// components/CreatePost.js
import React, { useState } from 'react';
const CreatePost = () => {
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');
  const handleSubmit = (event) => {
    event.preventDefault();
    console.log({
      "title": title,
      "content": content
    });
    setTitle('');
    setContent('');
  }
  return (
    <div>
      <h2>Add Blog</h2>
      <form onSubmit={handleSubmit}>
        <div>
          Title<br />
          <input type="text" value={title} 
           onChange={({target}) => setTitle(target.value)} />
        </div>
        <div>
          Content<br />
          <textarea value={content} 
           onChange={({target}) => setContent(target.value)} />
        </div>
        <button type="submit">Save</button>
      </form>
    </div>
  )
}
export default CreatePost;

Мы импортируем React, useState и создаем стандартную (бесклассовую) функцию.

Поскольку форма состоит из двух полей, мы будем использовать здесь два крючка. React будет отслеживать изменения в полях формы и сохранять их в своих переменных title и content с помощью useState (). Мы фиксируем значение и обновляем переменные, используя их соответствующую функцию-перехватчик.

Возьмем для примера эту строку. setTitle сохранит входное значение в соответствующей переменной title, которую мы объявили ранее в нашем коде:

const [title, setTitle] = useState('');

Мы можем использовать функцию setTitle, чтобы изменить значение title. Это именно то, что мы делаем в форме:

onChange={({target}) => setTitle(target.value)}

Давайте посмотрим на функцию handleSubmit ():

const handleSubmit = (event) => {
  event.preventDefault();
  console.log({
      "title": title,
      "content": content
    });
    setTitle('');
    setContent('');
  }

Когда мы отправляем нашу форму, мы записываем оба значения формы в их соответствующие хуки. Мы можем использовать переменные ловушки, чтобы распечатать содержимое формы. Мы также сбрасываем title и content обратно на пустую строку. Это гарантирует, что наша форма будет очищена при отправке.

Вернувшись в наш файл index.js, мы импортируем и разместим компонент внутри оператора return:

import CreatePost from '../components/CreatePost';
const Home = () => {
  return (
    <div>
      <Head>
        <title>Blog App</title>
      </Head>
      <h1>Blog</h1>
      <CreatePost /> 
    </div>
  )
}

Давайте попробуем форму. Если мы посмотрим в консоли браузера:

Теперь, как мы можем записать это в Cloud Firestore? Это очень легко. Мы включим наш модуль firebase из нашего файла конфигурации и воспользуемся встроенной функцией для записи в базу данных.

import fire from '../config/fire-config';

fire.firestore()
  .collection('blog')
  .add({
    title: title,
    content: content,
  });

Мы используем встроенные функции для добавления новой коллекции. Мы можем назвать коллекцию как угодно. Если его не существует, база данных создаст его для нас.
Полный файл CreatePost.js в настоящее время выглядит следующим образом:

// components/CreatePost.js
import React, { useState } from 'react';
import fire from '../config/fire-config';
const CreatePost = () => {
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');
  const [notification, setNotification] = useState('');
  const handleSubmit = (event) => {
    event.preventDefault();
    fire.firestore()
      .collection('blog')
      .add({
        title: title,
        content: content,
      });
    setTitle('');
    setContent('');
    setNotification('Blogpost created');
    setTimeout(() => {
      setNotification('')
    }, 2000)
  }
  return (
    <div>
      <h2>Add Blog</h2>
      {notification}
      <form onSubmit={handleSubmit}>
        <div>
          Title<br />
          <input type="text" value={title} 
           onChange={({target}) => setTitle(target.value)} />
        </div>
        <div>
          Content<br />
          <textarea value={content} 
           onChange={({target}) => setContent(target.value)} />
        </div>
        <button type="submit">Save</button>
      </form>
    </div>
  )
}
export default CreatePost;

Обратите внимание, что я добавил еще один крючок - уведомление. Это просто намекнет, когда сообщение в блоге будет отправлено.

Если мы вернемся на сайт Firebase внутри нашей базы данных, мы увидим нашу первую запись:

Чтение из Cloud Firestore

Таким образом, мы можем писать в базу данных. Мы также должны уметь читать оттуда. Это почти так же просто, как запись в базу данных. Мы собираемся решить эту проблему с помощью перехватчиков React.

внутри нашего файла index.js мы создадим новый хук:

const [blogs, setBlogs] = useState([]);

Мы устанавливаем ловушку на пустой массив. Которые мы скоро заполним записями в нашем блоге.

Нам также нужно использовать еще один хук под названием useEffect. Из документации:

Если вы знакомы с методами жизненного цикла класса React, вы можете думать о useEffect Hook как о componentDidMount, componentDidUpdate и componentWillUnmount вместе взятых.

Обычно useEffect обновляется при каждой визуализации. Если мы изменим весь наш файл index.js на это:

// pages/index.js
import { useState, useEffect } from 'react';
import Head from 'next/head';
import fire from '../config/fire-config';
import CreatePost from '../components/CreatePost';
const Home = () => {
  const [blogs, setBlogs] = useState([]);
useEffect(() => {
    fire.firestore()
      .collection('blog')
      .onSnapshot(snap => {
        const blogs = snap.docs.map(doc => ({
          id: doc.id,
          ...doc.data()
        }));
        setBlogs(blogs);
      });
  }, []);
console.log(blogs)
  return (
    //
  )
}
export default Home;

Завершающий массив в useEffect означает, что содержимое ловушки будет запускаться один раз при каждой визуализации.

Если мы посмотрим на консоль нашего браузера, блоги собираются из базы данных и сохраняются в переменной blog, которую мы устанавливаем с помощью setBlogs () внутри useEffect.

Если мы добавим еще один блог:

Новые блоги выводятся на консоль без необходимости обновлять страницу или делать что-либо еще.

Визуализируйте наши блоги в нашем приложении

Поскольку мы уже сохранили все наши блоги в переменной blog, отобразить их на нашей странице очень просто. Мы просто отображаем блоги и показываем их в неупорядоченном списке:

<ul>
  {blogs.map(blog =>
    <li key={blog.id}>
      {blog.title}
    </li>
  )}
</ul>

В настоящее время весь файл index.js выглядит следующим образом:

// pages/index.js
import { useState, useEffect } from 'react';
import Head from 'next/head';
import fire from '../config/fire-config';
import CreatePost from '../components/CreatePost';
const Home = () => {
  const [blogs, setBlogs] = useState([]);
  useEffect(() => {
    fire.firestore()
      .collection('blog')
      .onSnapshot(snap => {
        const blogs = snap.docs.map(doc => ({
          id: doc.id,
          ...doc.data()
        }));
        setBlogs(blogs);
      });
  }, []);
  return (
    <div>
      <Head>
        <title>Blog App</title>
      </Head>
      <h1>Blog</h1>
      <ul>
        {blogs.map(blog =>
          <li key={blog.id}>
            {blog.title}
          </li>
        )}
      </ul>
      <CreatePost />
    </div>
  )
}
export default Home;

Создавайте маршруты для блогов, читайте записи в блогах

Мы можем перечислить все записи нашего блога. Мы также можем добавлять новые записи в блог. На данный момент мы просматриваем только заголовок блога. Мы хотим иметь возможность щелкнуть заголовок и перейти на другую страницу, где мы сможем прочитать весь блог. Это хороший способ использовать встроенную систему маршрутизации Next.js.

Прокладка маршрутов

внутри нашей папки pages / мы создадим новую папку, которую мы назовем blog /. Внутри папки blog / мы создаем новый файл, который назовем [id] .js. Страницы, которые начинаются с [ и заканчиваются ], являются динамическими страницами в Next.js.

Внутри нашего файла [id] .js:

const Blog = () => {
  return (
    <div>
      Some blog
    </div>
  )
}
export default Blog

Если вы посетите http: // localhost: 3000 / blog / entry

Фактически, все, что вы напишете после blog /, будет отображать тот же контент. К тому же… Нам не нужно было кодировать маршруты! Next.js все сделал за нас автоматически.

Прежде чем мы начнем кодировать файл [id] .js, мы обсудим, как создать динамический URL-адрес из страницы индекса для каждой записи в блоге.

Next.js имеет для этого встроенный компонент. Называется Ссылка. Импортируем Ссылку:

import Link from 'next/link';

И оберните заголовки наших блогов в компонент Link:

// index.js
{blogs.map(blog =>
  <li key={blog.id}>
    <Link href="/blog/[id]" as={'/blog/' + blog.id}>
      <a>{blog.title}</a>
    </Link>
  </li>
)}

Мы указываем href на сам файл. Мы можем использовать атрибут as для создания собственного маршрута. Для простоты использования мы просто будем использовать идентификатор блога.

Как видите, заголовки записей в наших блогах теперь являются ссылками. Они указывают на blog / postid, который, например, может выглядеть так: localhost: 3000 / blog / d5pNUEPyeyCGLuFlLiSf

Теперь, если мы щелкнем по любой из ссылок, мы получим тот же жестко закодированный контент, который мы видели раньше.

Единичные записи в блоге

К сожалению, на мой взгляд, компоненты Link не поддерживают передачу дополнительных свойств. Нам нужно найти способ получить только ту запись в блоге, которую мы хотим.

У нас есть идентификатор блога как параметр в запросе.

В Next.js есть несколько способов выполнения различных предварительных рендеров. Я предлагаю вам прочитать о различных рендерах в документации Next.js.

Давайте попробуем использовать getInitialProps.

// blog/[id].js
Blog.getInitialProps = ({ query }) => {
  return {
      id: query.id,
  }
}

Мы можем получить id из запроса. Это сделает его доступным как prop в нашем основном компоненте. Если мы попытаемся вывести prop на консоль:

Мы можем использовать этот идентификатор для получения записи нашего блога из базы данных.

Мы можем сделать почти то же самое, что и при получении всех сообщений в блогах.

// blog/[id].js
const [blog, setBlog] = useState(null);
useEffect(() => {
    fire.firestore()
      .collection('blog')
      .doc(props.id)
      .get()
      .then(blog => {
        setBlog(blog.data())
      })
  }, [])
  console.log(blog)

Обратите внимание, что мы получаем только одно сообщение. Затем мы используем хук useState, чтобы сохранить его состояние в переменной blog. Это позволяет нам отображать весь контент блога на нашей странице. Это весь файл blog / [id] .js на данный момент:

// blog/[id].js
import { useEffect, useState } from 'react';
import fire from '../../config/fire-config';
import Link from 'next/link'
const Blog = (props) => {
  const [blog, setBlog] = useState(null);
  useEffect(() => {
    fire.firestore()
      .collection('blog')
      .doc(props.id)
      .get()
      .then(result => {
        setBlog(result.data())
      })
  }, []);
  if(!blog){
    return(
      <h2>Loading...</h2>
    )
  }
  return (
    <div>
      <h2>{blog.title}</h2>
      <p>
        {blog.content}
      </p>
      <Link href="/">
        <a>Back</a>
      </Link>
    </div>
  )
}
Blog.getInitialProps = ({ query }) => {
  return {
      id: query.id,
  }
}
export default Blog

Мы также добавили условный оператор:

if(!blog){
    return(
      <h2>Loading...</h2>
    )
  }

Мы получим данные с сервера с небольшой задержкой. Это может быть хорошее место, чтобы поставить счетчик, загрузчик или что-то в этом роде, чтобы улучшить UX. Ради этого урока мы оставим все как есть.

Ошибка: не отображается серверная часть

Однако есть одна проблема с этим решением. Он работает хорошо, но если вы посмотрите исходный код браузера, контента там нет. Это связано с тем, что содержимое записи в блоге было отображено на клиенте, а не на сервере.

Если мы хотим отобразить контент на сервере, лучше использовать getServerSideProps вместо getInitialProps.

Мы заменим нашу функцию на getServerSideProps и переместим в нее код выборки firebase:

export const getServerSideProps = async ({ query }) => {
  const blogObj = {};
  await fire.firestore()
    .collection('blog')
    .doc(query.id)
    .get()
    .then(result => {
      content['title'] = result.data().title
      content['content'] = result.data().content
    });
  return {
    props: {
      title: blogObj.title,
      content: blogObj.content,
    }
  }
}

Из того же параметра запроса мы можем получить идентификатор сообщения в блоге. Мы используем его в этой функции вместо того, чтобы передавать его в основную функцию. Мы инициализируем пустой объект / хеш-таблицу, которую мы называем blogObj. Мы извлекаем сообщение в блоге из базы данных и сохраняем контент внутри этого объекта. Затем мы можем вернуть объект в основную функцию в качестве реквизита.

Полный файл [id] .js выглядит так:

// pages/blog/[id].js
import fire from '../../config/fire-config';
import Link from 'next/link'
const Blog = (props) => {
  return (
    <div>
      <h2>{props.title}</h2>
      <p>
        {props.content}
      </p>
      <Link href="/">
        <a>Back</a>
      </Link>
    </div>
  )
}
export const getServerSideProps = async ({ query }) => {
  const content = {}
  await fire.firestore()
    .collection('blog')
    .doc(query.id)
    .get()
    .then(result => {
      content['title'] = result.data().title;
      content['content'] = result.data().content;
    });
return {
    props: {
      title: content.title,
      content: content.content,
    }
  }
}
export default Blog

Содержание блога печатается как обычно, но если вы посмотрите исходный код браузера, вы также увидите его содержание.

Это все для создания сообщений в блогах. Блог закончен. Затем давайте посмотрим, сможем ли мы добавить аутентификацию.

Добавление пользователей и аутентификация с помощью Firebase

Firebase позволяет нам настраивать различные типы методов аутентификации. В этом приложении мы будем использовать традиционный тип аутентификации по имени пользователя и паролю.

Нам понадобится способ создания новых пользователей и способ аутентификации пользователей. Может быть, только зарегистрированные пользователи должны иметь возможность публиковать сообщения в блоге?

Настройка аутентификации Firebase

Первым делом давайте настроим наш проект Firebase для проверки подлинности.

На веб-сайте Firebase в левом меню мы находим Authentication.
Давайте выберем Настроить способ входа:

Выбираем Электронная почта / пароль:

Мы выбираем Включить на верхнем ползунке и нажимаем Сохранить.

Создание пользователей

Мы готовы создавать пользователей. Мы можем создавать пользователей прямо на сайте Firebase, но что в этом интересного?

Давайте создадим новую папку в pages /. Мы будем называть это пользователями. Внутри папки мы создадим файл с именем register.js. Это даст нам маршрут localhost: 3000 / users / register. Пока мы это делаем, мы создадим еще один файл в том же каталоге. Назовем его login.js. Мы уже писали большую часть этого кода раньше, поэтому я просто вставлю его сюда:

// pages/users/register.js
import { useState } from 'react'; 
import fire from '../../config/fire-config';
import { useRouter } from 'next/router';
const Register = () => {
  const router = useRouter();
  const [userName, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [passConf, setPassConf] = useState('');
  const [notification, setNotification] = useState('');
  const handleLogin = (e) => {
    e.preventDefault();
    if (password !== passConf) {
      setNotification(
       'Password and password confirmation does not   match'
      )
      setTimeout(() => {
        setNotification('')
      }, 2000)
      setPassword('');
      setPassConf('');
      return null;
      }
    fire.auth()
      .createUserWithEmailAndPassword(userName, password)
      .catch((err) => {
        console.log(err.code, err.message)
      });
    router.push("/")
  }
  return (
    <div>
      <h1>Create new user</h1>
        {notification}
      <form onSubmit={handleLogin}>
        Email: <input type="text" value={userName} 
        onChange={({target}) => setUsername(target.value)} /> 
        <br />
        Password: <input type="password" value={password} 
        onChange={({target}) => setPassword(target.value)} /> 
        <br />
        Password conf: <input type="password" value={passConf}    
        onChange={({target}) => setPassConf(target.value)} /> 
        <br />
        <button type="submit">Login</button>
      </form>
    </div>
  )
}
export default Register

Вы уже видели большую часть этого раньше. Мы записываем поля формы с помощью хука useState. Если пароль и подтверждение не совпадают, мы отправляем уведомление. Если они совпадают, мы сохраняем пользователя с помощью функции auth в firebase:

fire.auth()
      .createUserWithEmailAndPassword(userName, password)
      .catch((err) => {
        console.log(err.code, err.message)
      });

Firebase позаботится о хешировании пароля и проверке уникальности пользователя.

Мы используем встроенный в маршрутизатор перехватчик Next.js, чтобы перенаправить нас на главную страницу при создании нового пользователя.

import { useRouter } from 'next/router';
//
const router = useRouter();
//
router.push("/")

Если мы пытаемся создать нового пользователя, мы перенаправляемся на главную страницу. Давайте проверим Firebase:

Пользователь хранится в базе данных. Какой невероятно простой способ создать пользователя!

Вход пользователя

Код для входа пользователя находится в другом файле, который мы только что создали, pages / users / login.js:

// pages/users/login.js
import { useState } from 'react';
import fire from '../../config/fire-config';
import { useRouter } from 'next/router'
const Login = () => {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [notify, setNotification] = useState('');
  const router = useRouter();
  const handleLogin = (e) => {
    e.preventDefault();
    fire.auth()
      .signInWithEmailAndPassword(username, password)
      .catch((err) => {
        console.log(err.code, err.message)
        setNotification(err.message)
        setTimeout(() => {
          setNotification('')
        }, 2000)
      })
    setUsername('')
    setPassword('')
    router.push("/")
  }
  return (
    <div>
      <h1>Login</h1>
      {notify}
      <form onSubmit={handleLogin}>
        Email<input type="text" value={username} 
        onChange= {({target}) => setUsername(target.value)} />
        <br />
        Password<input type="password" value={password} 
        onChange={({target}) => setPassword(target.value)} />
        <br />
        <button type="submit">Login</button>
      </form>
    </div>
  )
}
export default Login

Этот код почти идентичен файлу, в котором мы создаем пользователей. Если мы введем неправильное имя пользователя или пароль, отобразится ошибка. Если мы вошли в систему, мы перенаправляемся на главную страницу нашего приложения. Как мы узнаем, что мы действительно вошли в систему?

Есть два разных способа проверить, вошел ли пользователь в систему. Мы собираемся использовать оба в демонстрационных целях. Вы можете прочитать о них обоих на странице https://firebase.google.com/docs/auth/web/manage-users.

Если мы поместим это в наш компонент index.js Home:

const user = fire.auth().currentUser;
  if (!user) {
    return(
      <div>Loading..</div>
    )
  }
  console.log(user.email)

Мы можем видеть электронную почту вошедшего в систему пользователя в консоли.

С его помощью мы можем создать небольшое меню, которое будет видно только при выходе из системы:

//pages/index.js
<h1>Blog</h1>
{!user 
?
  <div>
    <Link href="/users/register">
      <a>Register</a>
    </Link> | 
    <Link href="/users/login">
      <a> Login</a>
    </Link>
  </div>
:
  <button>Logout</button>
}
//

Поскольку мы уже вошли в систему, отображается кнопка выхода:

Выйти очень просто. Мы собираемся немного изменить код. Мы собираемся создать ловушку, которая сохраняет состояние входа в систему или нет. Мы также собираемся условно отобразить меню входа / регистрации и форму добавления блога. Это полный код для страниц / index.js:

// pages/index.js 
import { useState, useEffect } from 'react';
import Head from 'next/head';
import fire from '../config/fire-config';
import CreatePost from '../components/CreatePost';
import Link from 'next/link';
const Home = () => {
  const [blogs, setBlogs] = useState([]);
  const [notification, setNotification] = useState('');
  const [loggedIn, setLoggedIn] = useState(false);
  fire.auth()
    .onAuthStateChanged((user) => {
      if (user) {
        setLoggedIn(true)
      } else {
        setLoggedIn(false)
      }
    })
  useEffect(() => {
    fire.firestore()
      .collection('blog')
      .onSnapshot(snap => {
        const blogs = snap.docs.map(doc => ({
          id: doc.id,
          ...doc.data()
        }));
        setBlogs(blogs);
      });
  }, []);
  const handleLogout = () => {
    fire.auth()
      .signOut()
      .then(() => {
        setNotification('Logged out')
        setTimeout(() => {
          setNotification('')
        }, 2000)
      });
  }
  return (
    <div>
      <Head>
        <title>Blog App</title>
      </Head>
      <h1>Blog</h1>
      {notification}
      {!loggedIn 
      ?
        <div>
          <Link href="/users/register">
            <a>Register</a>
          </Link> | 
          <Link href="/users/login">
            <a> Login</a>
          </Link>
        </div>
      :
        <button onClick={handleLogout}>Logout</button>
      }
    <ul>
        {blogs.map(blog =>
          <li key={blog.id}>
            <Link href="/blog/[id]" as={'/blog/' + blog.id }>
              <a itemProp="hello">{blog.title}</a>
            </Link>
          </li>
        )}
      </ul>
      {loggedIn && <CreatePost />}
    </div>
  )
}
export default Home;

При входе в систему:

При выходе из системы:

Вот и все. У нас есть простое приложение для блога с аутентификацией, созданное с помощью Next.js, React Hooks и Firebase.

Заключительные мысли

Работа с Next.js дает мне немного ощущения Ruby on Rails, с тех пор как я впервые попробовал Ruby on Rails. Происходит немного магии. Он очень быстро развивается. Есть некоторые вещи, которые мне больше нравятся в чистом React, например, как вы передаете реквизиты от компонентов к компонентам. Однако Next.js довольно солидный!

Также было очень легко настроить полную серверную часть с Firebase. Мы вообще не писали никакого внутреннего кода. Мы просто соединили в готовое, очень гибкое решение.

В своем следующем проекте я, вероятно, буду использовать Next.js. Если я буду использовать Firebase? По крайней мере, сначала. Поразительно, как быстро с этим можно что-то наладить. Мне еще предстоит опробовать его в производстве, поэтому я не могу ответить на этот вопрос. Думаю, есть только один способ узнать? Создать приложение, получить массу пользователей и воспользоваться им? Если бы это было так просто ...

Вот и все, ребята. Если вы дочитали до конца, поздравляю. Я очень надеюсь, что вам понравится Next.js. Я уверен.

Исходный код этого приложения можно найти по адресу
https://github.com/Devalo/Next.js-blog-app-

До следующего раза
Стефан Баккелунд Валуа