DEV Community

lccua
lccua

Posted on

Error when Implementing Conversation Creation Method Similar to OpenAI's ChatGPT: Cannot Read Properties of Null

I am attempting to implement the same conversation creation method that OpenAI's ChatGPT uses. This involves displaying an empty chat when a user clicks the button to start a new conversation. Only when the user enters a message should the conversation be created and stored in the database, with that message linked to the newly created conversation.

My code successfully creates the conversation in createConversation (I confirmed this by logging responseJson). However, I suspect that the conversation isn't instantly set by setSelectedConversation, which leads to an error when attempting to read the idof selectedConversation in sendMessage.

The error message reads:

ERROR Cannot read properties of null (reading 'id') TypeError: Cannot read properties of null (reading 'id') at sendMessage

The conversation eventually gets set, though. When the error occurs, no message is created or set, but the conversation always gets added to the existing conversations list. I confirmed this because the newly created conversation consistently appears in the sidebar after encountering the 'Cannot read properties of null' error.

I believe this issue may be related to asynchronous behavior, but I'm not entirely certain. If you have suggestions for a potentially better solution to achieve my goal, please let me know.

import React, { useState } from 'react';

import { useSendMessage } from '../../../hooks/message/useSendMessage';

import useMessage from '../../../zustand/useMessage';

import "./Chat.css"
import "./ToggleSwitch.css"
import { useCreateConversation } from '../../../hooks/conversation/useCreateConversastion.js';

const Input = () => {
  //state
  const [newMessage, setNewMessage] = useState('');
  const [isFuture, setIsFuture] = useState(false);

  //custom hooks
  const { isLoading, sendMessage } = useSendMessage();
  const { createConversation } = useCreateConversation();

  // const { setSelectedConversation } = useConversation();
  const { messages } = useMessage();



  const toggleSwitch = () => {
    setIsFuture(!isFuture);
  };

  const handleMessageChange = (e) => {
    setNewMessage(e.target.value);
  };

  const handleSendMessage = async (e) => {
    e.preventDefault();
    if (newMessage.trim() === "") return;

    if (messages.length === 0) {
      // If there's no conversation messages, create a new conversation
      await createConversation();
    }

    // Send the message
    await sendMessage(newMessage, isFuture);

    // Clear the input field and toggle state
    setNewMessage("");
    setIsFuture(!isFuture);
  };



  return (
    <div className="input-container">
      <label className="switch">
        <input type="checkbox" onChange={toggleSwitch} checked={isFuture} />
        <span className="slider"></span>
      </label>
      <div className="input-field">
        <input
          type="text"
          value={newMessage}
          onChange={handleMessageChange}
          placeholder="Type your message..."
        />
        <button onClick={handleSendMessage} disabled={!newMessage.trim()}>
          {isLoading ? "is sending..." : "Send"}
        </button>
      </div>
    </div>
  );
};

export default Input;

Enter fullscreen mode Exit fullscreen mode
import { useState } from "react";

import useAuthContext from "../context/useAuthContext";
import useConversation from "../../zustand/useConversation";


export const useCreateConversation = () => {
  //loading and error state
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  //context
  const { user } = useAuthContext();
  const { setNewConversation, setSelectedConversation } = useConversation();

  const createConversation = async () => {
    setIsLoading(true);
    setError(null);

    const response = await fetch('/api/conversations', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${user.token}`
      }
    });
    const responseJson = await response.json();

    if (!response.ok) {
      setIsLoading(false);
      setError(responseJson.error);
    }

    if (response.ok) {
      setNewConversation(responseJson)
      setSelectedConversation(responseJson)
      setIsLoading(false);
    }
  };

  return { isLoading, error, createConversation };
};
Enter fullscreen mode Exit fullscreen mode
import { create } from "zustand";

const useConversation = create((set) => ({

  selectedConversation: null,
  setSelectedConversation: (selectedConversation) => set({ selectedConversation }),

  conversations: [],
  setConversations: (conversations) => set({ conversations }),

  setNewConversation: (newConversation) => {
    set((state) => ({
      conversations: [...state.conversations, newConversation],
    }));
  },

}));

export default useConversation;
Enter fullscreen mode Exit fullscreen mode
import { useState } from "react";
import useAuthContext from "../context/useAuthContext";
import useMessage from "../../zustand/useMessage";
import useConversation from "../../zustand/useConversation";

export const useSendMessage = () => {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  const { user } = useAuthContext();  
  const { setNewMessage } = useMessage();
  const { selectedConversation } = useConversation();


  const sendMessage = async (messageContent, isFuture) => {
    setIsLoading(true);
    setError(null);

    // data that will be passed to the POST body
    const messageData = {conversationId: selectedConversation.id, messageContent, isFuture}

    const response = await fetch('/api/messages', {
      method: 'POST',
      body: JSON.stringify(messageData),
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${user.token}`
      },
    })

    const responseJson = await response.json()

    if (!response.ok) {
      setIsLoading(false);
      setError(responseJson.error);
    }

    if (response.ok) {
      setNewMessage(responseJson)
      setIsLoading(false);
    }
  };

  return { isLoading, error, sendMessage };
};
Enter fullscreen mode Exit fullscreen mode
import { create } from "zustand";

const useMessage = create((set) => ({

  messages: [],
  setMessages: (messages) => set({ messages }),

  setNewMessage: (newMessage) => {
    set((state) => ({
      messages: [...state.messages, newMessage],
    }));
  },

}));

export default useMessage;
Enter fullscreen mode Exit fullscreen mode

Top comments (0)