Desenvolvendo um React Chat usando Socket.IO





Bom dia amigos!



Gostaria de compartilhar com vocês minha experiência no desenvolvimento de um chat simples no React usando a biblioteca Socket.IO .



Presume-se que você esteja familiarizado com a biblioteca nomeada. Se você não estiver familiarizado, aqui está um guia relacionado com exemplos de criação de um tudushka e bate-papo em JavaScript vanilla .



Ele também assume que você tem pelo menos superficialmente familiarizado com Node.js .



Neste artigo, vou me concentrar nos aspectos práticos do uso de Socket.IO, React e Node.js.



Nosso chat terá os seguintes recursos principais:



  • Seleção de sala
  • Enviando mensagens
  • Excluir mensagens do remetente
  • Armazenamento de mensagens em um banco de dados local no formato JSON
  • Armazenar o nome de usuário e ID no armazenamento local do navegador
  • Exibindo o número de usuários ativos
  • Exibindo uma lista de usuários com um indicador online


Também implementaremos a capacidade de enviar emoji .



Se você estiver interessado, por favor me siga.



Para quem está interessado apenas no código: aqui está o link para o repositório .



Caixa de areia:





Estrutura e dependências do projeto



Vamos começar a criar um projeto:



mkdir react-chat
cd react-chat

      
      





Crie um cliente usando Create React App :



yarn create react-app client
# 
npm init react-app client
# 
npx create-react-app client

      
      





No futuro, usarei yarn : para instalar dependências yarn add = npm i, yarn start = npm start, yarn dev = npm run dev



.



Vá para o diretório "cliente" e instale dependências adicionais:



cd client
yarn add socket.io-client react-router-dom styled-components bootstrap react-bootstrap react-icons emoji-mart react-timeago

      
      







A seção "dependências" do arquivo "package.json":



{
  "bootstrap": "^4.6.0",
  "emoji-mart": "^3.0.0",
  "react": "^17.0.1",
  "react-bootstrap": "^1.5.0",
  "react-dom": "^17.0.1",
  "react-icons": "^4.2.0",
  "react-router-dom": "^5.2.0",
  "react-scripts": "4.0.1",
  "react-timeago": "^5.2.0",
  "socket.io-client": "^3.1.0",
  "styled-components": "^5.2.1"
}

      
      





Volte para o diretório raiz (react-chat), crie o diretório "server", vá até ele, inicialize o projeto e instale as dependências:



cd ..
mkdir server
cd server
yarn init -yp
yarn add socket.io lowdb supervisor

      
      





  • socket.io - back-end Socket.IO
  • lowdb - banco de dados local em formato JSON
  • supervisor - servidor de desenvolvimento (alternativa ao nodemon , que não funciona corretamente com a última versão estável do Node.js; tem algo a ver com início / interrupção incorreta de processos filho)


Adicione o comando "start" para iniciar o servidor de produção e o comando "dev" para iniciar o servidor de desenvolvimento. package.json:



{
  "name": "server",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "private": true,
  "dependencies": {
    "lowdb": "^1.0.0",
    "socket.io": "^3.1.0",
    "supervisor": "^0.12.0"
  },
  "scripts": {
    "start": "node index.js",
    "dev": "supervisor index.js"
  }
}

      
      





Volte para o diretório raiz (react-chat), inicialize o projeto e instale as dependências:



  cd ..
  yarn init -yp
  yarn add nanoid concurrently

      
      





  • identificadores geradores de nanoides (serão usados ​​tanto no cliente quanto no servidor)
  • concorrentemente - execução simultânea de dois ou mais comandos


react-chat / package.json (observe que os comandos para npm são diferentes; consulte os documentos simultâneos):



{
  "name": "react-chat",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "private": true,
  "dependencies": {
    "concurrently": "^6.0.0",
    "nanoid": "^3.1.20"
  },
  "scripts": {
    "server": "yarn --cwd server dev",
    "client": "yarn --cwd client start",
    "start": "concurrently \"yarn server\" \"yarn client\""
  }
}

      
      





Ótimo, terminamos com a formação da estrutura principal do projeto e instalação das dependências necessárias. Vamos começar a implementar o servidor.



Implementação de servidor



A estrutura do diretório "server":



|--server
  |--db -    
  |--handlers
    |--messageHandlers.js
    |--userHandlers.js
  |--index.js
  ...

      
      





No arquivo "index.js", fazemos o seguinte:



  • Construindo um servidor HTTP
  • Nós conectamos Socket.IO a ele
  • Iniciamos o servidor na porta 5000
  • Registrando manipuladores de eventos ao conectar um soquete


index.js:



//  HTTP-
const server = require('http').createServer()
//    Socket.IO
const io = require('socket.io')(server, {
  cors: {
    origin: '*'
  }
})

const log = console.log

//   
const registerMessageHandlers = require('./handlers/messageHandlers')
const registerUserHandlers = require('./handlers/userHandlers')

//        (,   =  )
const onConnection = (socket) => {
  //     
  log('User connected')

  //       ""
  const { roomId } = socket.handshake.query
  //       
  socket.roomId = roomId

  //    (  )
  socket.join(roomId)

  //  
  //     
  registerMessageHandlers(io, socket)
  registerUserHandlers(io, socket)

  //   -
  socket.on('disconnect', () => {
    //  
    log('User disconnected')
    //  
    socket.leave(roomId)
  })
}

//  
io.on('connection', onConnection)

//  
const PORT = process.env.PORT || 5000
server.listen(PORT, () => {
  console.log(`Server ready. Port: ${PORT}`)
})

      
      





No arquivo "handlers / messageHandlers.js" fazemos o seguinte:



  • Configurando um banco de dados local no formato JSON usando lowdb
  • Nós gravamos os dados iniciais no banco de dados
  • Criação de funções para receber, adicionar e excluir mensagens
  • Registramos o processamento dos eventos correspondentes:

    • mensagem: obter - receber mensagens
    • mensagem: adicionar - adicionar uma mensagem
    • mensagem: remover - excluir uma mensagem




As mensagens são objetos com as seguintes propriedades:



  • messageId (string) - identificador de mensagem
  • userId (string) - ID do usuário
  • senderName (string) - nome do remetente
  • messageText (string) - texto da mensagem
  • createdAt (data) - data de criação


handlers / messageHandlers.js:



const { nanoid } = require('nanoid')
//  
const low = require('lowdb')
const FileSync = require('lowdb/adapters/FileSync')
//     "db"   "messages.json"
const adapter = new FileSync('db/messages.json')
const db = low(adapter)

//     
db.defaults({
  messages: [
    {
      messageId: '1',
      userId: '1',
      senderName: 'Bob',
      messageText: 'What are you doing here?',
      createdAt: '2021-01-14'
    },
    {
      messageId: '2',
      userId: '2',
      senderName: 'Alice',
      messageText: 'Go back to work!',
      createdAt: '2021-02-15'
    }
  ]
}).write()

module.exports = (io, socket) => {
  //     
  const getMessages = () => {
    //    
    const messages = db.get('messages').value()
    //   ,   
    //  - , , 
    io.in(socket.roomId).emit('messages', messages)
  }

  //   
  //    
  const addMessage = (message) => {
    db.get('messages')
      .push({
        //     nanoid, 8 -  id
        messageId: nanoid(8),
        createdAt: new Date(),
        ...message
      })
      .write()

    //     
    getMessages()
  }

  //   
  //   id 
  const removeMessage = (messageId) => {
    db.get('messages').remove({ messageId }).write()

    getMessages()
  }

  //  
  socket.on('message:get', getMessages)
  socket.on('message:add', addMessage)
  socket.on('message:remove', removeMessage)
}

      
      





No arquivo "handlers / userHandlers.js" fazemos o seguinte:



  • Crie uma estrutura normalizada com usuários
  • Criamos funções para obter, adicionar e remover usuários
  • Registramos o processamento dos eventos correspondentes:

    • usuário: obter - obter usuários
    • usuário: adicionar - adicionar um usuário
    • usuário: deixar - excluir um usuário




Também poderíamos usar lowdb para trabalhar com a lista de usuários. Você pode fazer isso se quiser. Eu, com sua permissão, vou me limitar ao objeto.



A estrutura normalizada (objeto) dos usuários possui o seguinte formato:



{
  id (string) - : {
    username (string) -  ,
    online (boolean) -     
  }
}

      
      





Na verdade, não estamos excluindo usuários, mas transferindo seu status para offline (atribuindo a propriedade "online" para "false").



handlers / userHandlers.js:



//  
//  
const users = {
  1: { username: 'Alice', online: false },
  2: { username: 'Bob', online: false }
}

module.exports = (io, socket) => {
  //     
  //  "roomId"  ,
  //       ,
  //      
  const getUsers = () => {
    io.in(socket.roomId).emit('users', users)
  }

  //   
  //         id
  const addUser = ({ username, userId }) => {
    // ,     
    if (!users[userId]) {
      //   ,    
      users[userId] = { username, online: true }
    } else {
      //  ,     
      users[userId].online = true
    }
    //     
    getUsers()
  }

  //   
  const removeUser = (userId) => {
    //        ,
    //     (O(1))    
    //      () 
    //  redux, ,  immer,     
    users[userId].online = false
    getUsers()
  }

  //  
  socket.on('user:get', getUsers)
  socket.on('user:add', addUser)
  socket.on('user:leave', removeUser)
}

      
      





Iniciamos o servidor para verificar seu desempenho:



yarn dev

      
      





Se virmos a mensagem “Servidor pronto. Porta: 5000 ", e um arquivo" messages.json "com os dados iniciais apareceu no diretório" db ", o que significa que o servidor está funcionando conforme o esperado, e você pode prosseguir com a implementação da parte cliente.



Implementação do cliente



Com o cliente tudo fica um pouco mais complicado. A estrutura do diretório "cliente":



|--client
  |--public
    |--index.html
  |--src
    |--components
      |--ChatRoom
        |--MessageForm
          |--MessageForm.js
          |--package.json
        |--MessageList
          |--MessageList.js
          |--MessageListItem.js
          |--package.json
        |--UserList
          |--UserList.js
          |--package.json
        |--ChatRoom.js
        |--package.json
      |--Home
        |--Home.js
        |--package.json
      |--index.js
    |--hooks
      |--useBeforeUnload.js
      |--useChat.js
      |--useLocalStorage.js
    App.js
    index.js
  |--jsconfig.json (  src)
  ...

      
      





Como o nome indica, o diretório "componentes" contém os componentes do aplicativo (partes da interface do usuário, módulos), e o diretório "ganchos" contém ganchos do usuário ("custom"), o principal dos quais é useChat ().



Os arquivos "package.json" nos diretórios do componente têm um único campo "main" com o valor do caminho para o arquivo JS, por exemplo:



{
  "main": "./Home"
}

      
      





Isso permite que você importe um componente de um diretório sem especificar um nome de arquivo, por exemplo:



import { Home } from './Home'
// 
import { Home } from './Home/Home'

      
      





Os arquivos "components / index.js" e "hooks / index.js" são usados ​​para agregar e reexportar componentes e ganchos, respectivamente.



components / index.js:



export { Home } from './Home'
export { ChatRoom } from './ChatRoom'

      
      





hooks / index.js:



export { useChat } from './useChat'
export { useLocalStorage } from './useLocalStorage'
export { useBeforeUnload } from './useBeforeUnload'

      
      





Isso novamente permite importar componentes e ganchos por diretório e ao mesmo tempo. A agregação e a reexportação causam o uso de exportações de componentes nomeados (a documentação do React recomenda o uso da exportação padrão).



O arquivo jsconfig.json tem a seguinte aparência:



{
  "compilerOptions": {
    "baseUrl": "src"
  }
}

      
      





Isso "diz" ao compilador que a importação de módulos começa a partir do diretório "src", portanto, os componentes, por exemplo, podem ser importados assim:



//      
import { Home, ChatRoom } from 'components'
// 
import { Home, ChatRoom } from './components'

      
      





Vamos começar examinando os ganchos personalizados.



Você pode usar soluções prontas. Por exemplo, aqui estão os ganchos oferecidos pela biblioteca react-use :



# 
yarn add react-use
# 
import { useLocalStorage } from 'react-use'
import { useBeforeUnload } from 'react-use'

      
      





O gancho useLocalStorage () permite que você armazene (escreva e recupere) valores no armazenamento local do navegador. Vamos usá-lo para armazenar o nome de usuário e a ID do usuário entre as sessões do navegador. Não queremos forçar o usuário a inserir seu nome todas as vezes, mas o ID é necessário para determinar as mensagens pertencentes a esse usuário. O gancho leva um nome para a chave e, opcionalmente, um valor inicial.



hooks / useLocalstorage.js:



import { useState, useEffect } from 'react'

export const useLocalStorage = (key, initialValue) => {
  const [value, setValue] = useState(() => {
    const item = window.localStorage.getItem(key)
    return item ? JSON.parse(item) : initialValue
  })

  useEffect(() => {
    const item = JSON.stringify(value)
    window.localStorage.setItem(key, item)
    //  ,        key,   useEffect,   ,  
    //     useEffect
    // eslint-disable-next-line
  }, [value])

  return [value, setValue]
}

      
      





O gancho "useBeforeUnload ()" é usado para exibir uma mensagem ou executar uma função quando a página (guia do navegador) é recarregada ou fechada. Vamos usá-lo para enviar um evento "user: leave" ao servidor para alternar o status do usuário. Uma tentativa de implementar o despacho do evento especificado usando o retorno de chamada retornado pelo gancho "useEffect ()" não foi bem-sucedida. O gancho leva um parâmetro, um primitivo ou uma função.



hooks / useBeforeUnload.js:



import { useEffect } from 'react'

export const useBeforeUnload = (value) => {
  const handleBeforeunload = (e) => {
    let returnValue
    if (typeof value === 'function') {
      returnValue = value(e)
    } else {
      returnValue = value
    }
    if (returnValue) {
      e.preventDefault()
      e.returnValue = returnValue
    }
    return returnValue
  }

  useEffect(() => {
    window.addEventListener('beforeunload', handleBeforeunload)
    return () => window.removeEventListener('beforeunload', handleBeforeunload)
    // eslint-disable-next-line
  }, [])
}

      
      





O gancho useChat () é o gancho principal para nosso aplicativo. Será mais fácil se eu comentar linha por linha.



hooks / useChat.js:



import { useEffect, useRef, useState } from 'react'
//   IO
import io from 'socket.io-client'
import { nanoid } from 'nanoid'
//  
import { useLocalStorage, useBeforeUnload } from 'hooks'

//  
//    -  
const SERVER_URL = 'http://localhost:5000'

//    
export const useChat = (roomId) => {
  //    
  const [users, setUsers] = useState([])
  //    
  const [messages, setMessages] = useState([])

  //        
  const [userId] = useLocalStorage('userId', nanoid(8))
  //      
  const [username] = useLocalStorage('username')

  // useRef()        DOM-,
  //             
  const socketRef = useRef(null)

  useEffect(() => {
    //   ,    
    //          ""
    // socket.handshake.query.roomId
    socketRef.current = io(SERVER_URL, {
      query: { roomId }
    })

    //    ,
    //         id 
    socketRef.current.emit('user:add', { username, userId })

    //    
    socketRef.current.on('users', (users) => {
      //   
      setUsers(users)
    })

    //     
    socketRef.current.emit('message:get')

    //   
    socketRef.current.on('messages', (messages) => {
      // ,      ,
      //    "userId"     id ,
      //       "currentUser"   "true",
      // ,    
      const newMessages = messages.map((msg) =>
        msg.userId === userId ? { ...msg, currentUser: true } : msg
      )
      //   
      setMessages(newMessages)
    })

    return () => {
      //      
      socketRef.current.disconnect()
    }
  }, [roomId, userId, username])

  //   
  //        
  const sendMessage = ({ messageText, senderName }) => {
    //    id     
    socketRef.current.emit('message:add', {
      userId,
      messageText,
      senderName
    })
  }

  //     id
  const removeMessage = (id) => {
    socketRef.current.emit('message:remove', id)
  }

  //     "user:leave"   
  useBeforeUnload(() => {
    socketRef.current.emit('user:leave', userId)
  })

  //   ,       
  return { users, messages, sendMessage, removeMessage }
}

      
      





Por padrão, todas as solicitações do cliente são enviadas para localhost: 3000 (a porta na qual o servidor de desenvolvimento está sendo executado). Para redirecionar as solicitações para a porta na qual o servidor "servidor" está sendo executado, o proxy deve ser executado. Para fazer isso, adicione a seguinte linha ao arquivo "src / package.json":



"proxy": "http://localhost:5000"

      
      





Resta implementar os componentes do aplicativo.



O componente Home é a primeira coisa que o usuário vê ao iniciar o aplicativo. Ele contém um formulário no qual o usuário é solicitado a inserir seu nome e selecionar uma sala. Na realidade, no caso de um quarto, o usuário não tem escolha, apenas uma opção (gratuita) está disponível. A segunda opção (desabilitada) (trabalho) é a capacidade de dimensionar o aplicativo. A exibição do botão para iniciar um chat depende do campo com o nome do usuário (quando este campo está vazio, o botão não é exibido). O botão é na verdade um link para a página de bate-papo.



componentes / Home.js:



import { useState, useRef } from 'react'
//    react-router-dom
import { Link } from 'react-router-dom'
//  
import { useLocalStorage } from 'hooks'
//    react-bootstrap
import { Form, Button } from 'react-bootstrap'

export function Home() {
  //        
  //     
  const [username, setUsername] = useLocalStorage('username', 'John')
  //    
  const [roomId, setRoomId] = useState('free')
  const linkRef = useRef(null)

  //    
  const handleChangeName = (e) => {
    setUsername(e.target.value)
  }

  //   
  const handleChangeRoom = (e) => {
    setRoomId(e.target.value)
  }

  //   
  const handleSubmit = (e) => {
    e.preventDefault()
    //   
    linkRef.current.click()
  }

  const trimmed = username.trim()

  return (
    <Form
      className='mt-5'
      style={{ maxWidth: '320px', margin: '0 auto' }}
      onSubmit={handleSubmit}
    >
      <Form.Group>
        <Form.Label>Name:</Form.Label>
        <Form.Control value={username} onChange={handleChangeName} />
      </Form.Group>
      <Form.Group>
        <Form.Label>Room:</Form.Label>
        <Form.Control as='select' value={roomId} onChange={handleChangeRoom}>
          <option value='free'>Free</option>
          <option value='job' disabled>
            Job
          </option>
        </Form.Control>
      </Form.Group>
      {trimmed && (
        <Button variant='success' as={Link} to={`/${roomId}`} ref={linkRef}>
          Chat
        </Button>
      )}
    </Form>
  )
}

      
      





O componente UserList, como o nome sugere, é uma lista de usuários. Ele contém um acordeão, a própria lista e indicadores da presença online dos usuários.



components / UserList.js:



// 
import { Accordion, Card, Button, Badge } from 'react-bootstrap'
//  -   
import { RiRadioButtonLine } from 'react-icons/ri'

//      -  
export const UserList = ({ users }) => {
  //    
  const usersArr = Object.entries(users)
  //    ( )
  // [ ['1', { username: 'Alice', online: false }], ['2', {username: 'Bob', online: false}] ]

  //   
  const activeUsers = Object.values(users)
    //   
    // [ {username: 'Alice', online: false}, {username: 'Bob', online: false} ]
    .filter((u) => u.online).length

  return (
    <Accordion className='mt-4'>
      <Card>
        <Card.Header bg='none'>
          <Accordion.Toggle
            as={Button}
            variant='info'
            eventKey='0'
            style={{ textDecoration: 'none' }}
          >
            Active users{' '}
            <Badge variant='light' className='ml-1'>
              {activeUsers}
            </Badge>
          </Accordion.Toggle>
        </Card.Header>
        {usersArr.map(([userId, obj]) => (
          <Accordion.Collapse eventKey='0' key={userId}>
            <Card.Body>
              <RiRadioButtonLine
                className={`mb-1 ${
                  obj.online ? 'text-success' : 'text-secondary'
                }`}
                size='0.8em'
              />{' '}
              {obj.username}
            </Card.Body>
          </Accordion.Collapse>
        ))}
      </Card>
    </Accordion>
  )
}

      
      





O componente MessageForm é um formulário padrão para enviar mensagens. Picker é um componente de emoji fornecido pela biblioteca de emoji-mart. Este componente é mostrado / oculto ao pressionar um botão.



components / MessageForm.js:



import { useState } from 'react'
// 
import { Form, Button } from 'react-bootstrap'
// 
import { Picker } from 'emoji-mart'
// 
import { FiSend } from 'react-icons/fi'
import { GrEmoji } from 'react-icons/gr'

//        
export const MessageForm = ({ username, sendMessage }) => {
  //     
  const [text, setText] = useState('')
  //   
  const [showEmoji, setShowEmoji] = useState(false)

  //   
  const handleChangeText = (e) => {
    setText(e.target.value)
  }

  //  / 
  const handleEmojiShow = () => {
    setShowEmoji((v) => !v)
  }

  //   
  //    ,     
  const handleEmojiSelect = (e) => {
    setText((text) => (text += e.native))
  }

  //   
  const handleSendMessage = (e) => {
    e.preventDefault()
    const trimmed = text.trim()
    if (trimmed) {
      sendMessage({ messageText: text, senderName: username })
      setText('')
    }
  }

  return (
    <>
      <Form onSubmit={handleSendMessage}>
        <Form.Group className='d-flex'>
          <Button variant='primary' type='button' onClick={handleEmojiShow}>
            <GrEmoji />
          </Button>
          <Form.Control
            value={text}
            onChange={handleChangeText}
            type='text'
            placeholder='Message...'
          />
          <Button variant='success' type='submit'>
            <FiSend />
          </Button>
        </Form.Group>
      </Form>
      {/*  */}
      {showEmoji && <Picker onSelect={handleEmojiSelect} emojiSize={20} />}
    </>
  )
}

      
      





O componente MessageListItem é um item da lista de mensagens. TimeAgo é um componente para formatar data e hora. Leva uma data e retorna uma string como "1 mês atrás". Esta linha é atualizada em tempo real. Apenas o usuário que as enviou pode excluir mensagens.



components / MessageListItem.js:



//    
import TimeAgo from 'react-timeago'
// 
import { ListGroup, Card, Button } from 'react-bootstrap'
// 
import { AiOutlineDelete } from 'react-icons/ai'

//         
export const MessageListItem = ({ msg, removeMessage }) => {
  //   
  const handleRemoveMessage = (id) => {
    removeMessage(id)
  }

  const { messageId, messageText, senderName, createdAt, currentUser } = msg
  return (
    <ListGroup.Item
      className={`d-flex ${currentUser ? 'justify-content-end' : ''}`}
    >
      <Card
        bg={`${currentUser ? 'primary' : 'secondary'}`}
        text='light'
        style={{ width: '55%' }}
      >
        <Card.Header className='d-flex justify-content-between align-items-center'>
          {/*  TimeAgo    */}
          <Card.Text as={TimeAgo} date={createdAt} className='small' />
          <Card.Text>{senderName}</Card.Text>
        </Card.Header>
        <Card.Body className='d-flex justify-content-between align-items-center'>
          <Card.Text>{messageText}</Card.Text>
          {/*        */}
          {currentUser && (
            <Button
              variant='none'
              className='text-warning'
              onClick={() => handleRemoveMessage(messageId)}
            >
              <AiOutlineDelete />
            </Button>
          )}
        </Card.Body>
      </Card>
    </ListGroup.Item>
  )
}

      
      





O componente "MessageList" é uma lista de mensagens. Ele usa o componente "MessageListItem".



components / MessageList.js:



import { useRef, useEffect } from 'react'
// 
import { ListGroup } from 'react-bootstrap'
// 
import { MessageListItem } from './MessageListItem'

//    (inline styles)
const listStyles = {
  height: '80vh',
  border: '1px solid rgba(0,0,0,.4)',
  borderRadius: '4px',
  overflow: 'auto'
}

//         
//          "MessageListItem"
export const MessageList = ({ messages, removeMessage }) => {
  //  ""          
  const messagesEndRef = useRef(null)

  //  ,     
  useEffect(() => {
    messagesEndRef.current?.scrollIntoView({
      behavior: 'smooth'
    })
  }, [messages])

  return (
    <>
      <ListGroup variant='flush' style={listStyles}>
        {messages.map((msg) => (
          <MessageListItem
            key={msg.messageId}
            msg={msg}
            removeMessage={removeMessage}
          />
        ))}
        <span ref={messagesEndRef}></span>
      </ListGroup>
    </>
  )
}

      
      





O componente App é o principal componente do aplicativo. Ele define rotas e monta a interface.



src / App.js:



//  
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
// 
import { Container } from 'react-bootstrap'
// 
import { Home, ChatRoom } from 'components'

// 
const routes = [
  { path: '/', name: 'Home', Component: Home },
  { path: '/:roomId', name: 'ChatRoom', Component: ChatRoom }
]

export const App = () => (
  <Router>
    <Container style={{ maxWidth: '512px' }}>
      <h1 className='mt-2 text-center'>React Chat App</h1>
      <Switch>
        {routes.map(({ path, Component }) => (
          <Route key={path} path={path} exact>
            <Component />
          </Route>
        ))}
      </Switch>
    </Container>
  </Router>
)

      
      





Finalmente, o arquivo "src / index.js" é o ponto de entrada JavaScript para Webpack. Ele faz estilização e renderização globais do componente App.



src / index.js:



import React from 'react'
import { render } from 'react-dom'
import { createGlobalStyle } from 'styled-components'
// 
import 'bootstrap/dist/css/bootstrap.min.css'
import 'emoji-mart/css/emoji-mart.css'
// 
import { App } from './App'
//   "" 
const GlobalStyles = createGlobalStyle`
.card-header {
  padding: 0.25em 0.5em;
}
.card-body {
  padding: 0.25em 0.5em;
}
.card-text {
  margin: 0;
}
`

const root = document.getElementById('root')
render(
  <>
    <GlobalStyles />
    <App />
  </>,
  root
)

      
      





Bem, terminamos de desenvolver nosso pequeno aplicativo.



É hora de ter certeza de que funciona. Para isso, no diretório raiz do projeto (react-chat), execute o comando "yarn start". Depois disso, na guia do navegador que se abre, você deve ver algo assim:















Em vez de uma conclusão



Se você deseja melhorar o aplicativo, aqui estão algumas idéias:



  • Adicionar banco de dados para usuários (usando o mesmo lowdb)
  • Adicione uma segunda sala - para isso é suficiente implementar o processamento separado de listas de mensagens no servidor
  • ( ) —
  • MongoDB Cloud Mongoose; Express
  • : (, , ..) — react-filepond, — multer; WebRTC
  • Do mais exótico: adicione voz ao texto e traduza mensagens de voz em texto - você pode usar o kit react-speech para isso


Algumas dessas ideias estão incluídas em meus planos para melhorar o chat.



Obrigado pela atenção e tenha um bom dia.



All Articles