Привет, разработчики React и энтузиасты JavaScript!

Вы когда-нибудь хотели добавить раздел комментариев на свой веб-сайт или в приложение, чтобы повысить вовлеченность пользователей и поощрить обсуждения? Ну, не смотрите дальше! В этой статье мы проведем вас через пошаговое руководство о том, как реализовать полнофункциональный раздел комментариев с помощью платформы React Server. React Server позволяет создавать серверные приложения с модульной структурой, управляемой компонентами, точно так же, как React используется во внешнем интерфейсе. К концу этого руководства вы получите более глубокое представление о React Server и сможете создать свой собственный раздел комментариев, что сделает ваш веб-сайт или приложение еще более интерактивным и привлекательным. Итак, давайте погрузимся и начнем!

Посетите документацию и примеры на https://state-less.cloud

Предпосылки

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

  • Node.js (версия 16 или выше)
  • npm (версия 6 или выше)

Настройка нового проекта

Запустите сервер

git clone https://github.com/state-less/clean-starter.git -b react-server comment-app-backend
cd comment-app-backend
git remote remove origin
yarn install
yarn start

Запустите клиент

Создайте новый проект vite и выберите React в качестве фреймворка и TypeScript в качестве варианта.

yarn create vite comment-app-frontend

Теперь перейдите во вновь созданную папку, установите зависимости и добавьте @apollo/client и state-less/react-client в свой проект и запустите сервер.

cd todo-app-frontend
yarn
yarn add @mui/material @mui/icons-material
yarn add @emotion/react @emotion/styled
yarn add @apollo/client state-less/react-client
yarn dev

Создание экземпляра клиента GraphQl

Чтобы подключиться к нашему бэкенду, нам нужно создать клиент GraphQl. Создайте новый файл под comment-app-frontend/src/lib/client.ts и вставьте следующее содержимое.

import { ApolloClient, InMemoryCache, split, HttpLink } from "@apollo/client";
import { WebSocketLink } from "@apollo/client/link/ws";
import { getMainDefinition } from "@apollo/client/utilities";// Create an HTTP link
const localHttp = new HttpLink({
  uri: "http://localhost:4000/graphql",
});
// Create a WebSocket link
const localWs = new WebSocketLink({
  uri: `ws://localhost:4000/graphql`,
  options: {
    reconnect: true,
  },
});
// Use the split function to direct traffic between the two links
const local = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === "OperationDefinition" &&
      definition.operation === "subscription"
    );
  },
  localWs,
  localHttp
);
// Create the Apollo Client instance
export const localClient = new ApolloClient({
  link: local,
  cache: new InMemoryCache(),
});

export default localClient;

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

Примечание. Сейчас вам нужно создать этот файл вручную, но позже он будет создан с помощью инициализатора или react-client, который предоставит способ загрузить клиент graphql, предоставив URL-адрес, указывающий на сервер реагирования. Сейчас вам нужно вручную создать и предоставить клиент GraphQl.

Бэкэнд

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

Откройте файл по адресу comment-app-backend\src\lib\permissions.ts и укажите свой адрес электронной почты в качестве администратора. Мы будем использовать это позже, чтобы аутентифицировать вас и разрешить вам удалять другие комментарии.

export const admins = ['[email protected]'];

Фронтенд

Чтобы потреблять и взаимодействовать с компонентом комментариев, вам нужно использовать компонент, используя хук useComponent, предоставленный @state-less/react-client.

Создайте файл в папке todo-app-frontend\src\components\Comments.tsx и вставьте следующее содержимое.

import {
  Alert,
  Avatar,
  Button,
  Card,
  CardActions,
  CardContent,
  Chip,
  IconButton,
  TextField,
  Tooltip,
  Typography,
} from '@mui/material';
import { authContext, useComponent } from '@state-less/react-client';
import { useContext, useState } from 'react';
import GoogleIcon from '@mui/icons-material/Google';
import DeleteIcon from '@mui/icons-material/Delete';

export const Comments = ({ id = 'comments' }) => {
  const [component, { error, loading }] = useComponent(id, {});
  const [comment, setComment] = useState('');
  const comments = component?.props?.comments || [];

  const canComment = component?.props?.permissions.comment;
  const canDelete = component?.props?.permissions.delete;

  return (
    <Card>
      {loading && <Alert severity="info">Loading...</Alert>}
      {error && <Alert severity="error">{error.message}</Alert>}
      {!canComment && (
        <Alert severity="info">You need to be logged in to comment.</Alert>
      )}

      {comments.map((comment, index) => {
        return (
          <Comment
            comment={comment}
            del={() => component?.props?.del(index)}
            canDelete={canDelete}
          />
        );
      })}

      <CardContent>
        <TextField
          multiline
          rows={3}
          onChange={(e) => setComment(e.target.value)}
          fullWidth
          value={comment}
        />
      </CardContent>
      <CardActions>
        <Tooltip
          title={canComment ? '' : 'You need to be logged in to comment.'}
        >
          <span>
            <Button
              onClick={() => {
                component?.props?.comment(comment);
                setComment('');
              }}
              disabled={!canComment}
            >
              Add
            </Button>
          </span>
        </Tooltip>
      </CardActions>
    </Card>
  );
};

const StrategyIcons = {
  google: GoogleIcon,
};
const Comment = ({ comment, del, canDelete }) => {
  const { session } = useContext(authContext);
  const isOwnComment =
    comment.identity.email === session?.strategies?.[session.strategy]?.email ||
    (comment.identity.strategy === 'anonymous' &&
      comment.identity.id === JSON.parse(localStorage.id));
  const Icon = StrategyIcons[comment.strategy];
  return (
    <Card sx={{ m: 1 }}>
      <CardContent sx={{ display: 'flex' }}>
        <Typography variant="body1">{comment.message}</Typography>
      </CardContent>
      <CardActions>
        {(canDelete || isOwnComment) && (
          <IconButton onClick={del}>
            <DeleteIcon />
          </IconButton>
        )}
        <Chip
          avatar={
            comment.identity.picture && (
              <Avatar src={comment.identity.picture}>
                <Icon />
              </Avatar>
            )
          }
          label={comment.identity.name}
          sx={{ ml: 'auto' }}
        ></Chip>
      </CardActions>
    </Card>
  );
};

Замените содержимое todo-app-frontend\src\App.tsx

import { ApolloProvider } from '@apollo/client'
import './App.css'
import { Comments } from './components/Comments'
import localClient from './lib/client'

function App() {
  return (
    <div className="App">
      <ApolloProvider client={localClient}>
        <Comments />
      </ApolloProvider>
    </div>
  )
}

export default App

Теперь удалите весь код из comment-app-frontend/src/index.css.

Запустите интерфейс с yarn dev и посетите http://127.0.0.1:5173/. Вы должны увидеть пустой раздел комментариев, который в настоящее время не позволяет вам добавлять комментарии.

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

Реализация аутентификации

Процесс прост с реагирующим сервером.

Сначала установите react-google-login yarn add react-google-login и создайте новый файл в разделе comment-app-frontend/src/components/GoogleButton.tsx.

import { Avatar, Button } from '@mui/material';
import { authContext } from '@state-less/react-client';
import { useContext } from 'react';
import GoogleLogin from 'react-google-login';
import GoogleIcon from '@mui/icons-material/Google';

const logError = (response) => {
  console.log(response);
};

export const LoggedInGoogleButton = (props) => {
  const { session, authenticate } = useContext(authContext);

  if (session.strategy !== 'google') {
    return null;
  }

  const decoded = session.strategies.google.decoded;
  return (
    <Button color="secondary">
      <Avatar
        src={decoded.picture}
        sx={{ width: 24, height: 24, mr: 1 }}
      ></Avatar>
      {decoded.name}
    </Button>
  );
};

export const GoogleLoginButton = () => {
  const { session, authenticate } = useContext(authContext);

  return session?.strategy === 'google' ? (
    <LoggedInGoogleButton />
  ) : (
    <GoogleLogin
      clientId="534678949355-odq15l4236372p864f63ci14g794sfqf.apps.googleusercontent.com"
      buttonText="Login"
      onSuccess={({ accessToken, tokenId }) => {
        console.log('Authenticating with google');
        authenticate({
          strategy: 'google',
          data: { accessToken, idToken: tokenId },
        });
      }}
      render={(props) => {
        return (
          <Button color="secondary" {...props}>
            <GoogleIcon sx={{ mr: 1 }} />
            Login
          </Button>
        );
      }}
      onFailure={logError}
      cookiePolicy={'single_host_origin'}
    />
  );
};

Теперь обновите файл comment-app-frontent/src/App.tsx и добавьте GoogleLoginButton. Не забудьте обернуть ваше приложение в AuthProvider, что необходимо для работы аутентификации.

import { ApolloProvider } from '@apollo/client'
import './App.css'
import { Comments } from './components/Comments'
import localClient from './lib/client'
import { AppBar, Paper, Toolbar } from '@mui/material'
import { GoogleLoginButton } from './components/GoogleButton'
import { AuthProvider } from '@state-less/react-client';

function App() {
  return (
    <div className="App">
      <ApolloProvider client={localClient}>
        <AuthProvider   >
          <AppBar>
            <Toolbar>
                <GoogleLoginButton />
            </Toolbar>
          </AppBar>
          <Paper sx={{mt: 9}}>
            <Comments />
          </Paper>
          </AuthProvider >
      </ApolloProvider>
    </div>
  )
}

export default App

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