Skip to content

Instantly share code, notes, and snippets.

@Slyracoon23
Created May 14, 2024 15:53
Show Gist options
  • Save Slyracoon23/b1b3f5225ad56e2a84d00e2efd2b3c64 to your computer and use it in GitHub Desktop.
Save Slyracoon23/b1b3f5225ad56e2a84d00e2efd2b3c64 to your computer and use it in GitHub Desktop.
Bug: Thread details not updating correctly in Context Provider
"use client"
// contexts/MainContext.tsx
import React, { createContext, use, useContext, useEffect, useState } from "react"
import { useParams, usePathname, useSearchParams } from "next/navigation"
import { useThreadDetail } from "@/lib_v2/frontend/provider/hooks/use-thread-detail"
import { useThreads } from "@/lib_v2/frontend/provider/hooks/use-threads"
import { useAiAction } from "../hooks/use-ai-action"
import { useThreadActions } from "../hooks/use-thread-ai-action"
import { useThreadMessages } from "../hooks/use-thread-messages"
import { useOrgActions } from "../hooks/use-workspace-ai-action"
import { fetchThreadDetails } from "../services/thread-detail"
interface MainContextState {
threads: {
threads: any
threadsIsLoading: boolean
threadsError: any
}
threadMessages: {
threadMessages: any
threadMessagesIsLoading: boolean
threadMessagesError: any
}
threadDetail: {
threadDetails: any
threadDetailsIsLoading: boolean
threadDetailsError: any
}
orgActions: {
orgActions: any
orgActionsIsLoading: boolean
orgActionsError: string | null
}
threadActions: {
threadActions: any
threadActionsIsLoading: boolean
threadActionsError: string | null
}
actionData: {
actionData: any
actionDataIsLoading: boolean
actionDataError: string | null
}
error: Error | null
setError: React.Dispatch<React.SetStateAction<Error | null>>
}
const MainContext = createContext<MainContextState | undefined>(undefined)
const extractIdFromUrl = (pathname: string | null) => {
if (!pathname) return null
const paths = pathname.split("/").filter(Boolean)
if (paths.includes("thread")) {
// URL contains thread ID
const threadIndex = paths.indexOf("thread")
return paths[threadIndex + 1]
} else if (paths.includes("task")) {
// URL contains action ID
const actionIndex = paths.indexOf("task")
return paths[actionIndex + 1]
}
return null
}
export const MainProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [error, setError] = useState<Error | null>(null)
const pathname = usePathname()
const searchParams = useSearchParams()
// Extract org_id from the pathname
const paths = pathname?.split("/").filter(Boolean) || []
const org_id = paths[1]
// Extract thread_id or action_id from the pathname
const id = extractIdFromUrl(pathname)
const actionId = extractIdFromUrl(pathname)
const { threadDetails, threadDetailsError, threadDetailsIsLoading } =
useThreadDetail(id as string)
const { threads, threadsError, threadsIsLoading } = useThreads(
org_id as string
)
const { orgActions, orgActionsIsLoading, orgActionsError } = useOrgActions(
org_id as string
)
const { threadActions, threadActionsIsLoading, threadActionsError } =
useThreadActions(id as string, org_id as string)
const { threadMessages, threadMessagesError, threadMessagesIsLoading } =
useThreadMessages(org_id as string, id as string)
const { actionData, actionDataIsLoading, actionDataError } = useAiAction(
actionId as string,
org_id as string
)
// Log threadDetails state whenever it is updated
useEffect(() => {
console.log("threadDetails updated in MainProvider:", threadDetails)
}, [threadDetails])
useEffect(() => {
console.log("threads updated in MainProvider:", threads)
}, [threads])
const value = {
threads: {
threads,
threadsIsLoading,
threadsError,
},
threadMessages: {
threadMessages,
threadMessagesError,
threadMessagesIsLoading,
},
threadDetail: {
threadDetails,
threadDetailsError,
threadDetailsIsLoading,
},
orgActions: {
orgActions,
orgActionsIsLoading,
orgActionsError,
},
threadActions: {
threadActions,
threadActionsIsLoading,
threadActionsError,
},
actionData: {
actionData,
actionDataIsLoading,
actionDataError,
},
error,
setError,
}
return <MainContext.Provider value={value}>{children}</MainContext.Provider>
}
export const useMain = () => {
const context = useContext(MainContext)
if (!context) {
throw new Error("useMain must be used within a MainProvider")
}
return context
}
import { useEffect, useState } from "react"
import { createClientComponentClient } from "@supabase/auth-helpers-nextjs"
import { useMutation } from "@tanstack/react-query"
import { set } from "date-fns"
import { usePathname } from "next/navigation"
interface UpdateParams {
threadId: string
newStatus?: string
newPriority?: string
newAssignee?: string
userId: string
orgId: number
}
interface ThreadDetails {
id: string // Add id field
source_thread_id: string
status?: string // Add status field
// Add other properties of the thread details object
}
const supabase = createClientComponentClient()
export const useThreadDetail = (threadId: string) => {
const [threadDetails, setThreadDetails] = useState<ThreadDetails | null>(null)
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const pathname = usePathname()
// Extract org_id from the pathname
const paths = pathname?.split("/").filter(Boolean) || []
const orgId = paths[1]
useEffect(() => {
console.log("threadDetails updated in Hook:", threadDetails)
}, [threadDetails])
// const updateMetadata = async (params: UpdateParams) => {
// try {
// const response = await fetch(
// `/api/v2/orgs/${orgId}/threads/metadata/${threadId}`,
// {
// method: "PUT",
// headers: {
// "Content-Type": "application/json",
// },
// body: JSON.stringify(params),
// }
// );
// if (!response.ok) {
// throw new Error("Failed to update thread metadata");
// }
// const data = await response.json();
// return data;
// } catch (error) {
// console.error("Failed to update thread metadata:", error);
// throw error;
// }
// };
// const { mutate: updateThreadMetadata } = useMutation({
// mutationFn: updateMetadata,
// });
const fetchThreadDetails = async () => {
try {
setIsLoading(true)
const response = await fetch(
`/api/v2/orgs/${orgId}/threads/metadata/${threadId}`
)
if (!response.ok) {
throw new Error("Failed to fetch thread")
}
const data = await response.json()
console.log("setting thread details", data)
// debugger;
setThreadDetails(data)
} catch (error) {
console.error("Failed to fetch thread:", error)
setError(error.message)
} finally {
setIsLoading(false)
}
}
useEffect(() => {
console.log("fetching thread details", threadDetails)
fetchThreadDetails()
}, [threadId])
useEffect(() => {
const channel = supabase
.channel(`thread_metadata:id=eq.${threadId}:update`)
.on(
"postgres_changes",
{
event: "UPDATE",
schema: "public",
table: "thread_metadata",
filter: `id=eq.${threadId}`,
},
(payload) => {
console.log("thread metadata update", payload)
// debugger;
// const new_data = {
// id: payload.new.id,
// source_thread_id: payload.new.source_thread_id,
// status: payload.new.status,
// }
setThreadDetails(payload.new)
// debugger;
// fetchThreadDetails();
}
)
.subscribe()
return () => {
supabase.removeChannel(channel)
}
}, [threadId])
return {
threadDetails,
threadDetailsError: error,
threadDetailsIsLoading: isLoading,
// updateThreadMetadata,
}
}
@HoseinKhanBeigi
Copy link

i think To ensure that the Context Provider correctly re-renders when the data fetched by the custom hook updates, regardless of the number of arguments passed to the hook, we need to ensure the custom hook correctly handles multiple arguments and that the Context Provider correctly consumes this data.

Steps to Diagnose and Resolve the Issue
Ensure Dependencies Are Correctly Managed in the Custom Hook:
Make sure the dependencies in the custom hook are properly defined so that the hook re-fetches the data when any of the arguments change.

Verify the Context Provider Uses the Updated Data Correctly:
Ensure that the Context Provider is correctly using the data from the custom hook and re-renders when the data changes.

Refactor the Custom Hook:
Refactor the custom hook to use multiple arguments and ensure it correctly updates the data.

import { useEffect, useState } from "react";
import { createClientComponentClient } from "@supabase/auth-helpers-nextjs";
import { usePathname } from "next/navigation";

interface ThreadDetails {
  id: string;
  source_thread_id: string;
  status?: string;
  // Add other properties of the thread details object
}

const supabase = createClientComponentClient();

export const useThreadDetail = (orgId: string, threadId: string) => {
  const [threadDetails, setThreadDetails] = useState<ThreadDetails | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const fetchThreadDetails = async () => {
      try {
        setIsLoading(true);
        const response = await fetch(`/api/v2/orgs/${orgId}/threads/metadata/${threadId}`);
        if (!response.ok) {
          throw new Error("Failed to fetch thread");
        }
        const data = await response.json();
        setThreadDetails(data);
      } catch (error) {
        console.error("Failed to fetch thread:", error);
        setError(error.message);
      } finally {
        setIsLoading(false);
      }
    };

    fetchThreadDetails();
  }, [orgId, threadId]);

  useEffect(() => {
    const channel = supabase
      .channel(`thread_metadata:id=eq.${threadId}:update`)
      .on(
        "postgres_changes",
        {
          event: "UPDATE",
          schema: "public",
          table: "thread_metadata",
          filter: `id=eq.${threadId}`,
        },
        (payload) => {
          setThreadDetails(payload.new);
        }
      )
      .subscribe();

    return () => {
      supabase.removeChannel(channel);
    };
  }, [threadId]);

  return {
    threadDetails,
    threadDetailsError: error,
    threadDetailsIsLoading: isLoading,
  };
};

Here’s the updated MainProvider to use the custom hook with multiple arguments:


import React, { createContext, useContext, useEffect, useState } from "react";
import { useParams, usePathname, useSearchParams } from "next/navigation";
import { useThreadDetail } from "@/lib_v2/frontend/provider/hooks/use-thread-detail";
import { useThreads } from "@/lib_v2/frontend/provider/hooks/use-threads";
import { useAiAction } from "../hooks/use-ai-action";
import { useThreadActions } from "../hooks/use-thread-ai-action";
import { useThreadMessages } from "../hooks/use-thread-messages";
import { useOrgActions } from "../hooks/use-workspace-ai-action";

interface MainContextState {
  threads: {
    threads: any;
    threadsIsLoading: boolean;
    threadsError: any;
  };
  threadMessages: {
    threadMessages: any;
    threadMessagesIsLoading: boolean;
    threadMessagesError: any;
  };
  threadDetail: {
    threadDetails: any;
    threadDetailsIsLoading: boolean;
    threadDetailsError: any;
  };
  orgActions: {
    orgActions: any;
    orgActionsIsLoading: boolean;
    orgActionsError: string | null;
  };
  threadActions: {
    threadActions: any;
    threadActionsIsLoading: boolean;
    threadActionsError: string | null;
  };
  actionData: {
    actionData: any;
    actionDataIsLoading: boolean;
    actionDataError: string | null;
  };
  error: Error | null;
  setError: React.Dispatch<React.SetStateAction<Error | null>>;
}

const MainContext = createContext<MainContextState | undefined>(undefined);

const extractIdFromUrl = (pathname: string | null) => {
  if (!pathname) return null;

  const paths = pathname.split("/").filter(Boolean);

  if (paths.includes("thread")) {
    // URL contains thread ID
    const threadIndex = paths.indexOf("thread");
    return paths[threadIndex + 1];
  } else if (paths.includes("task")) {
    // URL contains action ID
    const actionIndex = paths.indexOf("task");
    return paths[actionIndex + 1];
  }

  return null;
};

export const MainProvider: React.FC<{ children: React.ReactNode }> = ({
  children,
}) => {
  const [error, setError] = useState<Error | null>(null);
  const pathname = usePathname();
  const searchParams = useSearchParams();

  // Extract org_id from the pathname
  const paths = pathname?.split("/").filter(Boolean) || [];
  const org_id = paths[1];

  // Extract thread_id or action_id from the pathname
  const id = extractIdFromUrl(pathname);
  const actionId = extractIdFromUrl(pathname);

  const { threadDetails, threadDetailsError, threadDetailsIsLoading } =
    useThreadDetail(org_id as string, id as string);

  const { threads, threadsError, threadsIsLoading } = useThreads(org_id as string);
  const { orgActions, orgActionsIsLoading, orgActionsError } = useOrgActions(org_id as string);
  const { threadActions, threadActionsIsLoading, threadActionsError } =
    useThreadActions(id as string, org_id as string);
  const { threadMessages, threadMessagesError, threadMessagesIsLoading } =
    useThreadMessages(org_id as string, id as string);

  const { actionData, actionDataIsLoading, actionDataError } = useAiAction(
    actionId as string,
    org_id as string
  );

  useEffect(() => {
    console.log("threadDetails updated in MainProvider:", threadDetails);
  }, [threadDetails]);

  useEffect(() => {
    console.log("threads updated in MainProvider:", threads);
  }, [threads]);

  const value = {
    threads: {
      threads,
      threadsIsLoading,
      threadsError,
    },
    threadMessages: {
      threadMessages,
      threadMessagesError,
      threadMessagesIsLoading,
    },
    threadDetail: {
      threadDetails,
      threadDetailsError,
      threadDetailsIsLoading,
    },
    orgActions: {
      orgActions,
      orgActionsIsLoading,
      orgActionsError,
    },
    threadActions: {
      threadActions,
      threadActionsIsLoading,
      threadActionsError,
    },
    actionData: {
      actionData,
      actionDataIsLoading,
      actionDataError,
    },
    error,
    setError,
  };

  return <MainContext.Provider value={value}>{children}</MainContext.Provider>;
};

export const useMain = () => {
  const context = useContext(MainContext);
  if (!context) {
    throw new Error("useMain must be used within a MainProvider");
  }
  return context;
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment