Hi everyone! Today, we are going to make a basic chat application using Spring Boot and React.js. We'll follow the steps assuming you're a complete beginner and only know the basics of Spring Boot and React.js.
First, we’ll create a Spring Boot project. Go to the website https://start.spring.io/.
Currently, I am using Java 17, so I have chosen Java 17 and selected Maven as the build tool. I’m adding some basic dependencies for now; we’ll add more as needed later. After setting everything up, click on "Generate" to download the file. Once it’s downloaded, extract it and open it in your editor. I’m going to use IntelliJ IDEA.
- config
- controller
- dto
- model
- repository
- service
Create a User Entity inside model
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
private String email;
@Column(unique = true)
private String userName;
private String password;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of();
}
@Override
public String getUsername() {
return userName;
}
}
I won’t go into detail about what's written here, but since we’ll be using JWT authentication, don’t forget to implement UserDetails. If you’re not familiar with authentication and authorization, you can refer to other blogs for a deeper understanding.
After this, you will encounter an error because we haven’t added the Spring Security dependencies. Let’s add these dependencies first, and we will add more as we progress.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>6.3.1</version>
</dependency>
Now Create Another entity MessageEntity as below
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.LocalDateTime;
@Entity
@Table(name = "messages")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class MessageEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "sender")
private String sender;
@Column(name = "receiver")
private String receiver;
@Column(name = "message")
private String message;
@Column(name = "timestamp")
private LocalDateTime timestamp;
}
So far, we have created two entities, but no tables have been created in the database yet. I’m using MySQL, but you can choose your preferred database and add dependencies accordingly. Now, it’s time to set up the application.properties file.
When you run your program in MySQL Workbench, two tables will be created as shown above.
now we have created our entity now lets cerate controller to register and log in.
so inside controller create a class name as AuthenticationController below
@RestController
@CrossOrigin
public class AuthenticationController {
@Autowired
private AuthenticationService authService;
@PostMapping("/register")
public ResponseEntity<AuthenticationResponse> register(
@RequestBody User request
) {
return ResponseEntity.ok(authService.register(request));
}
@PostMapping("/login")
public ResponseEntity<AuthenticationResponse> login(
@RequestBody User request
) {
return ResponseEntity.ok(authService.authenticate(request));
}
now we are going to see a lot of red flag here beacause we had not created repository and service class so lets create it but before this lets add some securityconfig inside config like this.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private UserService userService;
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.anyRequest().permitAll()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
}
Now let's create the AuthenticationService and UserService. You may see some red flags because we are using certain elements in these services that we haven't implemented yet. Don't worry about that; we will cover them in detail later.
AuthenticationService
@Service
public class AuthenticationService {
@Autowired
private UserRepository repository;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private JwtService jwtService;
@Autowired
private AuthenticationManager authenticationManager;
public AuthenticationResponse register(User request) {
User user = new User();
user.setName(request.getName());
user.setPassword(passwordEncoder.encode(request.getPassword()));
user.setUserName( request.getUsername());
user.setEmail(request.getEmail());
repository.save(user);
String token = jwtService.generateToken(user);
return new AuthenticationResponse(token);
}
public AuthenticationResponse authenticate(User request) {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.getUsername(),
request.getPassword()
)
);
User user= repository.findByUserName(request.getUsername());
String token=jwtService.generateToken(user);
return new AuthenticationResponse(token);
}
}
UserService
@Repository
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User addUser(User user) {
userRepository.save(user);
return user;
}
public List<User> getUsers() {
return userRepository.findAll();
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findByUserName(username);
}
}
Now, since we have not created a repository yet, let's create one. By using a repository, we can manage the database. So, inside the repository package, create two repositories: UserRepository and MessageRepository.
UserRepository
public interface UserRepository extends JpaRepository<User,Long>{
User findByUserName(String username);
}
MessageRepository
public interface MessageRepository extends JpaRepository<MessageEntity,Long> {
@Query("SELECT m FROM MessageEntity m WHERE (m.sender = :sender AND m.receiver = :receiver) OR (m.sender = :receiver AND m.receiver = :sender)")
List<MessageEntity> findMessages(@Param("sender") String sender, @Param("receiver") String receiver);
}
Now, we've moved ahead, but since we're using JWT authentication and haven't implemented it yet, let's import the necessary dependencies and create a service and filter for it. We need to add some dependencies, so let's include them.
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- to use Jackson for JSON parsing -->
<version>0.11.5</version>
</dependency>
now lets create jwtservice class
@Service
public class JwtService {
private final String Private_Key="4bb6d1dfbafb64a681139d1586b6f1160d18159afd57c8c79136d7490630407c";
public <T> T extractClaim(String token, Function<Claims, T> resolver) {
Claims claims = extractAllClaims(token);
System.out.println(claims);
return resolver.apply(claims);
}
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public boolean isValid(String token, UserDetails user) {
String username = extractUsername(token);
return (username.equals(user.getUsername())) && !isTokenExpired(token);
}
private boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
private Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
private Claims extractAllClaims(String token) {
return
Jwts.parserBuilder()
.setSigningKey(getSigninKey())
.build()
.parseClaimsJws(token)
.getBody();
}
public String generateToken(User user) {
String token = Jwts
.builder()
.setSubject(user.getUsername())
.claim("id", user.getId()) // Add user ID as a claim
.claim("firstName", user.getName())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 24*60*60*1000 ))
.signWith(getSigninKey())
.compact();
return token;
}
private Key getSigninKey() {
byte[] keyBytes = Decoders.BASE64URL.decode(Private_Key);
return Keys.hmacShaKeyFor(keyBytes);
}
}
now create jwt filter in starting i forget to create the filter package so lets first create that package and inside that create jwt filter with name JwtAuthenticationFilter
JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserService userService;
public JwtAuthenticationFilter(JwtService jwtService, UserService userService) {
this.jwtService = jwtService;
this.userService = userService;
}
@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain)
throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if(authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request,response);
return;
}
String token = authHeader.substring(7);
String username = jwtService.extractUsername(token);
if(username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userService.loadUserByUsername(username);
if(jwtService.isValid(token, userDetails)) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
authToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
in AuthenticationController we are using AuthenticationResponse this is dto so lets create that also inside dto cerate this class
package com.example.Messenger.dto;
public class AuthenticationResponse {
private String token;
public AuthenticationResponse(String token) {
this.token = token;
}
public String getToken() {
return token;
}
}
So, we have mostly completed our main agenda, but it is still missing. Let's move forward with it, but before that, let's add the WebSocket dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
ater adding we have to configure the reduied congifartion for using this so lets add inside config lets add
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// Enable CORS for WebSocket connections
registry.addEndpoint("/ws")
.setAllowedOrigins("http://localhost:3000")
.withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
}
"I will explain everything separately, but currently, our main focus is on building the application. I will add some links at the end which will explain everything in more detail
@Slf4j
@RestController
@CrossOrigin(origins = "http://localhost:3000")
public class ChatController {
@Autowired
private MessageRepository messageRepository;
@Autowired
private SimpMessagingTemplate messagingTemplate; // Add this line
@MessageMapping("/chat")
public void sendMessage(MessageEntity message) {
message.setTimestamp(LocalDateTime.now());
messageRepository.save(message);
log.info(" I am inside send message");
log.error("something wrong");
// Send message to both sender and receiver
messagingTemplate.convertAndSend("/topic/messages/" + message.getReceiver(), message);
messagingTemplate.convertAndSend("/topic/messages/" + message.getSender(), message);
}
@GetMapping("/api/messages")
public List<MessageEntity> getMessages(
@RequestParam String sender,
@RequestParam String receiver) {
return messageRepository.findMessages(sender, receiver);
}
}
We are done with the backend part, so let's focus on the frontend. If you are familiar with React.js, that's great! If not, you should first learn the basics of React.js.
Let's get started with React.js.
- Open VS Code.
- Create a React application named chatapplicationfrontend (you can give it any name of your choice).
- After creating the application, we will import some extra dependencies like react-router and socket.io-client.
npm install react-router-dom
npm install react-use-websocket
Now, remove all the unnecessary things from App.js and add the routes, so that when a user visits a particular route, they are directed to the correct page.
import './App.css';
import Users from './Users';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import LoginPage from './LoginPage';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/users" element={<Users/>}/>
</Routes>
</BrowserRouter>
);
}
export default App;
lets create LoginPage.js and LoginPage.css like this
import React, { useState } from 'react';
import './LoginPage.css';
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
function LoginPage() {
const [userName, setUserName] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const nav=useNavigate();
const handleLogin = async () => {
try {
const response = await axios.post('http://localhost:8080/login', {
userName,
password,
});
if (response.data.token) {
// Save the JWT token in local storage
localStorage.setItem('token', response.data.token);
console.log('Login successful. Token saved to local storage.');
nav('/users');
} else {
setError('Login failed: Token not received.');
}
} catch (error) {
setError('Login failed: Invalid credentials or server error.');
console.error(error);
}
};
return (
<div className="login-container">
<h2>Login</h2>
{error && <p className="error-message">{error}</p>}
<div className="input-container">
<label>Username:</label>
<input
type="text"
value={userName}
onChange={(e) => setUserName(e.target.value)}
/>
</div>
<div className="input-container">
<label>Password:</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<button onClick={handleLogin} className="login-button">
Login
</button>
</div>
);
}
export default LoginPage;
.login-container {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 50px;
}
h2 {
margin-bottom: 20px;
}
.input-container {
width: 300px;
margin-bottom: 15px;
}
.input-container label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.input-container input {
width: 100%;
padding: 8px;
margin-top: 5px;
border-radius: 4px;
border: 1px solid #ccc;
}
.login-button {
width: 300px;
padding: 10px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.login-button:hover {
background-color: #45a049;
}
When you log in successfully, we want to show all the users present in the database except the current user. Let's add a Users.js page
import axios from 'axios';
import React, { useEffect, useState } from 'react';
import Chat from './Chat';
import { jwtDecode } from 'jwt-decode';
// Ensure this is the correct path to your Chat component
const Users = () => {
const [users, setUsers] = useState([]);
const [loggedInUserId, setLoggedInUserId] = useState(null);
const [selectedUser, setSelectedUser] = useState(null); // State to store selected user for chat
useEffect(() => {
// Decode the JWT token to get the logged-in user's ID
const token = localStorage.getItem('token');
if (token) {
const decodedToken = jwtDecode(token);
setLoggedInUserId(decodedToken.sub); // Assuming 'sub' holds the user ID
}
// Fetch all users
axios.get('http://localhost:8080/user')
.then((response) => {
setUsers(response.data);
})
.catch((error) => console.error(error));
}, []);
return (
<div>
{selectedUser ? (
<Chat user={selectedUser} onBack={() => setSelectedUser(null)} />
) : (
<div>
{users
.filter((person) => person.username !== loggedInUserId) // Exclude the logged-in user
.map((person) => (
<div key={person.id}>
<p>{person.name}</p>
<button onClick={() => setSelectedUser(person)}>Chat</button> {/* Set selected user on button click */}
</div>
))}
</div>
)}
</div>
);
};
export default Users;
so finally lets make chat Chat.js and understand everything
import React, { useState, useEffect, useRef } from 'react';
import SockJS from 'sockjs-client';
import { jwtDecode } from 'jwt-decode';
import { Stomp } from '@stomp/stompjs';
import './Chat.css'; // Import the external CSS file
const Chat = ({ user, onBack }) => {
const [messages, setMessages] = useState([]);
const [newMessage, setNewMessage] = useState("");
const stompClientRef = useRef(null);
const [currUser, setCurrUser] = useState("");
// Decode JWT to get current user username
useEffect(() => {
const token = localStorage.getItem("token");
if (token) {
try {
const decoded = jwtDecode(token);
setCurrUser(decoded.sub); // Assuming the username is stored in 'sub'
} catch (error) {
console.error("Error decoding token:", error);
}
}
}, []);
// Fetch previous messages between currUser and user
useEffect(() => {
const fetchMessages = async () => {
if (currUser && user.username) {
try {
const response = await fetch(`http://localhost:8080/api/messages?sender=${currUser}&receiver=${user.username}`);
if (!response.ok) throw new Error('Failed to fetch messages');
const data = await response.json();
const sortedMessages = data.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
setMessages(sortedMessages);
} catch (error) {
console.error('Error fetching messages:', error);
}
}
};
fetchMessages();
}, [currUser, user]);
// WebSocket connection setup
useEffect(() => {
const connect = () => {
console.log("trying to Connect");
const socket = new SockJS('http://localhost:8080/ws');
const stompClient = Stomp.over(socket);
stompClientRef.current = stompClient;
stompClient.connect({}, () => {
console.log('Connected to WebSocket');
// Subscribe to messages for the current user
stompClient.subscribe(`/topic/messages/${currUser}`, (message) => {
const receivedMessage = JSON.parse(message.body);
console.log('Received message:', receivedMessage);
// Check if the message is between the current user and the user
const isValidMessage =
(receivedMessage.receiver === currUser && receivedMessage.sender === user.username) ||
(receivedMessage.sender === currUser && receivedMessage.receiver === user.username);
if (isValidMessage) {
setMessages(prevMessages => {
const exists = prevMessages.some(msg => msg.id === receivedMessage.id);
return exists ? prevMessages : [...prevMessages, receivedMessage]; // Avoid duplicates
});
}
});
}, (error) => {
console.error('WebSocket connection error:', error);
});
};
if (currUser && user) {
connect();
}
// Cleanup on component unmount
return () => {
if (stompClientRef.current) {
stompClientRef.current.disconnect(() => {
console.log('Disconnected from WebSocket');
});
}
};
}, [currUser, user]);
// Handle sending messages
const handleSend = () => {
if (newMessage.trim() === "") return;
const messageObject = {
id: Date.now(), // Generate a temporary ID based on timestamp
sender: currUser,
receiver: user.username,
message: newMessage,
timestamp: new Date().toISOString()
};
if (stompClientRef.current && stompClientRef.current.connected) {
stompClientRef.current.send('/app/chat', {}, JSON.stringify(messageObject));
console.log('Sent message:', messageObject);
setMessages(prevMessages => {
const exists = prevMessages.some(msg => msg.timestamp === messageObject.timestamp);
return exists ? prevMessages : [...prevMessages, messageObject]; // Avoid duplicates
});
setNewMessage(""); // Clear input field
} else {
console.error('STOMP client is not connected');
}
};
return (
<div className="chat-screen">
<button onClick={onBack} className="back-button">Back</button>
<h2>Chat with {user.userName}</h2>
<div className="chat-messages">
{messages.map((message, index) => (
<div key={index} className={`message ${message.sender === currUser ? "me" : "them"}`}>
<strong>{message.sender}: </strong> {message.message}
<div>{new Date(message.timestamp).toLocaleTimeString()}</div>
</div>
))}
</div>
<div className="chat-input">
<input
type="text"
placeholder="Type a message..."
value={newMessage}
onChange={(e) => setNewMessage(e.target.value)}
/>
<button onClick={handleSend}>Send</button>
</div>
</div>
);
};
export default Chat;
lets understand by breaking our code
import React, { useState, useEffect, useRef } from 'react';
import SockJS from 'sockjs-client';
import { jwtDecode } from 'jwt-decode';
import { Stomp } from '@stomp/stompjs';
import './Chat.css'; // Import the external CSS file
React: For building the component.
useState, useEffect, useRef: React hooks for managing state, side effects, and references.
SockJS: A client-side library that provides WebSocket-like communication (used when WebSockets are not available).
jwt-decode: A library to decode JWT tokens.
Stomp.js: A JavaScript client for STOMP (Simple Text Oriented Messaging Protocol), which is used for WebSocket communication.
Chat.css: External CSS file for styling the chat component.
const [messages, setMessages] = useState([]);
const [newMessage, setNewMessage] = useState("");
const stompClientRef = useRef(null);
const [currUser, setCurrUser] = useState("");
messages: Holds an array of messages to display in the chat.
newMessage: Holds the value of the new message being typed by the user.
stompClientRef: A reference to the STOMP WebSocket client instance.
currUser: Holds the username of the current logged-in user.
useEffect(() => {
const token = localStorage.getItem("token");
if (token) {
try {
const decoded = jwtDecode(token);
setCurrUser(decoded.sub); // Assuming the username is stored in 'sub'
} catch (error) {
console.error("Error decoding token:", error);
}
}
}, []);
This useEffect hook runs once when the component mounts and decodes the JWT token from localStorage. It extracts the username (sub field) from the decoded token and stores it in currUser.
useEffect(() => {
const fetchMessages = async () => {
console.log("i am doing");
if (currUser && user.username) {
console.log("i am inside");
try {
const response = await fetch(`http://localhost:8080/api/messages?sender=${currUser}&receiver=${user.username}`);
if (!response.ok) throw new Error('Failed to fetch messages');
const data = await response.json();
const sortedMessages = data.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
setMessages(sortedMessages);
} catch (error) {
console.error('Error fetching messages:', error);
}
}
};
fetchMessages();
}, [currUser, user]);
This useEffect fetches previous messages between the current user (currUser) and the selected user (user.username) from an API.
It sorts the fetched messages by timestamp and stores them in the messages state.
This hook runs whenever currUser or user changes.
useEffect(() => {
const connect = () => {
console.log("trying to Connect");
const socket = new SockJS('http://localhost:8080/ws');
const stompClient = Stomp.over(socket);
stompClientRef.current = stompClient;
stompClient.connect({}, () => {
console.log('Connected to WebSocket');
// Subscribe to messages for the current user
stompClient.subscribe(`/topic/messages/${currUser}`, (message) => {
const receivedMessage = JSON.parse(message.body);
console.log('Received message:', receivedMessage);
// Check if the message is between the current user and the user
const isValidMessage =
(receivedMessage.receiver === currUser && receivedMessage.sender === user.username) ||
(receivedMessage.sender === currUser && receivedMessage.receiver === user.username);
if (isValidMessage) {
setMessages(prevMessages => {
const exists = prevMessages.some(msg => msg.id === receivedMessage.id);
return exists ? prevMessages : [...prevMessages, receivedMessage]; // Avoid duplicates
});
}
});
}, (error) => {
console.error('WebSocket connection error:', error);
});
};
if (currUser && user) {
connect();
}
// Cleanup on component unmount
return () => {
if (stompClientRef.current) {
stompClientRef.current.disconnect(() => {
console.log('Disconnected from WebSocket');
});
}
};
}, [currUser, user]);
This useEffect sets up a WebSocket connection using SockJS and STOMP.
When the connection is established, it subscribes to a topic (/topic/messages/{currUser}) to receive messages for the current user.
Whenever a new message is received, it checks if the message is between the current user and the selected user (user.username), and if so, adds it to the messages state.
The stompClientRef is used to manage the STOMP client instance.
const handleSend = () => {
if (newMessage.trim() === "") return;
const messageObject = {
id: Date.now(), // Generate a temporary ID based on timestamp
sender: currUser,
receiver: user.username,
message: newMessage,
timestamp: new Date().toISOString()
};
if (stompClientRef.current && stompClientRef.current.connected) {
stompClientRef.current.send('/app/chat', {}, JSON.stringify(messageObject));
console.log('Sent message:', messageObject);
setMessages(prevMessages => {
const exists = prevMessages.some(msg => msg.timestamp === messageObject.timestamp);
return exists ? prevMessages : [...prevMessages, messageObject]; // Avoid duplicates
});
setNewMessage(""); // Clear input field
} else {
console.error('STOMP client is not connected');
}
};
This function handles sending a message. It creates a messageObject with the necessary properties, such as the sender, receiver, message text, and timestamp.
It then sends the message via STOMP if the WebSocket connection is active.
After sending the message, it adds the new message to the messages state and clears the input field.
return (
<div className="chat-screen">
<button onClick={onBack} className="back-button">Back</button>
<h2>Chat with {user.userName}</h2>
<div className="chat-messages">
{messages.map((message, index) => (
<div key={index} className={`message ${message.sender === currUser ? "me" : "them"}`}>
<strong>{message.sender}: </strong> {message.message}
<div>{new Date(message.timestamp).toLocaleTimeString()}</div>
</div>
))}
</div>
<div className="chat-input">
<input
type="text"
placeholder="Type a message..."
value={newMessage}
onChange={(e) => setNewMessage(e.target.value)}
/>
<button onClick={handleSend}>Send</button>
</div>
</div>
);
The chat-screen displays the chat interface, which includes:
A Back button that triggers the onBack function (likely to go back to the previous screen).
A list of messages (chat-messages), where each message is displayed with the sender's name and the message content.
An input field to type new messages, and a Send button to send the message.
The messages are dynamically rendered using .map(), and the message sender is styled differently (me vs. them) for visual distinction.
Now we are done Just add Css and enjoy chatting
/* Chat screen container */
.chat-screen {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
background-color: #f9f9f9;
font-family: Arial, sans-serif;
height: 100vh;
box-sizing: border-box;
}
/* Back button */
.back-button {
align-self: flex-start;
margin-bottom: 20px;
padding: 10px 20px;
background-color: #007bff;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
.back-button:hover {
background-color: #0056b3;
}
/* Chat header */
h2 {
margin: 0;
margin-bottom: 20px;
color: #333;
}
/* Chat messages container */
.chat-messages {
width: 100%;
max-width: 600px;
height: 400px;
overflow-y: scroll;
border: 1px solid #ccc;
border-radius: 4px;
background-color: #fff;
padding: 10px;
margin-bottom: 10px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
/* Individual messages */
.message {
margin-bottom: 10px;
padding: 5px;
border-radius: 4px;
font-size: 14px;
}
.message.me {
background-color: #d1e7dd;
align-self: flex-end;
}
.message.them {
background-color: #f8d7da;
align-self: flex-start;
}
/* Timestamp for messages */
.message div {
font-size: 12px;
color: gray;
text-align: right;
}
/* Chat input container */
.chat-input {
display: flex;
width: 100%;
max-width: 600px;
align-items: center;
gap: 10px;
}
/* Input field */
.chat-input input {
flex: 1;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
}
/* Send button */
.chat-input button {
padding: 10px 20px;
background-color: #007bff;
color: #fff;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s;
}
.chat-input button:hover {
background-color: #0056b3;
}
Top comments (0)