Error when Implementing Conversation Creation Method Similar to OpenAI's ChatGPT: Cannot Read Properties of Null
Tags: react
Published: 2024-05-08 09:54:47


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 `id `of `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.



```js
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;

```

```js
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 };
};
```

```js
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;
```


```js
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 };
};
```

```js
import { create } from "zustand";

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

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

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

export default useMessage;
```