@planningcenter/chat-react-native 3.17.0 → 3.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"api_provider.d.ts","sourceRoot":"","sources":["../../src/contexts/api_provider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,WAAW,EAIZ,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAwC,MAAM,OAAO,CAAA;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAkBxC,eAAO,MAAM,eAAe,aAO1B,CAAA;AAEF,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,EAAE,SAAS,qBAkBlD;AAcD,wBAAgB,eAAe,WAQ9B"}
1
+ {"version":3,"file":"api_provider.d.ts","sourceRoot":"","sources":["../../src/contexts/api_provider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,WAAW,EAIZ,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAwC,MAAM,OAAO,CAAA;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAkBxC,eAAO,MAAM,eAAe,aAO1B,CAAA;AAEF,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,EAAE,SAAS,qBAsBlD;AAcD,wBAAgB,eAAe,WAQ9B"}
@@ -23,12 +23,15 @@ export const chatQueryClient = new QueryClient({
23
23
  export function ApiProvider({ children }) {
24
24
  const { token, env } = useContext(ChatContext);
25
25
  const sessionChanged = useSessionChanged({ token, env });
26
- apiClient = useApiClient();
26
+ const client = useApiClient();
27
27
  useEffect(() => {
28
28
  if (!sessionChanged)
29
29
  return;
30
30
  chatQueryClient.clear();
31
31
  }, [sessionChanged]);
32
+ useEffect(() => {
33
+ apiClient = client;
34
+ }, [client]);
32
35
  return (<QueryClientProvider client={chatQueryClient}>
33
36
  <PrefetchQueries />
34
37
  {children}
@@ -1 +1 @@
1
- {"version":3,"file":"api_provider.js","sourceRoot":"","sources":["../../src/contexts/api_provider.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n focusManager,\n QueryClient,\n QueryClientProvider,\n QueryKey,\n usePrefetchQuery,\n} from '@tanstack/react-query'\nimport React, { useContext, useEffect, useRef } from 'react'\nimport { ViewProps } from 'react-native'\nimport { ChatContext, ChatContextValue } from './chat_context'\nimport { appGrantsRequestArgs, getRequestQueryKey, RequestQueryKey } from '../hooks'\nimport { ApiClient, useApiClient } from '../hooks/use_api_client'\nimport { useAppState } from '../hooks/use_app_state'\n\nlet apiClient: ApiClient | undefined\n\nconst defaultQueryFn = ({ queryKey }: { queryKey: QueryKey }) => {\n if (!apiClient) {\n throw new Error('No token present')\n }\n\n const [url, data, headers, app = 'chat'] = queryKey as RequestQueryKey\n\n return apiClient[app].get({ url, data, headers })\n}\n\nexport const chatQueryClient = new QueryClient({\n defaultOptions: {\n queries: {\n queryFn: defaultQueryFn,\n retry: 3,\n },\n },\n})\n\nexport function ApiProvider({ children }: ViewProps) {\n const { token, env } = useContext(ChatContext)\n const sessionChanged = useSessionChanged({ token, env })\n\n apiClient = useApiClient()\n\n useEffect(() => {\n if (!sessionChanged) return\n\n chatQueryClient.clear()\n }, [sessionChanged])\n\n return (\n <QueryClientProvider client={chatQueryClient}>\n <PrefetchQueries />\n {children}\n </QueryClientProvider>\n )\n}\n\n// Component to prefetch queries when the app is focused\n// This needs to live in the provider so that it can access the api client\n// and the chat query client\nconst PrefetchQueries = () => {\n usePrefetchQuery({\n queryKey: getRequestQueryKey(appGrantsRequestArgs),\n queryFn: defaultQueryFn,\n })\n\n return null\n}\n\nexport function useFocusManager() {\n const appState = useAppState()\n\n useEffect(() => {\n focusManager.setFocused(appState === 'active')\n }, [appState])\n\n return appState\n}\n\nfunction useSessionChanged(value: Pick<ChatContextValue, 'token' | 'env'>): boolean {\n const { token: newToken, env: newEnv } = value\n const { token: prevToken, env: prevEnv } = usePrevious<typeof value>(value)\n\n return Boolean(prevToken && newToken !== prevToken) || Boolean(prevEnv && newEnv !== prevEnv)\n}\n\nfunction usePrevious<T>(value: T): T {\n const ref = useRef<T>(value)\n\n useEffect(() => {\n ref.current = value\n }, [value])\n\n return ref.current\n}\n"]}
1
+ {"version":3,"file":"api_provider.js","sourceRoot":"","sources":["../../src/contexts/api_provider.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,WAAW,EACX,mBAAmB,EAEnB,gBAAgB,GACjB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAK,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAE5D,OAAO,EAAE,WAAW,EAAoB,MAAM,gBAAgB,CAAA;AAC9D,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAmB,MAAM,UAAU,CAAA;AACpF,OAAO,EAAa,YAAY,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAEpD,IAAI,SAAgC,CAAA;AAEpC,MAAM,cAAc,GAAG,CAAC,EAAE,QAAQ,EAA0B,EAAE,EAAE;IAC9D,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAA;IACrC,CAAC;IAED,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,GAAG,MAAM,CAAC,GAAG,QAA2B,CAAA;IAEtE,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAA;AACnD,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,WAAW,CAAC;IAC7C,cAAc,EAAE;QACd,OAAO,EAAE;YACP,OAAO,EAAE,cAAc;YACvB,KAAK,EAAE,CAAC;SACT;KACF;CACF,CAAC,CAAA;AAEF,MAAM,UAAU,WAAW,CAAC,EAAE,QAAQ,EAAa;IACjD,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,UAAU,CAAC,WAAW,CAAC,CAAA;IAC9C,MAAM,cAAc,GAAG,iBAAiB,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAA;IAExD,MAAM,MAAM,GAAG,YAAY,EAAE,CAAA;IAE7B,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,cAAc;YAAE,OAAM;QAE3B,eAAe,CAAC,KAAK,EAAE,CAAA;IACzB,CAAC,EAAE,CAAC,cAAc,CAAC,CAAC,CAAA;IAEpB,SAAS,CAAC,GAAG,EAAE;QACb,SAAS,GAAG,MAAM,CAAA;IACpB,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IAEZ,OAAO,CACL,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAC3C;MAAA,CAAC,eAAe,CAAC,AAAD,EAChB;MAAA,CAAC,QAAQ,CACX;IAAA,EAAE,mBAAmB,CAAC,CACvB,CAAA;AACH,CAAC;AAED,wDAAwD;AACxD,0EAA0E;AAC1E,4BAA4B;AAC5B,MAAM,eAAe,GAAG,GAAG,EAAE;IAC3B,gBAAgB,CAAC;QACf,QAAQ,EAAE,kBAAkB,CAAC,oBAAoB,CAAC;QAClD,OAAO,EAAE,cAAc;KACxB,CAAC,CAAA;IAEF,OAAO,IAAI,CAAA;AACb,CAAC,CAAA;AAED,MAAM,UAAU,eAAe;IAC7B,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;IAE9B,SAAS,CAAC,GAAG,EAAE;QACb,YAAY,CAAC,UAAU,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAA;IAChD,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;IAEd,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,SAAS,iBAAiB,CAAC,KAA8C;IACvE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,KAAK,CAAA;IAC9C,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,WAAW,CAAe,KAAK,CAAC,CAAA;IAE3E,OAAO,OAAO,CAAC,SAAS,IAAI,QAAQ,KAAK,SAAS,CAAC,IAAI,OAAO,CAAC,OAAO,IAAI,MAAM,KAAK,OAAO,CAAC,CAAA;AAC/F,CAAC;AAED,SAAS,WAAW,CAAI,KAAQ;IAC9B,MAAM,GAAG,GAAG,MAAM,CAAI,KAAK,CAAC,CAAA;IAE5B,SAAS,CAAC,GAAG,EAAE;QACb,GAAG,CAAC,OAAO,GAAG,KAAK,CAAA;IACrB,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;IAEX,OAAO,GAAG,CAAC,OAAO,CAAA;AACpB,CAAC","sourcesContent":["import {\n focusManager,\n QueryClient,\n QueryClientProvider,\n QueryKey,\n usePrefetchQuery,\n} from '@tanstack/react-query'\nimport React, { useContext, useEffect, useRef } from 'react'\nimport { ViewProps } from 'react-native'\nimport { ChatContext, ChatContextValue } from './chat_context'\nimport { appGrantsRequestArgs, getRequestQueryKey, RequestQueryKey } from '../hooks'\nimport { ApiClient, useApiClient } from '../hooks/use_api_client'\nimport { useAppState } from '../hooks/use_app_state'\n\nlet apiClient: ApiClient | undefined\n\nconst defaultQueryFn = ({ queryKey }: { queryKey: QueryKey }) => {\n if (!apiClient) {\n throw new Error('No token present')\n }\n\n const [url, data, headers, app = 'chat'] = queryKey as RequestQueryKey\n\n return apiClient[app].get({ url, data, headers })\n}\n\nexport const chatQueryClient = new QueryClient({\n defaultOptions: {\n queries: {\n queryFn: defaultQueryFn,\n retry: 3,\n },\n },\n})\n\nexport function ApiProvider({ children }: ViewProps) {\n const { token, env } = useContext(ChatContext)\n const sessionChanged = useSessionChanged({ token, env })\n\n const client = useApiClient()\n\n useEffect(() => {\n if (!sessionChanged) return\n\n chatQueryClient.clear()\n }, [sessionChanged])\n\n useEffect(() => {\n apiClient = client\n }, [client])\n\n return (\n <QueryClientProvider client={chatQueryClient}>\n <PrefetchQueries />\n {children}\n </QueryClientProvider>\n )\n}\n\n// Component to prefetch queries when the app is focused\n// This needs to live in the provider so that it can access the api client\n// and the chat query client\nconst PrefetchQueries = () => {\n usePrefetchQuery({\n queryKey: getRequestQueryKey(appGrantsRequestArgs),\n queryFn: defaultQueryFn,\n })\n\n return null\n}\n\nexport function useFocusManager() {\n const appState = useAppState()\n\n useEffect(() => {\n focusManager.setFocused(appState === 'active')\n }, [appState])\n\n return appState\n}\n\nfunction useSessionChanged(value: Pick<ChatContextValue, 'token' | 'env'>): boolean {\n const { token: newToken, env: newEnv } = value\n const { token: prevToken, env: prevEnv } = usePrevious<typeof value>(value)\n\n return Boolean(prevToken && newToken !== prevToken) || Boolean(prevEnv && newEnv !== prevEnv)\n}\n\nfunction usePrevious<T>(value: T): T {\n const ref = useRef<T>(value)\n\n useEffect(() => {\n ref.current = value\n }, [value])\n\n return ref.current\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"use_message_draft.d.ts","sourceRoot":"","sources":["../../src/hooks/use_message_draft.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gEAAgE,CAAA;AAEpG,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,cAAc,EAAE,CAAA;IAC7B,WAAW,EAAE,MAAM,CAAA;CACpB;AAQD,wBAAgB,eAAe,CAAC,cAAc,EAAE,MAAM;;sBAyC3C,MAAM,eAAe,cAAc,EAAE;;EAkD/C"}
1
+ {"version":3,"file":"use_message_draft.d.ts","sourceRoot":"","sources":["../../src/hooks/use_message_draft.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gEAAgE,CAAA;AAEpG,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,cAAc,EAAE,CAAA;IAC7B,WAAW,EAAE,MAAM,CAAA;CACpB;AAQD,wBAAgB,eAAe,CAAC,cAAc,EAAE,MAAM;;sBAmD3C,MAAM,eAAe,cAAc,EAAE;;EAkD/C"}
@@ -1,4 +1,4 @@
1
- import { useCallback, useEffect, useState } from 'react';
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
2
  import { useAsyncStorage } from './use_async_storage';
3
3
  const DRAFT_STORAGE_KEY = 'chat_message_drafts';
4
4
  const DRAFT_EXPIRY_HOURS = 720; // 30 days
@@ -13,16 +13,23 @@ export function useMessageDraft(conversationId) {
13
13
  // Filter expired attachments
14
14
  if (storedDraft?.attachments) {
15
15
  const now = Date.now();
16
- storedDraft.attachments = storedDraft.attachments.filter(attachment => {
16
+ const validAttachments = storedDraft.attachments.filter(attachment => {
17
17
  if (!attachment.uploadedAt)
18
18
  return false;
19
19
  return now - attachment.uploadedAt < ATTACHMENT_EXPIRY_MS;
20
20
  });
21
+ return {
22
+ ...storedDraft,
23
+ attachments: validAttachments,
24
+ };
21
25
  }
22
26
  return storedDraft;
23
27
  });
24
- // Clean up expired drafts on mount
28
+ // Clean up expired drafts only once on mount - React 18 recommended pattern
29
+ const hasCleanedUp = useRef(false);
25
30
  useEffect(() => {
31
+ if (hasCleanedUp.current)
32
+ return;
26
33
  const now = Date.now();
27
34
  const validDrafts = {};
28
35
  Object.entries(allDrafts).forEach(([id, draftData]) => {
@@ -34,8 +41,8 @@ export function useMessageDraft(conversationId) {
34
41
  if (Object.keys(validDrafts).length !== Object.keys(allDrafts).length) {
35
42
  setAllDrafts(validDrafts);
36
43
  }
37
- // eslint-disable-next-line react-hooks/exhaustive-deps
38
- }, []); // I only want this to run on mount
44
+ hasCleanedUp.current = true;
45
+ }, [allDrafts, setAllDrafts]);
39
46
  const saveDraft = useCallback((text, attachments) => {
40
47
  const hasContent = text.trim().length > 0 || attachments.length > 0;
41
48
  const updatedDrafts = { ...allDrafts };
@@ -1 +1 @@
1
- {"version":3,"file":"use_message_draft.js","sourceRoot":"","sources":["../../src/hooks/use_message_draft.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useState } from 'react'\nimport { useAsyncStorage } from './use_async_storage'\nimport type { FileAttachment } from '../types/resources/denormalized_attachment_resource_for_create'\n\ninterface MessageDraft {\n text: string\n attachments: FileAttachment[]\n lastUpdated: number\n}\n\nconst DRAFT_STORAGE_KEY = 'chat_message_drafts'\nconst DRAFT_EXPIRY_HOURS = 720 // 30 days\nconst DRAFT_EXPIRY_MS = DRAFT_EXPIRY_HOURS * 60 * 60 * 1000\nconst ATTACHMENT_EXPIRY_HOURS = 48 // 2 days\nconst ATTACHMENT_EXPIRY_MS = ATTACHMENT_EXPIRY_HOURS * 60 * 60 * 1000\n\nexport function useMessageDraft(conversationId: number) {\n const conversationKey = conversationId.toString()\n const [allDrafts, setAllDrafts] = useAsyncStorage<Record<string, MessageDraft>>(\n DRAFT_STORAGE_KEY,\n {}\n )\n\n const [draft, setDraft] = useState<MessageDraft | null>(() => {\n const storedDraft = allDrafts[conversationKey] || null\n\n // Filter expired attachments\n if (storedDraft?.attachments) {\n const now = Date.now()\n storedDraft.attachments = storedDraft.attachments.filter(attachment => {\n if (!attachment.uploadedAt) return false\n return now - attachment.uploadedAt < ATTACHMENT_EXPIRY_MS\n })\n }\n\n return storedDraft\n })\n\n // Clean up expired drafts on mount\n useEffect(() => {\n const now = Date.now()\n const validDrafts: Record<string, MessageDraft> = {}\n\n Object.entries(allDrafts).forEach(([id, draftData]) => {\n if (draftData?.lastUpdated && now - draftData.lastUpdated < DRAFT_EXPIRY_MS) {\n validDrafts[id] = draftData\n }\n })\n\n // If we filtered out any drafts, update storage\n if (Object.keys(validDrafts).length !== Object.keys(allDrafts).length) {\n setAllDrafts(validDrafts)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []) // I only want this to run on mount\n\n const saveDraft = useCallback(\n (text: string, attachments: FileAttachment[]) => {\n const hasContent = text.trim().length > 0 || attachments.length > 0\n const updatedDrafts = { ...allDrafts }\n\n if (hasContent) {\n // Convert attachments to serializable format (similar to web implementation)\n const normalizedAttachments = attachments\n .filter(att => att.id && att.status === 'success')\n .map(att => ({\n ...att,\n file: {\n uri: att.file.uri,\n name: att.file.name,\n type: att.file.type,\n size: att.file.size,\n width: att.file.width,\n height: att.file.height,\n },\n }))\n\n const newDraft: MessageDraft = {\n text,\n attachments: normalizedAttachments,\n lastUpdated: Date.now(),\n }\n\n updatedDrafts[conversationKey] = newDraft\n setDraft(newDraft)\n } else {\n delete updatedDrafts[conversationKey]\n setDraft(null)\n }\n\n setAllDrafts(updatedDrafts)\n },\n [conversationKey, allDrafts, setAllDrafts]\n )\n\n const clearDraft = useCallback(() => {\n const updatedDrafts = { ...allDrafts }\n delete updatedDrafts[conversationKey]\n setAllDrafts(updatedDrafts)\n setDraft(null)\n }, [conversationKey, allDrafts, setAllDrafts])\n\n return {\n draft,\n saveDraft,\n clearDraft,\n }\n}\n"]}
1
+ {"version":3,"file":"use_message_draft.js","sourceRoot":"","sources":["../../src/hooks/use_message_draft.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react'\nimport { useAsyncStorage } from './use_async_storage'\nimport type { FileAttachment } from '../types/resources/denormalized_attachment_resource_for_create'\n\ninterface MessageDraft {\n text: string\n attachments: FileAttachment[]\n lastUpdated: number\n}\n\nconst DRAFT_STORAGE_KEY = 'chat_message_drafts'\nconst DRAFT_EXPIRY_HOURS = 720 // 30 days\nconst DRAFT_EXPIRY_MS = DRAFT_EXPIRY_HOURS * 60 * 60 * 1000\nconst ATTACHMENT_EXPIRY_HOURS = 48 // 2 days\nconst ATTACHMENT_EXPIRY_MS = ATTACHMENT_EXPIRY_HOURS * 60 * 60 * 1000\n\nexport function useMessageDraft(conversationId: number) {\n const conversationKey = conversationId.toString()\n const [allDrafts, setAllDrafts] = useAsyncStorage<Record<string, MessageDraft>>(\n DRAFT_STORAGE_KEY,\n {}\n )\n\n const [draft, setDraft] = useState<MessageDraft | null>(() => {\n const storedDraft = allDrafts[conversationKey] || null\n\n // Filter expired attachments\n if (storedDraft?.attachments) {\n const now = Date.now()\n const validAttachments = storedDraft.attachments.filter(attachment => {\n if (!attachment.uploadedAt) return false\n return now - attachment.uploadedAt < ATTACHMENT_EXPIRY_MS\n })\n\n return {\n ...storedDraft,\n attachments: validAttachments,\n }\n }\n\n return storedDraft\n })\n\n // Clean up expired drafts only once on mount - React 18 recommended pattern\n const hasCleanedUp = useRef(false)\n\n useEffect(() => {\n if (hasCleanedUp.current) return\n\n const now = Date.now()\n const validDrafts: Record<string, MessageDraft> = {}\n\n Object.entries(allDrafts).forEach(([id, draftData]) => {\n if (draftData?.lastUpdated && now - draftData.lastUpdated < DRAFT_EXPIRY_MS) {\n validDrafts[id] = draftData\n }\n })\n\n // If we filtered out any drafts, update storage\n if (Object.keys(validDrafts).length !== Object.keys(allDrafts).length) {\n setAllDrafts(validDrafts)\n }\n\n hasCleanedUp.current = true\n }, [allDrafts, setAllDrafts])\n\n const saveDraft = useCallback(\n (text: string, attachments: FileAttachment[]) => {\n const hasContent = text.trim().length > 0 || attachments.length > 0\n const updatedDrafts = { ...allDrafts }\n\n if (hasContent) {\n // Convert attachments to serializable format (similar to web implementation)\n const normalizedAttachments = attachments\n .filter(att => att.id && att.status === 'success')\n .map(att => ({\n ...att,\n file: {\n uri: att.file.uri,\n name: att.file.name,\n type: att.file.type,\n size: att.file.size,\n width: att.file.width,\n height: att.file.height,\n },\n }))\n\n const newDraft: MessageDraft = {\n text,\n attachments: normalizedAttachments,\n lastUpdated: Date.now(),\n }\n\n updatedDrafts[conversationKey] = newDraft\n setDraft(newDraft)\n } else {\n delete updatedDrafts[conversationKey]\n setDraft(null)\n }\n\n setAllDrafts(updatedDrafts)\n },\n [conversationKey, allDrafts, setAllDrafts]\n )\n\n const clearDraft = useCallback(() => {\n const updatedDrafts = { ...allDrafts }\n delete updatedDrafts[conversationKey]\n setAllDrafts(updatedDrafts)\n setDraft(null)\n }, [conversationKey, allDrafts, setAllDrafts])\n\n return {\n draft,\n saveDraft,\n clearDraft,\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planningcenter/chat-react-native",
3
- "version": "3.17.0",
3
+ "version": "3.17.1",
4
4
  "description": "",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -16,8 +16,10 @@
16
16
  "test": "expo-module test",
17
17
  "prepublishOnly": "expo-module prepublishOnly"
18
18
  },
19
+ "target": "18",
19
20
  "dependencies": {
20
- "lodash-inflection": "^1.5.0"
21
+ "lodash-inflection": "^1.5.0",
22
+ "react-compiler-runtime": "^1.0.0"
21
23
  },
22
24
  "peerDependencies": {
23
25
  "@planningcenter/datetime-fmt": ">=1.0.0",
@@ -33,7 +35,7 @@
33
35
  "lodash": "*",
34
36
  "moment": ">=2.0.0",
35
37
  "moment-timezone": "*",
36
- "react": "*",
38
+ "react": "^18.0.0 || ^19.0.0",
37
39
  "react-native": "*",
38
40
  "react-native-device-info": "*",
39
41
  "react-native-gesture-handler": "*",
@@ -47,6 +49,7 @@
47
49
  "@testing-library/react-hooks": "^8.0.1",
48
50
  "@types/jest": "^29.5.14",
49
51
  "@typescript-eslint/parser": "^8.32.0",
52
+ "eslint-plugin-react-compiler": "^19.1.0-rc.2",
50
53
  "expo-module-scripts": "^5.0.7",
51
54
  "fast-text-encoding": "^1.0.6",
52
55
  "jest": "^29.7.0",
@@ -56,5 +59,5 @@
56
59
  "react-native-url-polyfill": "^2.0.0",
57
60
  "typescript": "<5.6.0"
58
61
  },
59
- "gitHead": "df5f37a2d46dca3d54aeff706c80bbcf086a36b2"
62
+ "gitHead": "7c2622a94c9803e4085cf9c58929f76b8614895e"
60
63
  }
@@ -37,7 +37,7 @@ export function ApiProvider({ children }: ViewProps) {
37
37
  const { token, env } = useContext(ChatContext)
38
38
  const sessionChanged = useSessionChanged({ token, env })
39
39
 
40
- apiClient = useApiClient()
40
+ const client = useApiClient()
41
41
 
42
42
  useEffect(() => {
43
43
  if (!sessionChanged) return
@@ -45,6 +45,10 @@ export function ApiProvider({ children }: ViewProps) {
45
45
  chatQueryClient.clear()
46
46
  }, [sessionChanged])
47
47
 
48
+ useEffect(() => {
49
+ apiClient = client
50
+ }, [client])
51
+
48
52
  return (
49
53
  <QueryClientProvider client={chatQueryClient}>
50
54
  <PrefetchQueries />
@@ -1,4 +1,4 @@
1
- import { useCallback, useEffect, useState } from 'react'
1
+ import { useCallback, useEffect, useRef, useState } from 'react'
2
2
  import { useAsyncStorage } from './use_async_storage'
3
3
  import type { FileAttachment } from '../types/resources/denormalized_attachment_resource_for_create'
4
4
 
@@ -27,17 +27,26 @@ export function useMessageDraft(conversationId: number) {
27
27
  // Filter expired attachments
28
28
  if (storedDraft?.attachments) {
29
29
  const now = Date.now()
30
- storedDraft.attachments = storedDraft.attachments.filter(attachment => {
30
+ const validAttachments = storedDraft.attachments.filter(attachment => {
31
31
  if (!attachment.uploadedAt) return false
32
32
  return now - attachment.uploadedAt < ATTACHMENT_EXPIRY_MS
33
33
  })
34
+
35
+ return {
36
+ ...storedDraft,
37
+ attachments: validAttachments,
38
+ }
34
39
  }
35
40
 
36
41
  return storedDraft
37
42
  })
38
43
 
39
- // Clean up expired drafts on mount
44
+ // Clean up expired drafts only once on mount - React 18 recommended pattern
45
+ const hasCleanedUp = useRef(false)
46
+
40
47
  useEffect(() => {
48
+ if (hasCleanedUp.current) return
49
+
41
50
  const now = Date.now()
42
51
  const validDrafts: Record<string, MessageDraft> = {}
43
52
 
@@ -51,8 +60,9 @@ export function useMessageDraft(conversationId: number) {
51
60
  if (Object.keys(validDrafts).length !== Object.keys(allDrafts).length) {
52
61
  setAllDrafts(validDrafts)
53
62
  }
54
- // eslint-disable-next-line react-hooks/exhaustive-deps
55
- }, []) // I only want this to run on mount
63
+
64
+ hasCleanedUp.current = true
65
+ }, [allDrafts, setAllDrafts])
56
66
 
57
67
  const saveDraft = useCallback(
58
68
  (text: string, attachments: FileAttachment[]) => {