# 15 Chat Realtime

Aprendamos a utilizar las bases de datos en tiempo real, un servicio que ya viene incorporado en Firestore: https://firebase.google.com/docs/firestore/query-data/listen?authuser=0 (opens new window)

db.collection("cities").doc("SF")
    .onSnapshot(function(doc) {
        console.log("Current data: ", doc.data());
    });

# index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.jsx';
import ChatProvider from './context/ChatProvider.js';

ReactDOM.render(
  <React.StrictMode>
    <ChatProvider>
      <App />
    </ChatProvider>
  </React.StrictMode>,
  document.getElementById('root')
);

# App.jsx

import React from 'react'
import {ChatContext} from './context/ChatProvider'
import Chat from './components/Chat'
import Navbar from './components/Navbar'

const App = () => {

    const {usuario} = React.useContext(ChatContext)

    return usuario.activo !== null ? (
        <div>
            <Navbar />
            {
                usuario.activo ? (
                    <Chat />
                ) : (
                    <div className='text-center mt-5 lead'>
                        click en acceder para iniciar chat
                    </div>
                )
            }
        </div>
    ) : (<div>Cargando...</div>)
}

export default App

# ChatProvider.js

import React from 'react'
import {auth, provider, db} from '../firebase'

export const ChatContext = React.createContext()

const ChatProvider = (props) => {
    
    const dataUsuarioInicial = {email: null, uid: null, activo: null}
    const [usuario, setUsuario] = React.useState(dataUsuarioInicial)
    const [mensajes, setMensajes] = React.useState([])

    React.useEffect(() => {
        detectarUsuario()
    }, [])
    
    const detectarUsuario = () => {
        auth.onAuthStateChanged(userChange => {
            if(userChange){
                // console.log(userChange)
                setUsuario({email: userChange.email, uid: userChange.uid, activo: true})
                cargarMensajes()
            }else{
                // console.log(userChange)
                setUsuario({email: null, uid: null, activo: false})
            }
        })
    }

    const iniciarSesion = async() => {
        try {
            const res = await auth.signInWithPopup(provider)
            // console.log(res)
        } catch (error) {
            console.log(error)
        }
    }

    const cerrarSesion = () => {
        auth.signOut()
    }

    const cargarMensajes = () => {
        db.collection('messages').orderBy('fecha')
        .onSnapshot(query => {
            const arrayMensajes = query.docs.map(item => item.data())
            setMensajes(arrayMensajes)
        })
    } 

    const agregarMensaje = async (uid, texto) => {
        try {
            await db.collection('messages').add({
                uid: uid,
                texto: texto,
                fecha: Date.now()
            })
        } catch (error) {
            console.log(error)
        }
    }

    return (
        <ChatContext.Provider value={{usuario, iniciarSesion, cerrarSesion, mensajes, agregarMensaje}}>
            {props.children}
        </ChatContext.Provider>
    )
}

export default ChatProvider
import React from 'react'
import {ChatContext} from '../context/ChatProvider'

const Navbar = () => {

    const {usuario, iniciarSesion, cerrarSesion} = React.useContext(ChatContext)

    return (
        <nav className="navbar navbar-dark bg-dark">
            <span className="navbar-brand">Chat</span>
            <div>
                {
                    usuario.activo ? (
                        <button 
                            className="btn btn-outline-success my-2 my-sm-0" 
                            onClick={cerrarSesion}
                        >
                            Salir
                        </button>
                    ) : (
                        <button 
                            className="btn btn-outline-success my-2 my-sm-0" 
                            onClick={iniciarSesion}
                        >
                            Acceder
                        </button>
                    )
                }
            </div>
        </nav>
    )
}

export default Navbar

# Chat.jsx

import React from 'react'

import {ChatContext} from '../context/ChatProvider'
import Agregar from './Agregar'

const Chat = () => {

    const {usuario, mensajes} = React.useContext(ChatContext)
    const refZonaChat = React.useRef(null)

    React.useEffect(() => {
        if(refZonaChat.current !== null){
        //   console.log('useefecct', refZonaChat.current)
        //   console.log('scrollTop', refZonaChat.current.scrollTop)
        //   console.log('scrollHeight', refZonaChat.current.scrollHeight)
          refZonaChat.current.scrollTop = refZonaChat.current.scrollHeight;
        }
      }, [mensajes])

    return (
        <div 
            className='mt-3 px-2' 
            ref={refZonaChat} 
            style={{ height: '75vh', overflowY: 'scroll'}}
        >

            {
                mensajes.map((item, index) => (
                    item.uid === usuario.uid ? (
                        <div className="d-flex justify-content-end mb-2" key={index}>
                            <span className="badge badge-pill badge-primary">{item.texto}</span>
                        </div>
                    ) : (
                        <div className="d-flex justify-content-start mb-2" key={index}>
                            <span className="badge badge-pill badge-secondary">{item.texto}</span>
                        </div>
                    )
                ))
            }

            <Agregar />
        
        </div>
    )
}

export default Chat

# Agregar.jsx

import React from 'react'

import {ChatContext} from '../context/ChatProvider'

const Agregar = () => {

    const {agregarMensaje, usuario} = React.useContext(ChatContext)

    const [mensaje, setMensaje] = React.useState('')

    const formulario = e => {
        e.preventDefault()
        if(!mensaje.trim()){
            console.log('texto vacío')
            return
        }
        agregarMensaje(usuario.uid, mensaje)
        setMensaje('')
    }

    return (
        <form 
            className="input-group fixed-bottom p-3 bg-dark"
            onSubmit={formulario}
        >
            <input 
                type="text" 
                className="form-control"
                onChange={e => setMensaje(e.target.value)}
                value={mensaje}
            />
            <div className="input-group-append">
                <button className="btn btn-primary" type="submit">Enviar</button>
            </div>
        </form>
    )
}

export default Agregar

Last Updated: 6/4/2022, 12:52:28