Build Realtime Chat App with Socket.io, NodeJS, and ReactJS

In this article, you will learn how to build a real-time chat application using NodeJS, ReactJS, and the Socket.io library. This article will provide a good introduction to WebSockets and the Socket.io library.

The application will be very simple in terms of features and design, as it is mainly intended to demonstrate the basic implementation of a Socket server and how to interact with it in a frontend application.

Here is a video of the final application:

Prerequisites

In order to follow along with the tutorial, you will need the following:

  • NodeJS v10+ installed
  • Basic knowledge of NodeJS, Express, ReactJS

WebSockets & Socket.io

Before we start building the application, we will discuss what WebSockets is and why it is useful for building web applications with real-time features.

WebSockets is a protocol for bidirectional, or two-way, communication between a client and server.

But what makes a bidirectional communication protocol like WebSockets preferable to a unidirectional protocol like HTTP when building real-time features?

While HTTP requires the client to send a request to the server first to receive any data, a bidirectional protocol such as WebSockets allows the connection between client and server to persist.

This means that a client is not required to make a new request to the server every time that they want to check for new data from the server.

In this tutorial, we will use the WebSockets API to implement a real-time chat, but we won’t be working with the API directly. Instead, we will use Socket.io as a wrapper over WebSockets.

Socket.io is a library that abstracts away all of the complications of working with WebSockets, allowing you to implement bidirectional communication in your application with less headache.

The Socket.io library contains two parts: a server that mounts onto the NodeJS HTTP server and a client-side library for the browser.

Building the Socket Server

We will start by building out the backend of the application.

First, create a new NodeJS project by running the following command:

npm init -y

Installing dependencies

We will need to install the following packages:

  • Express: NodeJS framework that makes building back end web applications easier
  • Socket.io: Library that enables real-time, bi-directional client-server communication
  • CORS: Mechanism that allows restricted resources from a domain to be shared with other domains
npm install express socket.io cors

Create a server file called index.js. Inside the server file, import Express and create an Express app:

const express = require("express");

const app = express();

CORS middleware

Cross-origin resource sharing (CORS) is an HTTP header mechanism that allows servers to grant permission to other external domains to access server resources.

The CORS NodeJS package provides a middleware that can be used to enable CORS on your server.

Import the CORS package and enable the Express server to accept cross-origin HTTP requests:

const cors = require("cors");

app.use(cors());

Creating Socket IO object

Although HTTP comes built-in to Express, we will need to access it directly to set up the Socket.io server.

So, we need to first create an HTTP server, which we can do using the built-in HTTP library in NodeJS.

const http = require("http");
const server = http.createServer(app);

Import the Server function from Socket.io:

const { Server } = require("socket.io");

Now, we will initialize a new instance of socket.io by calling all the Server function from Socket.io, taking in the HTTP server as an argument. Since we will be accessing the backend from a cross-origin frontend browser application, we need to pass a CORS object as an optional second argument to allow any URL to access our server URL:

const io = new Server(server, {
  cors: {
    origin: "*",
    methods: ["GET", "POST"],
  },
});

server.listen(8000, () => {
  console.log(`Server is running on port ${PORT}`);
});

Creating Socket event listener

Now, we can implement Socket IO logic. Just like WebSockets, it’s an event based system. When an event is emitted, you will have access to any data that was passed along with the event in the callback function. There, you can implement some function in response to the event.

The first event that you will want to listen for is the connection of a client from the frontend. The callback of the connection event will give us access to the client Socket object.

io.on('connection', (socket) => { 
    console.log('new client connected');
});

Whenever there is a new Socket connection, we will send the user that connected their Socket ID using io.to().emit()

By calling to and passing in the client’s Socket ID, we are ensuring that the event is only emitted to the specific client.

io.on("connection", (socket) => {
  io.to(socket.id).emit("socket_id", socket.id);
});

Now, we will create a custom event handler to handle the send_message event from the client. This event will be emitted anytime a user submits a new message in the chat application. The message data that is sent along with the event is passed to the callback function.

Taking the message data, the server emits a receive_message event to all clients connected to the server (except the client who sent the message). In short, this event sends newly submitted messages to other users of the application.

io.on("connection", (socket) => {
  socket.on("send_message", (messageData) => {
    socket.broadcast.emit("receive_message", messageData);
  });
});

We have finished implementing the backend for the application. Here is the full server file code:

const express = require("express");
const http = require("http");
const cors = require("cors");
const { Server } = require("socket.io");

const app = express();
const server = http.createServer(app);

app.use(cors());

const io = new Server(server, {
  cors: {
    origin: "*",
    methods: ["GET", "POST"],
  },
});

io.on("connection", (socket) => {
  io.to(socket.id).emit("socket_id", socket.id);

  socket.on("send_message", (messageData) => {
    socket.broadcast.emit("receive_message", messageData);
  });
});

server.listen(8000, () => {
  console.log(`Server is running on port 8000`);
});

Building React frontend application

In a separate client folder, create a new React application by running the following command:

npx create-react-app client

Now, open the App.js file in the src folder. This is the only file we’ll be using in the React project.

React boilerplate setup

Before we add Socket IO to the frontend, we will set up the layout of the frontend, create state variables and other Hooks elements, and handle some other boilerplate.

socketID will store the ID of the client’s socket.

currentMessage will store the text typed into the input. When the user presses the Send button, the will send this message text to other clients in the send_message event.

messageList will store all of the messages– both from the current user and other users– sent thus far.

The sendMessage function will be responsible for sending messages from the current user to the server.

import React, { useState, useEffect, useRef } from "react";
import "./styles.css";

function App() {
  const [socketID, setSocketID] = useState("");
  const [currentMessage, setCurrentMessage] = useState("");
  const [messageList, setMessageList] = useState([]);

  const sendMessage = async () => {
    
  };

  useEffect(() => {
    
  }, []);

  return (
    <div class="container">
      <h1>Realtime Chat</h1>

      <div class="msg-container-wrapper">
        <div class="message-container">
          {messageList.map((msg) => {
            return (
              <div class={`message ${msg.sender === socketID ? "my-msg" : "other-msg"}`}>
                {msg.message}
              </div>
            );
          })}
        </div>
      </div>

      <div class="input-container">
        <input
          type="text"
          value={currentMessage}
          onChange={(e) => setCurrentMessage(e.target.value)}
        />
        <button onClick={() => sendMessage(currentMessage)}>Send</button>
      </div>
    </div>
  );
}

export default App;

Installing Socket IO client

Socket IO is not a direct WebSocket implementation, which means that you can’t just use the built-in WebSocket class in the browser. Instead, you must use the Socket IO client library.

You can install it with the following command:

npm install socket.io-client

The io object, which is the Socket IO client library, will now be globally available in the browser and you can import it throughout your project.

Import the io object:

import { io } from "socket.io-client";

Create the connection by pointing to the URL of the server:

const socket = io("http://localhost:8000");

Now, we are ready to start listening to events from and emitting events to the server.

Getting client Socket ID from the server

In the useEffect hook, our client Socket will listen for the socket_id event from the server which sends the current user’s socket ID. We use this socket ID to differentiate between messages sent by the current user and messages sent by other users.

We also return an anonymous function that acts as a component cleanup upon unmounting.

useEffect(() => {
    socket.on("socket_id", (id) => {
      setSocketID(id);
    });

    return () => {
      socket.off("socket_id");
    };
}, [socket]);

Receiving messages from the server

Next, we will add an event listener to the useEffect hook to listen for the receive_message event. This event is triggered to receive any new messages from the Socket IO server.

When the event occurs, the data from the event, which is an array including the message text and the message sender, is added to the messageList array to be added on screen.

Again, we create an anonymous cleanup function for the event listener.

useEffect(() => {
    socket.on("receive_message", (data) => {
      setMessageList((list) => [...list, data]);
    });

    return () => {
      socket.off("receive_message");
    };
}, [socket]);

Sending messages to the server

Now, we will create a function that will send a new message to the Socket IO server. This will be an asynchronous function.

First, we check to make sure that the current message is not an empty string. If it is not, then we create a messageData object including the socket ID of the sender and the message text.

Then, we use the current user’s socket to emit a send_message event to the server, passing along the messageData object with it.

When the server gets this data, it will emit it to all of the other users in the application. However, the user who actually sent the message will not receive the data from the server. Instead, the data is added to the messageList manually. Then, the currentMessage is cleared.

const sendMessage = async () => {
    if (currentMessage !== "") {
      const messageData = {
        sender: socketID,
        message: currentMessage,
      };

      await socket.emit("send_message", messageData);
      setMessageList((list) => [...list, messageData]);
      setCurrentMessage("");
    }
};

We have finished implementing the logic for the frontend application. Here is the full App.js file code:

import React, { useState, useEffect, useRef } from "react";
import { io } from "socket.io-client";
import "./styles.css";

const socket = io("http://localhost:8000");

function App() {
  const [socketID, setSocketID] = useState("");
  const [currentMessage, setCurrentMessage] = useState("");
  const [messageList, setMessageList] = useState([]);

  const sendMessage = async () => {
    if (currentMessage !== "") {
      const messageData = {
        sender: socketID,
        message: currentMessage,
      };

      await socket.emit("send_message", messageData);
      setMessageList((list) => [...list, messageData]);
      setCurrentMessage("");
    }
  };

  useEffect(() => {
    socket.on("socket_id", (id) => {
      setSocketID(id);
    });

    socket.on("receive_message", (data) => {
      setMessageList((list) => [...list, data]);
    });

    return () => {
      socket.off("socket_id");
      socket.off("receive_message");
    };
  }, [socket]);

  return (
    <div class="container">
      <h1>Realtime Chat</h1>

      <div class="msg-container-wrapper">
        <div class="message-container">
          {messageList.map((msg) => {
            return (
              <div class={`message ${msg.sender === socketID ? "my-msg" : "other-msg"}`}>
                {msg.message}
              </div>
            );
          })}
        </div>
      </div>

      <div class="input-container">
        <input
          type="text"
          value={currentMessage}
          onChange={(e) => setCurrentMessage(e.target.value)}
        />
        <button onClick={() => sendMessage(currentMessage)}>Send</button>
      </div>
    </div>
  );
}

export default App;

With that, you have basically completed the application! All that is left is the CSS styling, which we won’t cover in this tutorial. In the links below, you can access the CSS that was used in the demo project.

Thank you for following this tutorial. Happy coding!

Adding TypeScript to Existing React Project

Installation

First install it into the project:

npm install --save typescript @types/node @types/react @types/react-dom @types/jest

or

yarn add typescript @types/node @types/react @types/react-dom @types/jest

Next, rename any file to be a TypeScript file (e.g. src/index.js to src/index.tsx). Type errors will start to show up.

Generate tsconfig.json file

Although the React documentation says that you are not required to make a tsconfig.json file because one will be made automatically for you, that was not my experience. After doing the installation, a tsconfig.json file was not generated for me.

I had to run the following command to generate the file:

npx tsc --init

Now, you should be able to run your React project with TypeScript, if there are no TypeScript errors.

React Error: Attempted import error ‘X’ is not exported from

This error occurs when you try to import something that is not present in the specified file. When solving this error, check to make sure you are using the corresponding import and export mechanisms.

Default exports

Default exports are useful to export only a single object, function, variable. You can have only one default export per file.  

How to make a default export:

export default function Component() {
   return <h1>Hello, world</h1>
}

How to import a component with a default export:

import Component from './Component';

Notice that you do not use curly braces when importing a default export.

When importing a default export, you can use any name you want since there is only one in the file. For example:

import MyNewName from './Component';

Named import

You can have more than one named exports per file.

How to make a named export:

export function add(a, b) {
    return a + b;
}

export function subtract(a, b) {
    return a - b;
}

How to export multiple named modules at once:

export {
   add,
   subtract,
}

How to import a named export:

import { add, subtract } from './another-file';

When using named imports, the name of imported module must be the same as the name of the exported module.

You can also import both a default module and a named module on the same line, like so:

import Component, { add } from './another-file';

Testing Event Handlers in React Testing

In this short article, you will learn how to test event handlers, such as button clicks, with React Testing.

First, we will create a simple component to test:

import React from "react";

export default function ButtonWrapper({ title, ...props }) {
  return <button {...props}>{title}</button>;
}

This ButtonWrapper component takes in a title prop and any other props and returns a standard JSX button element.

Now, create a testing file with the same name as the component file and a .test.js extension (or .test.tsx if you are using TypeScript) (i.e. ButtonWrapper.test.js)

First, import the following from React testing and import the component:

import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import ButtonWrapper from "./ButtonWrapper";

Now, create the test and give it a name (i.e. "handles onClick")

test("handles onClick", () => {
 
})

Render the ButtonWrapper component:

render(<ButtonWrapper title={"Add"} />);

We will add an onClick property to the button and call the jest.fn() function whenever the component is clicked:

const onClick = jest.fn();
render(<ButtonWrapper onClick={onClick} title={"Add"} />);

jest.fn() is a function created by Jest which tracks how often it is called. In other words, it will keep track of how many times the button component is clicked.

Now, we will get access to the button and click it using fireEvent.click():

const buttonElement = screen.getByText("Add");
fireEvent.click(buttonElement);

fireEvent.click() simulates a click on the button element.

Next, we will write an assertion for how many times the button has been clicked. First, we will write an inaccurate assertion:

expect(onClick).toHaveBeenCalledTimes(0);

Now, we will run our test:

yarn test

This test will not pass because we know that the button was clicked once by the fireEvent call. The output should look like this:

Basically, React Testing is saying that it expected 0 calls to be made, but it received 1 call.

Now, let’s make a correct assertion:

expect(onClick).toHaveBeenCalledTimes(1);

The output should look like this:

Here is the final code:

import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import ButtonWrapper from "./ButtonWrapper";

test("handles onClick", () => {
  const onClick = jest.fn();
  render(<ButtonWrapper onClick={onClick} title={"Add"} />);
  const buttonElement = screen.getByText("Add");
  fireEvent.click(buttonElement);
  expect(onClick).toHaveBeenCalledTimes(1);
});

Now, you can test event handlers in React Testing. Thanks for reading!

Basic Component Testing with React Testing and TypeScript

In this article, we will create a simple React component and do some basic testing on the component using React Testing. This will help you get acquainted with the React Testing library and how to write tests.

Installation

The React Testing library comes by default when you run create-react-app, so it should already be in your project if you created your React project with that command.

Create TypeScript component

First, we will create a component using TypeScript. Make a file called Container.tsx

In the Container component, we will have a div element with a h1 inside of it:

import React from "react";

export const Container = ({ title }: { title: string }) => (
  <div role="contentinfo">
    <h1>{title}</h1>
  </div>
);

We define a title prop with a type of string in TypeScript with the code: { title }: { title: string }

We also set an aria-role for the div element of type contentinfo

Write Tests

Now, let’s write some tests for this component

Create a test file called Container.test.tsx

First, we need to add some imports:

import React from "react";
import { render, screen } from "@testing-library/react";
import { Container } from "./Container";

As you can see, we are importing render and screen from the React testing library, which we will use momentarily.

To create a new test, we use the following structure (which is based on Jest, which is the underlying test framework):

test("Name of test", () => {
   // Function body of test
});

So, we will create a test named “renders title” and then define it:

test("renders title, () => {
    // Test will go here
})

First, render the Container component:

render(<Container title={"New Container"} />);

Next, get access to an element:

const titleElement = screen.getByText(/New Container/i);

There are many ways to get access to an element in the library

  • getByText
  • getByRole (aria-role)
  • getByLabelText
  • getByPlaceholderText

Make an assertion about the element:

expect(titleElement).toBeInDocument()

The above assertion is simply that the title element is in the Container component. This is just a very simple test.

So, here is the full test code:

test("renders title, () => {
    render(<Container title={"New Container"} />);
    const titleElement = screen.getByText(/New Container/i);
    expect(titleElement).toBeInDocument()
})

Run yarn test

The test output should look like this if it passed successfully:

Now, you have a basic introduction to testing components in React.

Next.js Global State w/ React Context API

The easiest way to implement global state management in your Next.js application is the React Context API.

I made a new file called ContextProvider.js and created a Context object called MyContext:

import React, { useState, useContext, useEffect } from 'react';

const MyContext = React.createContext();

export function useMyContext() {
    return useContext(MyContext);
}

export function MyProvider({ children }) {
    const [myValue, setMyValue] = useState(false);

    return (
        <MyContext.Provider value={{myValue, setMyValue}}>
            {children}
        </MyContext.Provider>
    );
}

Once this is done, go back to pages/_app.js and wrap your component with the context provider:

import { MyProvider } from '../contexts/MyProvider';

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

export default MyApp;

Now I can use the state within MyContext in every part of the application.

Make sure to be careful about how much you put into Context. You don’t want unnecessary re-renders across pages when you could just share them across specific components.

React State Management with the Context API

Component-level state is the state within an individual component. It is only available to that component and only affects that component. We can use hooks such as useState to control component-level state.

But what if you want a piece of state to be available to multiple different components or all of your components? This is what we call application-level state.

Application-level state is the state that is available to the entire application. In order to implement this type of state, we need to implement more complex solutions.

One solution would be to “lift up” the state. This involved moving the piece of state to your App component, the root component which holds all of the other components. While this is a bit simpler to implement than other solutions, the problem with it is that you are giving the App component state that it doesn’t directly need and you will have to pass down the state to each component in a very long-winded way. This is called prop-drilling.

Instead, we can use the Context API as a more dynamic and versatile solution. Here’s an example of how it would be used:

Context File

import React, { useState, createContext } from 'react';

export const MyContext = createContext();

export const MyProvider = props => {
   const [property, setProperty] = useState('Initial state of property');

   return (
      <MyContext.Provider value={[property, setProperty]}>
         {props.children}
      </MyContext.Provider>

App File

import React from 'react';
import { MyProvider } from './MyContext';

function App() {
   return (
      <MyProvider>
         <div>
            <MyComponent />
            Other Components...
         </div>
      </MyProvider>
   );
}

Component File

import React, { useState, useContext } from 'react';
import { MyContext } from './MyContext';

const MyComponent = () => {
   const [property, setProperty] = useContext(MyContext);

   return (
      <h1>{property}</h1>
   );
};

export default MyComponent;

The downside to the Context API is every time we update the code in our Context, all the components that use the context have to re-render.

The benefits are that it’s more simple than Redux and doesn’t require any third-party libraries.

What is React.js?

React.js is a JavaScript library for building user interfaces. It is a front-end web framework. React.js breaks down the user interface into customizable components. It combines pieces of HTML, CSS, and JavaScript in each component. It allows the browser to refresh a single component without having to refresh the entire page. It is one of the most popular and in-demand web frameworks

Most people put all of their body content within a div with the id “root.” This is React convention.

<div id="root">
</div>

Before we can start coding, we must install our dependencies

npm i react react-dom
ReactDOM.render(WHAT TO SHOW, WHERE TO SHOW it, optional callback after render function completed)
var React = require("react");
var ReactDOM = require("react-dom");

ReactDOM.render(<h1>Hello world!</h1>, document.getElementById("root"));

React works by creating these JSX files. HTML is inputted into compiler which converts it into Vanilla JavaScript. The compiler comes from including the React module.

Inside the React Module, there is something called Babel. Babel is a JavaScript compiler. It’s able to take next-gen JavaScript (es6, es7, es8) and compile it down to a version that any browser can understand. This includes compiling JSX into plain JS.

var h1 = document.createElement("h1");
h1.innerHTML = "Hello World!";
document.getElementById("root").appendChild(h1)
import React from "react";
import ReactDOM from "react-dom";

Render method can only take a single HTML element. If you put two back-to-back, it will crash. The way around this is wrapping n elements into a div, so that it’s “1 element.”

ReactDOM.render(
<div>
   <h1>Hello world!</h1>
   <p>This is the first paragraph.</p>
</div>, 
document.getElementById("root")
);
const name = "Joshua";

ReactDOM.render(<h1>Hello {name}!</h1>, document.getElementById("root"));
const num = 4;

ReactDOM.render(<p>Your lucky number is {num}!</p>, document.getElementById("root"));
ReactDOM.render(<p>Your lucky number is {Math.floor(Math.random() * 10)}!</p>, document.getElementById("root"));

You can add any JavaScript value or expression inside the curly braces, but you can’t write JavaScript statements(if statements, etc.)

Styling

External Stylesheets

<h1 class="heading">Title</h1> // no 
<h1 className="heading">Title</h1> // yes

Although it looks like HTML, it’s JSX. It’s being converted to Vanilla JavaScript. There is no “class” property in JS; the correct JS equivalent is the className property.

HTML uses all lowercase. When we write JSX we use camel case because JS uses camel case.

contenteditable vs contentEditable

<h1 contentEditable="true" spellCheck="false">My Favourite Foods</h1>

It is recommended to do all of your styling in an external CSS file then apply those styles to your JSX content with class and id tags.

.heading {
   color: 'red';
}
<h1 className="heading">My Favorite Foods</h1>

Inline Styling

The style property requires a JavaScript object

JavaScript object

{
  key: value,
}
<h1 style="color: 'red'">My Title</h1> // no
<h1 style={{color: 'red'}}>My Title</h1> // yes

Any JavaScript within HTML must be wrapped in curly braces. JavaScript objects must be wrapped in curly braces. Thus, 2 sets of curly braces

const customStyle = {
  color: "red",         // use commas instead of semicolons
  fontSize: "20px",     // font-size becomes fontSize
  border: "1px solid #000" // every value goes in quotes
}

customStyle.color = "blue";  // change styles after definition

ReactDOM.render(
  <div>
    <h1 style={customStyle}>My Favourite Foods</h1>
  </div>,
  document.getElementById("root")
);

React Components

We always capitalize the names of our components because that’s how React differentiates that they are custom components

function Heading() {
   return <h1>My Heading!</h1>
}

ReactDOM.render(
   <div>
      <Heading />
   </div>,
   document.getElementById("root")
);

In the same folder, create a new file called “Heading.jsx”

All of our components separated into individual files with the .jsx extension

import React from "react";

function Heading() {
   return <h1>My Heading!</h1>;
}

export default Heading;

Don’t use parentheses. That will make it run immediately; we want to use it as a component

import React from "react";
import ReactDOM from "react-dom";
import Heading from "./Heading";

Most times, you won’t see any div or child elements in the index.js file. Instead, it will just be a single App custom component.

import React from "react";
import Heading from "./Heading";   // child component
import List from "./List";         // child component

function App(){
   return <div>
      <Heading />
      <List />
   </div>
}

export default App;
ReactDOM.render(
   <App />,
   document.getElementById("root")
);

Create a components folder and put all of your components in it

  • src
    • components
      • App.jsx
      • Heading.jsx
      • List.jsx
  • public
    • index.html
    • styles.css
import App from "./components/App";

Setup React Front-End in Existing Project

Let’s say you’re building with the MERN stack, for example. You have an existing project folder in which you’ve set up your entire backend server with Express and connecting your MongoDB database. Now you want to append a React frontend to the project. How do you setup a new React project within your existing overall project folder?

cd into your existing project folder and run this command:

npx create-react-app [your-folder-name]

This line basically creates a React application within a folder with whatever name you choose (ex. client) within your current project directory. npx is a tool that comes with Node.js that allows us to run create-react-app without having to install it globally on our machine.

To run your React project to make sure everything installed successfully, run these commands:

cd [your-folder-name]
yarn start

Run Frontend and Backend at the Same Time

We can run our frontend React app and backend Node.js server at the same time using the development tool concurrently.

Install concurrently as a development dependency:

npm i -D concurrently

Define the necessary scripts within your package.json:

"scripts": {
    "start": "node server",
    "server": "nodemon server",
    "client": "npm start --prefix client",
    "dev": "concurrently \"npm run server\" \"npm run client\""
},

The “dev” script uses concurrently to run the “server” and “client” scripts at the same time– with one single command.

In the root folder, run the “dev” script:

npm run dev

Installing Dependencies Within the React Project

You install dependencies within a submerged React project the same way you would within an non-submerged React project.

First, make sure you’re inside the React project folder. My folder name is client so:

cd client

Then, install dependencies with npm

npm i react-redux axios ...

Get Ride of Default Git Repository Within React App

Within the React app, a git repository is automatically initialized on the creation of the React app. If you already have a Git repository within your root project, you don’t necessarily need another one for your React project. If you wanted to delete the React repository, you would

  1. Delete the .gitignore and README.md files within the React folder
  2. Delete the Git folder

This is the command to delete the git folder. Make sure you cd into your React folder before running this command or you might delete your root repository:

rm -rf .git

Event Handling in React

This is the App.jsx file. You would render an App Component in the ReactDOM.render method inside the index.js file. You can create your own HTML pages and CSS styles.

import React, { useState } from "react";

function App() {
   const [isMousedOver, setMouseOver] = useState(false);

   function handleMouseOver() {
      setMouseOver(true);
   }

   function handleMouseOver() {
      setMouseOver(false);
   }

   return (
      <div>
         <button
            style={{ backgroundColor: isMousedOver ? "black" : "white" }}
            onMouseOver={handleMouseOver}
            onMouseOut = {handleMouseOut}
         >
            Hover
         </button>
      </div>
   );
}

export default App;