34 examples

Missing dependency

Required component or library not included.

[ FAQ1 ]

What is a missing dependency?

A missing dependency occurs when code relies on an external library, module, or resource that hasn't been installed, imported, or declared correctly. These errors frequently appear during application builds or runtime, particularly when using package managers (like npm) or frameworks (such as React's useEffect). Symptoms include build failures, import errors, or runtime crashes due to unresolved modules or functions. Properly managing and declaring dependencies is crucial to ensuring reliable and predictable software operation.
[ FAQ2 ]

How to fix missing dependency errors

To fix missing dependency errors, verify that all required dependencies are correctly listed in your project's dependency files (such as package.json in npm-based projects) and have been properly installed. Run commands like npm install or yarn install to ensure dependencies are fetched and configured correctly. Confirm that imports or require statements accurately reflect installed module names and file paths. Utilize dependency management tools and scripts to maintain consistent and complete dependencies, and regularly review build logs or error messages to promptly detect and resolve missing or improperly configured dependencies.
diff block
const [customerInfo, setCustomerInfo] = useState<CustomerInfo | null>(null);
const { userId } = useAuth();
const { hasNotificationPermission } = useOneSignal();
+ const posthog = usePostHog();
- const login = useCallback(
- async (userId: string) => {
- if (!isInitialized) {
- console.warn("RevenueCat is not initialized yet");
- return;
- }
- try {
- const { customerInfo } = await Purchases.logIn(userId);
- setCustomerInfo(customerInfo);
- } catch (error) {
- console.error("Error logging in to RevenueCat:", error);
- throw error;
- }
- },
- [isInitialized],
- );
+ // Initialize RevenueCat only once when the component mounts
+ useMountEffect(() => {
+ async function initializeRevenueCatOnce() {
+ if (isInitialized) return;
- useEffect(() => {
- async function initialize() {
try {
- // Initialize with anonymous ID first
await initializeRevenueCat();
setIsInitialized(true);
- // If user is already logged in, identify them
+ // Only try to log in if there's a userId after initialization
if (userId) {
- await login(userId);
+ void loginInternal(userId);
}
} catch (error) {
console.error("Failed to initialize RevenueCat:", error);
}
}
- void initialize();
- }, [login, userId]);
+ void initializeRevenueCatOnce();
+ });
Greptile
greptile
logic: Missing dependency array in useMountEffect call could cause multiple unnecessary initializations
diff block
+import {
+ Button,
+ Container,
+ Group,
+ Paper,
+ PasswordInput,
+ Text,
+ TextInput,
+ Title
+} from '@mantine/core'
+import { PiShuffleDuotone, PiSignpostDuotone } from 'react-icons/pi'
+import { RegisterCommand } from '@remnawave/backend-contract'
+import { notifications } from '@mantine/notifications'
+import { useForm, zodResolver } from '@mantine/form'
+import { generate } from 'generate-password-ts'
+import { useTranslation } from 'react-i18next'
+import { useClipboard } from '@mantine/hooks'
+import { useEffect } from 'react'
+
+import { handleFormErrors } from '@shared/utils/misc'
+import { useAuth } from '@shared/hooks/use-auth'
+import { useRegister } from '@shared/api/hooks'
+
+export const RegisterFormFeature = () => {
+ const { t } = useTranslation()
+
+ const { setIsAuthenticated } = useAuth()
+
+ const { copy, copied, error } = useClipboard()
+
+ const form = useForm({
+ validate: {
+ ...zodResolver(RegisterCommand.RequestSchema),
+ confirmPassword: (value, values) =>
+ value !== values.password
+ ? t('register-form.feature.passwords-do-not-match')
+ : null,
+ password: (value) =>
+ value.length < 12 ? t('register-form.feature.password-too-short') : null
+ },
+ initialValues: {
+ username: '',
+ password: '',
+ confirmPassword: ''
+ }
+ })
+
+ const { mutate: register, isPending: isLoading } = useRegister({
+ mutationFns: {
+ onSuccess: () => setIsAuthenticated(true),
+ onError: (error) => {
+ handleFormErrors(form, error)
+ }
+ }
+ })
+
+ const handleGeneratePassword = () => {
+ const newPassword = generate({
+ length: 32,
+ numbers: true,
+ symbols: false,
+ uppercase: true,
+ lowercase: true,
+ strict: true
+ })
+
+ form.setValues({
+ ...form.values,
+ password: newPassword,
+ confirmPassword: newPassword
+ })
+
+ copy(newPassword)
+ }
+
+ useEffect(() => {
+ if (error) {
+ notifications.show({
+ title: t('register-form.feature.error'),
+ message: t('register-form.feature.password-copied-error')
+ })
+ }
+
+ if (copied) {
+ notifications.show({
+ title: t('register-form.feature.password-copied'),
+ message: t('register-form.feature.password-copied-message')
+ })
+ }
+ }, [error, copied])
Greptile
greptile
logic: Missing dependency `t` in the useEffect dependency array. This could cause stale translations if the language changes while the component is mounted.
suggested fix
useEffect(() => {
if (error) {
notifications.show({
title: t('register-form.feature.error'),
message: t('register-form.feature.password-copied-error')
})
}
if (copied) {
notifications.show({
title: t('register-form.feature.password-copied'),
message: t('register-form.feature.password-copied-message')
})
}
+ }, [error, copied, t])
diff block
+import styled from '@emotion/styled';
+import { useEffect, useMemo } from 'react';
+
+import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader';
+import { EmailLoader } from '@/activities/emails/components/EmailLoader';
+import { EmailThreadHeader } from '@/activities/emails/components/EmailThreadHeader';
+import { EmailThreadMessage } from '@/activities/emails/components/EmailThreadMessage';
+import { CommandMenuMessageThreadIntermediaryMessages } from '@/command-menu/pages/message-thread/components/CommandMenuMessageThreadIntermediaryMessages';
+import { useEmailThreadInCommandMenu } from '@/command-menu/pages/message-thread/hooks/useEmailThreadInCommandMenu';
+import { messageThreadComponentState } from '@/command-menu/pages/message-thread/states/messageThreadComponentState';
+import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
+import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
+import { assertUnreachable } from '@/workflow/utils/assertUnreachable';
+import { ConnectedAccountProvider } from 'twenty-shared';
+import { Button, IconArrowBackUp } from 'twenty-ui';
+
+const StyledWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+`;
+
+const StyledContainer = styled.div`
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ height: 85%;
+ overflow-y: auto;
+`;
+
+const StyledButtonContainer = styled.div<{ isMobile: boolean }>`
+ background: ${({ theme }) => theme.background.secondary};
+ border-top: 1px solid ${({ theme }) => theme.border.color.light};
+ display: flex;
+ justify-content: flex-end;
+ height: ${({ isMobile }) => (isMobile ? '100px' : '50px')};
+ padding: ${({ theme }) => theme.spacing(2)};
+ width: 100%;
+ box-sizing: border-box;
+`;
+
+export const CommandMenuMessageThreadPage = () => {
+ const setMessageThread = useSetRecoilComponentStateV2(
+ messageThreadComponentState,
+ );
+
+ const isMobile = useIsMobile();
+
+ const {
+ thread,
+ messages,
+ fetchMoreMessages,
+ threadLoading,
+ messageThreadExternalId,
+ connectedAccountHandle,
+ messageChannelLoading,
+ connectedAccountProvider,
+ lastMessageExternalId,
+ } = useEmailThreadInCommandMenu();
+
+ useEffect(() => {
+ if (!messages[0]?.messageThread) {
+ return;
+ }
+ setMessageThread(messages[0]?.messageThread);
+ });
Greptile
greptile
logic: Missing dependency array in useEffect hook could cause unnecessary re-renders and state updates
suggested fix
useEffect(() => {
if (!messages[0]?.messageThread) {
return;
}
setMessageThread(messages[0]?.messageThread);
+ }, [messages, setMessageThread]);
diff block
const messagesEndRef = useRef<HTMLDivElement>(null);
useEffect(() => {
- console.log({searchQuery})
+ console.log({ searchQuery });
if (searchQuery === "") return;
appendMessage(new TextMessage({ content: searchQuery, role: Role.User }));
Greptile
greptile
logic: missing dependency 'appendMessage' in useEffect dependency array
suggested fix
+ appendMessage(new TextMessage({ content: searchQuery, role: Role.User }));
+ }, [searchQuery, appendMessage]);
diff block
+import { List } from "@raycast/api";
+import { useEffect } from "react";
+import { ChangeModelProp } from "../../type";
+import { CacheAdapter } from "../../utils/cache";
+
+export const ModelDropdown = (props: ChangeModelProp) => {
+ const { models, onModelChange, selectedModel } = props;
+ const separateDefaultModel = models.filter((x) => x.id !== "default");
+ const defaultModel = models.find((x) => x.id === "default");
+
+ const cache = new CacheAdapter("select_model");
+
+ // it should same as `DropDown.storeValue`
+ useEffect(() => {
+ const selectModel = cache.get();
+ onModelChange(selectModel ?? "default");
+ }, []);
Greptile
greptile
logic: Missing dependency onModelChange in useEffect dependency array could cause stale closure issues
suggested fix
useEffect(() => {
const selectModel = cache.get();
onModelChange(selectModel ?? "default");
+ }, [onModelChange]);
diff block
+import { List } from "@raycast/api";
+import { useEffect } from "react";
+import { ChangeModelProp } from "../../type";
+import { CacheAdapter } from "../../utils/cache";
+
+export const ModelDropdown = (props: ChangeModelProp) => {
+ const { models, onModelChange, selectedModel } = props;
+ const separateDefaultModel = models.filter((x) => x.id !== "default");
+ const defaultModel = models.find((x) => x.id === "default");
+
+ const cache = new CacheAdapter("select_model");
+
+ // it should same as `DropDown.storeValue`
+ useEffect(() => {
+ const selectModel = cache.get();
+ onModelChange(selectModel ?? "default");
+ }, []);
Greptile
greptile
logic: Missing dependency onModelChange in useEffect dependency array could cause stale closure issues
diff block
+import { useFindAllWhatsappIntegrations } from '@/settings/integrations/meta/whatsapp/hooks/useFindAllWhatsappIntegrations';
+import { FindWhatsappIntegration } from '@/settings/integrations/meta/whatsapp/types/FindWhatsappIntegrationInput';
+import { TextInput } from '@/ui/input/components/TextInput';
+import { useEffect } from 'react';
+import { Controller, useFormContext } from 'react-hook-form';
+import { H2Title, Section } from 'twenty-ui';
+import { z } from 'zod';
+
+const slaMetadataFormSchema = z.object({
+ sla: z.number().int(),
+});
+
+export const SettingsServiceCenterSLAFormSchema = slaMetadataFormSchema.pick({
+ sla: true,
+});
+
+export type SettingsServiceCenterSLAFormSchemaValues = z.infer<
+ typeof slaMetadataFormSchema
+>;
+
+interface ServiceLevelFormProps {
+ activeSla?: FindWhatsappIntegration; // | MessengerIntegration
+}
+
+export const ServiceLevelForm = ({ activeSla }: ServiceLevelFormProps) => {
+ // const { t } = useTranslation();
+
+ const { control, reset } =
+ useFormContext<SettingsServiceCenterSLAFormSchemaValues>();
+
+ const { refetchWhatsapp } = useFindAllWhatsappIntegrations();
+ // const { refetchMessenger } = useGetAllMessengerIntegrations();
+
+ useEffect(() => {
+ // eslint-disable-next-line @nx/workspace-explicit-boolean-predicates-in-if
+ if (activeSla) {
+ reset({ sla: activeSla.sla });
+ }
+ }, [activeSla, reset]);
+
+ useEffect(() => {
+ refetchWhatsapp();
+ // refetchMessenger();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
Greptile
greptile
logic: Empty dependency array with commented eslint-disable suggests potential missing dependency refetchWhatsapp
diff block
[debouncedSearch]
);
- const handleImportClick = useCallback(async (repo: Repo) => {
- try {
- const groupedMessages = await fetchGroupedCommits(
- repo.owner.login,
- repo.name
- );
- setCommitMessages((prev) => ({
- ...prev,
- [repo.name]: groupedMessages,
- }));
- await updateRepository({
- github_id: Number(repo.id),
- user_id: user.id as string,
- name: repo.name,
- owner: repo.owner.login,
- html_url: repo.html_url,
- setLoading: setLoading,
- });
- router.push(`/${repo.name}/dashboard`);
- } catch (error) {
- console.error("Error fetching commit messages:", error);
- }
- }, []);
+ const handleImportClick = useCallback(
+ async (repo: Repo) => {
+ try {
+ await updateRepository({
+ github_id: Number(repo.id),
+ user_id: user.id as string,
+ name: repo.name,
+ owner: repo.owner.login,
+ html_url: repo.html_url,
+ setLoading: setLoading,
+ });
+ const currentRepos = [...repos];
+ const updatedRepos = [...currentRepos, repo];
+ setRepos(updatedRepos);
+ router.push(`/${repo.name}/dashboard`);
+ } catch (error) {
+ console.error("Error fetching commit messages:", error);
+ }
+ },
+ [user.id, router]
+ );
Greptile
greptile
logic: Missing dependency 'repos' in useCallback dependencies array. This could cause stale closures when updating repos. ```suggestion const handleImportClick = useCallback( async (repo: Repo) => { try { await updateRepository({ github_id: Number(repo.id), user_id: user.id as string, name: repo.name, owner: repo.owner.login, html_url: repo.html_url, setLoading: setLoading, }); const currentRepos = [...repos]; const updatedRepos = [...currentRepos, repo]; setRepos(updatedRepos); router.push(`/${repo.name}/dashboard`); } catch (error) { console.error("Error fetching commit messages:", error); } }, + [user.id, router, repos] ); ```
diff block
+import { useEffect, useState } from 'react';
+
+import type { Args, StoryContext } from '@storybook/csf';
+
+type ReducedStoryContext = Omit<
+ StoryContext<any, Args>,
+ 'abortSignal' | 'canvasElement' | 'step' | 'context'
+>;
+
+export function useTransformCode(
+ source: string,
+ transform: (code: string, storyContext: ReducedStoryContext) => string | Promise<string>,
+ storyContext: ReducedStoryContext
+) {
+ const [transformedCode, setTransformedCode] = useState('Transforming...');
+
+ const transformed = transform ? transform?.(source, storyContext) : source;
+
+ useEffect(() => {
+ async function getTransformedCode() {
+ const transformResult = await transformed;
+ if (transformResult !== transformedCode) {
+ setTransformedCode(transformResult);
+ }
+ }
+
+ getTransformedCode();
+ });
Greptile
greptile
logic: Missing dependency array on useEffect may cause redundant executions. Consider adding dependencies (e.g. [transformed, transformedCode]) to control effect invocation.
suggested fix
useEffect(() => {
async function getTransformedCode() {
const transformResult = await transformed;
if (transformResult !== transformedCode) {
setTransformedCode(transformResult);
}
}
getTransformedCode();
+ }, [transformed, transformedCode]);
diff block
+import { Box, Menu, Text } from '@mantine/core'
+import { createStyles } from '@mantine/styles'
+import { useEffect, useRef } from 'react'
+
+const MIN_LEFT_MARGIN = 5
+
+export type LinkMenuItemProps = {
+ name: string
+ icon?: JSX.Element
+ hint?: string
+ disabled: boolean
+ isSelected: boolean
+ set: () => void
+}
+
+export function LinkMenuItem(props: LinkMenuItemProps) {
+ const itemRef = useRef<HTMLButtonElement>(null)
+ const { classes } = createStyles({ root: {} })(undefined, {
+ name: 'SuggestionListItem',
+ })
+
+ function isSelected() {
+ const isKeyboardSelected = props.isSelected
+ // props.selectedIndex !== undefined && props.selectedIndex === props.index;
+ const isMouseSelected = itemRef.current?.matches(':hover')
+
+ return isKeyboardSelected || isMouseSelected
+ }
+
+ // Updates HTML "data-hovered" attribute which Mantine uses to set mouse hover styles.
+ // Allows users to "hover" menu items when navigating using the keyboard.
+ function updateSelection() {
+ isSelected()
+ ? itemRef.current?.setAttribute('data-hovered', 'true')
+ : itemRef.current?.removeAttribute('data-hovered')
+ }
+
+ useEffect(() => {
+ // Updates whether the item is selected with the keyboard (triggered on selectedIndex prop change).
+ updateSelection()
+
+ // if (
+ // isSelected() &&
+ // itemRef.current &&
+ // itemRef.current.getBoundingClientRect().left > MIN_LEFT_MARGIN //TODO: Kinda hacky, fix
+ // // This check is needed because initially the menu is initialized somewhere above outside the screen (with left = 1)
+ // // scrollIntoView() is called before the menu is set in the right place, and without the check would scroll to the top of the page every time
+ // ) {
+ // itemRef.current.scrollIntoView({
+ // behavior: 'smooth',
+ // block: 'nearest',
+ // })
+ // }
+ })
Greptile
greptile
logic: useEffect is missing dependency array and runs on every render. Add [] or [props.isSelected] as dependencies.
suggested fix
useEffect(() => {
// Updates whether the item is selected with the keyboard (triggered on selectedIndex prop change).
updateSelection()
// if (
// isSelected() &&
// itemRef.current &&
// itemRef.current.getBoundingClientRect().left > MIN_LEFT_MARGIN //TODO: Kinda hacky, fix
// // This check is needed because initially the menu is initialized somewhere above outside the screen (with left = 1)
// // scrollIntoView() is called before the menu is set in the right place, and without the check would scroll to the top of the page every time
// ) {
// itemRef.current.scrollIntoView({
// behavior: 'smooth',
// block: 'nearest',
// })
// }
+ }, [props.isSelected])
diff block
+import { ActionPanel, Action, Detail, showToast, Toast } from "@raycast/api";
+import { useState, useEffect } from "react";
+import { exec } from "child_process";
+import { promisify } from "util";
+
+const execPromise = promisify(exec);
+
+interface CommandOutputProps {
+ command: string;
+ projectId: string;
+ gcloudPath: string;
+}
+
+export default function CommandOutput({ command, projectId, gcloudPath }: CommandOutputProps) {
+ const [isLoading, setIsLoading] = useState(true);
+ const [output, setOutput] = useState<string>("");
+ const [error, setError] = useState<string | null>(null);
+
+ useEffect(() => {
+ executeCommand();
+ }, []);
Greptile
greptile
logic: Missing dependency array items - command, projectId, and gcloudPath should be included to re-run when they change
suggested fix
useEffect(() => {
executeCommand();
+ }, [command, projectId, gcloudPath]);
diff block
+import { Badge, Menu, Stack, Text } from '@mantine/core'
+import { createStyles } from '@mantine/styles'
+import { JSX, useEffect, useRef } from 'react'
+
+const MIN_LEFT_MARGIN = 5
+
+export type SlashMenuItemProps = {
+ name: string
+ icon: JSX.Element
+ hint: string | undefined
+ shortcut?: string
+ isSelected: boolean
+ set: () => void
+}
+
+export function SlashMenuItem(props: SlashMenuItemProps) {
+ const itemRef = useRef<HTMLButtonElement>(null)
+ const { classes } = createStyles({ root: {} })(undefined, {
+ name: 'SuggestionListItem',
+ })
+
+ function isSelected() {
+ const isKeyboardSelected = props.isSelected
+ // props.selectedIndex !== undefined && props.selectedIndex === props.index;
+ const isMouseSelected = itemRef.current?.matches(':hover')
+
+ return isKeyboardSelected || isMouseSelected
+ }
+
+ // Updates HTML "data-hovered" attribute which Mantine uses to set mouse hover styles.
+ // Allows users to "hover" menu items when navigating using the keyboard.
+ function updateSelection() {
+ isSelected()
+ ? itemRef.current?.setAttribute('data-hovered', 'true')
+ : itemRef.current?.removeAttribute('data-hovered')
+ }
+
+ useEffect(() => {
+ // Updates whether the item is selected with the keyboard (triggered on selectedIndex prop change).
+ updateSelection()
+
+ // if (
+ // isSelected() &&
+ // itemRef.current &&
+ // itemRef.current.getBoundingClientRect().left > MIN_LEFT_MARGIN //TODO: Kinda hacky, fix
+ // // This check is needed because initially the menu is initialized somewhere above outside the screen (with left = 1)
+ // // scrollIntoView() is called before the menu is set in the right place, and without the check would scroll to the top of the page every time
+ // ) {
+ // itemRef.current.scrollIntoView({
+ // behavior: 'smooth',
+ // block: 'nearest',
+ // })
+ // }
+ })
Greptile
greptile
logic: Missing dependency array causing unnecessary re-renders on every render cycle
suggested fix
useEffect(() => {
// Updates whether the item is selected with the keyboard (triggered on selectedIndex prop change).
updateSelection()
// if (
// isSelected() &&
// itemRef.current &&
// itemRef.current.getBoundingClientRect().left > MIN_LEFT_MARGIN //TODO: Kinda hacky, fix
// // This check is needed because initially the menu is initialized somewhere above outside the screen (with left = 1)
// // scrollIntoView() is called before the menu is set in the right place, and without the check would scroll to the top of the page every time
// ) {
// itemRef.current.scrollIntoView({
// behavior: 'smooth',
// block: 'nearest',
// })
// }
+ }, [props.isSelected])
diff block
+import { Badge, Menu, Stack, Text } from '@mantine/core'
+import { createStyles } from '@mantine/styles'
+import React, { JSX, useEffect, useRef } from 'react'
+
+// const MIN_LEFT_MARGIN = 5
+
+export type SlashMenuItemProps = {
+ name: string
+ icon: JSX.Element
+ hint: string | undefined
+ shortcut?: string
+ isSelected: boolean
+ set: () => void
+}
+
+export const SlashMenuItem = (props: SlashMenuItemProps) => {
+ const itemRef = useRef<HTMLButtonElement>(null)
+ const { classes } = createStyles({ root: {} })(undefined, {
+ name: 'SuggestionListItem',
+ })
+
+ function isSelected() {
+ const isKeyboardSelected = props.isSelected
+ // props.selectedIndex !== undefined && props.selectedIndex === props.index;
+ const isMouseSelected = itemRef.current?.matches(':hover')
+
+ return isKeyboardSelected || isMouseSelected
+ }
+
+ // Updates HTML "data-hovered" attribute which Mantine uses to set mouse hover styles.
+ // Allows users to "hover" menu items when navigating using the keyboard.
+ function updateSelection() {
+ if (isSelected()) {
+ itemRef.current?.setAttribute('data-hovered', 'true')
+ } else {
+ itemRef.current?.removeAttribute('data-hovered')
+ }
+ }
+
+ useEffect(() => {
+ // Updates whether the item is selected with the keyboard (triggered on selectedIndex prop change).
+ updateSelection()
+
+ // if (
+ // isSelected() &&
+ // itemRef.current &&
+ // itemRef.current.getBoundingClientRect().left > MIN_LEFT_MARGIN //TODO: Kinda hacky, fix
+ // // This check is needed because initially the menu is initialized somewhere above outside the screen (with left = 1)
+ // // scrollIntoView() is called before the menu is set in the right place, and without the check would scroll to the top of the page every time
+ // ) {
+ // itemRef.current.scrollIntoView({
+ // behavior: 'smooth',
+ // block: 'nearest',
+ // })
+ // }
+ })
Greptile
greptile
logic: Missing dependency array in useEffect hook could cause unnecessary re-renders
suggested fix
useEffect(() => {
// Updates whether the item is selected with the keyboard (triggered on selectedIndex prop change).
updateSelection()
// if (
// isSelected() &&
// itemRef.current &&
// itemRef.current.getBoundingClientRect().left > MIN_LEFT_MARGIN //TODO: Kinda hacky, fix
// // This check is needed because initially the menu is initialized somewhere above outside the screen (with left = 1)
// // scrollIntoView() is called before the menu is set in the right place, and without the check would scroll to the top of the page every time
// ) {
// itemRef.current.scrollIntoView({
// behavior: 'smooth',
// block: 'nearest',
// })
// }
+ }, [props.isSelected])
diff block
useEffect(() => {
if (!r.data) return;
- cache.set("bookmarks", JSON.stringify(r.data));
+ setCached(r.data);
}, [r.data]);
Greptile
greptile
logic: Missing dependency array items. setCached should be included in the useEffect dependencies to prevent stale closure issues.
suggested fix
+ useEffect(() => {
+ if (!r.data) return;
setCached(r.data);
+ }, [r.data, setCached]);
diff block
+import {
+ Action,
+ ActionPanel,
+ Form,
+ getSelectedFinderItems,
+ Icon,
+ showToast,
+ Toast,
+ useNavigation,
+ Clipboard,
+} from "@raycast/api";
+import { useCallback, useEffect, useMemo, useState } from "react";
+import { DEFAULT_MODEL } from "../../hooks/useModel";
+import { QuestionFormProps } from "../../type";
+import { checkFileValidity, formats } from "../../utils";
+import path from "node:path";
+
+export const QuestionForm = ({
+ initialQuestion,
+ selectedModel,
+ models,
+ onModelChange,
+ onSubmit,
+ isFirstCall,
+}: QuestionFormProps) => {
+ const { pop } = useNavigation();
+
+ const [question, setQuestion] = useState<string>(initialQuestion ?? "");
+ const [questionError, setQuestionError] = useState<string | undefined>();
+ const [attachmentError, setAttachmentError] = useState<string | undefined>();
+
+ const separateDefaultModel = models.filter((x) => x.id !== "default");
+ const defaultModel = models.find((x) => x.id === "default") ?? DEFAULT_MODEL;
+
+ const visionMap = useMemo(() => {
+ const map = new Map<string, boolean>();
+ models.forEach((m) => map.set(m.id, m.vision || false));
+ return map;
+ }, [models]);
+
+ const [files, setFiles] = useState<string[]>([]);
+ const [enableVision, setEnableVision] = useState(visionMap.get(selectedModel) || false);
+
+ const addFromSelected = useCallback(
+ (errCallback?: (reason: unknown) => void | Promise<void>) => {
+ getSelectedFinderItems()
+ .then((items) => items.map((item) => item.path))
+ .then((p) => setFiles(p.sort()))
+ .catch(errCallback);
+ },
+ [setFiles],
+ );
+
+ const addFromClipboard = useCallback(async () => {
+ const { text, file } = await Clipboard.read();
+ // console.log(`text`, text);
+ // console.log(`file`, file);
+ if (file && (text.startsWith("Image") || Object.keys(formats).includes(path.extname(file)))) {
+ setFiles((files) => [...new Set([...files, file!])].sort());
+ }
+ }, [setFiles]);
+
+ useEffect(() => {
+ if (isFirstCall && enableVision) {
+ addFromSelected(() => {});
+ addFromClipboard();
+ }
+ }, []);
Greptile
greptile
logic: Missing dependency array items: addFromSelected and addFromClipboard should be included in useEffect dependencies
suggested fix
useEffect(() => {
if (isFirstCall && enableVision) {
addFromSelected(() => {});
addFromClipboard();
}
+ }, [isFirstCall, enableVision, addFromSelected, addFromClipboard]);
diff block
);
};
-const useResultsViewSelectMenu = () => {
- const onSetFileView = useChatLayoutContextSelector((x) => x.onSetFileView);
+const useResultsViewSelectMenu = ({
+ chatId,
+ metricId
+}: {
+ chatId: string | undefined;
+ metricId: string;
+}) => {
+ const link = useMemo(() => {
+ if (!chatId) {
+ return createBusterRoute({
+ route: BusterRoutes.APP_METRIC_ID_RESULTS,
+ metricId: metricId
+ });
+ }
- const onClickButton = useMemoizedFn(() => {
- onSetFileView({ secondaryView: null, fileView: 'results' });
- });
+ return createBusterRoute({
+ route: BusterRoutes.APP_CHAT_ID_METRIC_ID_RESULTS,
+ chatId: chatId,
+ metricId: metricId
+ });
+ }, [chatId, metricId]);
return useMemo(
() => ({
label: 'Results view',
value: 'results-view',
- onClick: onClickButton,
+ link,
icon: <Table />
}),
[]
);
};
-const useSQLEditorSelectMenu = () => {
- const onSetFileView = useChatLayoutContextSelector((x) => x.onSetFileView);
- const editableSecondaryView: MetricFileViewSecondary = 'sql-edit';
+const useSQLEditorSelectMenu = ({
+ chatId,
+ metricId
+}: {
+ chatId: string | undefined;
+ metricId: string;
+}) => {
+ const link = useMemo(() => {
+ if (!chatId) {
+ return createBusterRoute({
+ route: BusterRoutes.APP_METRIC_ID_SQL,
+ metricId: metricId
+ });
+ }
- const onClickButton = useMemoizedFn(() => {
- onSetFileView({ secondaryView: editableSecondaryView, fileView: 'results' });
- });
+ return createBusterRoute({
+ route: BusterRoutes.APP_CHAT_ID_METRIC_ID_SQL,
+ chatId: chatId,
+ metricId: metricId
+ });
+ }, [chatId, metricId]);
return useMemo(
() => ({
label: 'SQL Editor',
value: 'sql-editor',
- onClick: onClickButton,
- icon: <SquareCode />
+ icon: <SquareCode />,
+ link
}),
- [onClickButton]
+ []
);
Greptile
greptile
logic: Missing dependency array includes link which is needed for memoization
suggested fix
+ return useMemo(
+ () => ({
+ label: 'SQL Editor',
+ value: 'sql-editor',
icon: <SquareCode />,
link
+ }),
+ [link]
+ );
diff block
);
};
-const useResultsViewSelectMenu = () => {
- const onSetFileView = useChatLayoutContextSelector((x) => x.onSetFileView);
+const useResultsViewSelectMenu = ({
+ chatId,
+ metricId
+}: {
+ chatId: string | undefined;
+ metricId: string;
+}) => {
+ const link = useMemo(() => {
+ if (!chatId) {
+ return createBusterRoute({
+ route: BusterRoutes.APP_METRIC_ID_RESULTS,
+ metricId: metricId
+ });
+ }
- const onClickButton = useMemoizedFn(() => {
- onSetFileView({ secondaryView: null, fileView: 'results' });
- });
+ return createBusterRoute({
+ route: BusterRoutes.APP_CHAT_ID_METRIC_ID_RESULTS,
+ chatId: chatId,
+ metricId: metricId
+ });
+ }, [chatId, metricId]);
return useMemo(
() => ({
label: 'Results view',
value: 'results-view',
- onClick: onClickButton,
+ link,
icon: <Table />
}),
[]
);
Greptile
greptile
logic: Missing dependency array includes link which is needed for memoization
suggested fix
+ return useMemo(
+ () => ({
+ label: 'Results view',
+ value: 'results-view',
link,
+ icon: <Table />
+ }),
+ [link]
+ );
diff block
+import { useEffect } from 'react'
+import { posthog } from '@/src/posthog'
+
+const MobileAppAttribution = () => {
+ useEffect(() => {
+ if (
+ navigator.userAgent.toLowerCase().includes('linkedin') ||
+ navigator.userAgent.toLowerCase().includes('facebook')
+ ) {
+ const params = new URLSearchParams(window.location.search)
+ params.set('__ph_distinct_id', posthog.get_distinct_id())
+ params.set('__ph_is_identified', posthog._isIdentified() ? 'true' : 'false')
+ params.set('__ph_session_id', posthog.get_session_id())
+ window.location.search = params.toString()
+ }
+ })
Greptile
greptile
logic: Missing dependency array in useEffect, causing the effect to run on every render. Add an empty dependency array to run only once on mount.
suggested fix
useEffect(() => {
if (
navigator.userAgent.toLowerCase().includes('linkedin') ||
navigator.userAgent.toLowerCase().includes('facebook')
) {
const params = new URLSearchParams(window.location.search)
params.set('__ph_distinct_id', posthog.get_distinct_id())
params.set('__ph_is_identified', posthog._isIdentified() ? 'true' : 'false')
params.set('__ph_session_id', posthog.get_session_id())
window.location.search = params.toString()
}
+ }, [])
diff block
+import { List, Clipboard, Icon, ActionPanel, Action, showToast } from "@raycast/api";
+import { useEffect, useState } from "react";
+import dayjs, { Dayjs } from "dayjs";
+import utc from "dayjs/plugin/utc";
+import advancedFormat from "dayjs/plugin/advancedFormat";
+dayjs.extend(utc);
+dayjs.extend(advancedFormat);
+import localizedFormat from "dayjs/plugin/localizedFormat";
+dayjs.extend(localizedFormat);
+
+export default function DateConverter() {
+ const [clipboardText, setClipboardText] = useState("");
+ const [inputValue, setInputValue] = useState<string>(clipboardText);
+ const [resultList, setResultList] = useState<string[]>([]);
+ const [error, setError] = useState<boolean>(false);
+
+ useEffect(() => {
+ Clipboard.readText().then((text) => {
+ setClipboardText(text?.toString() || "now");
+ });
+ });
Greptile
greptile
logic: Missing dependency array in useEffect hook causes continuous clipboard reads. Add empty array to only run on mount
suggested fix
useEffect(() => {
Clipboard.readText().then((text) => {
setClipboardText(text?.toString() || "now");
});
+ }, []);
diff block
import { Input } from '@/components/ui/input';
import { AutoFormFieldProps } from '@autoform/react';
-import React from 'react';
+import React, { useEffect } from 'react';
export const NumberField: React.FC<AutoFormFieldProps> = ({ inputProps, error, field, id }) => {
const { key, ...props } = inputProps;
+ useEffect(() => {
+ if (field.default !== undefined) {
+ props.onChange({
+ target: { value: Number(field.default), name: inputProps.name },
+ });
+ }
+ }, [field.default]);
Greptile
greptile
style: Missing dependency 'props' in useEffect dependency array could cause stale closure issues
diff block
+import { useCallback, useContext } from 'react'
+import { DropdownMenu, Heading, MenuList, MenuItem } from '../../ui/DropdownMenu/DropdownMenu'
+import QuickbooksIcon from '../../../icons/QuickbooksIcon'
+import LinkIcon from '../../../icons/Link'
+import { Text, TextSize } from '../../Typography'
+import CheckIcon from '../../../icons/Check'
+import Cog from '../../../icons/Cog'
+import { Button } from '../../ui/Button/Button'
+import { Spacer } from '../../ui/Stack/Stack'
+import { QuickbooksContext } from '../../../contexts/QuickbooksContext/QuickbooksContext'
+
+const MenuTriggerButton = () => <Button variant='ghost'>Manage<Cog size={16}/></Button>
+
+export const IntegrationsConnectMenu = () => {
+ const {
+ quickbooksIsConnected,
+ linkQuickbooks,
+ } = useContext(QuickbooksContext)
+
+ const initiateQuickbooksOAuth = useCallback(
+ async () => {
+ const authorizationUrl = await linkQuickbooks()
+ window.location.href = authorizationUrl
+ },
+ []);
Greptile
greptile
logic: Missing dependency array entry for linkQuickbooks in useCallback
suggested fix
const initiateQuickbooksOAuth = useCallback(
async () => {
const authorizationUrl = await linkQuickbooks()
window.location.href = authorizationUrl
},
+ [linkQuickbooks]);
diff block
const [selectedFolder, setSelectedFolder] = useState<string | undefined>(value)
useEffect(() => {
- expandProjectFolder(value || '')
+ if (includeProtocol) {
+ if (value?.startsWith('project://')) {
+ expandProjectFolder(value.replace('project://', ''))
+ }
+ } else {
+ expandProjectFolder(value || '')
+ }
}, [value])
Greptile
greptile
logic: Missing dependency array items. `includeProtocol` and `expandProjectFolder` should be included in the useEffect dependencies.
suggested fix
+ useEffect(() => {
if (includeProtocol) {
if (value?.startsWith('project://')) {
expandProjectFolder(value.replace('project://', ''))
}
} else {
expandProjectFolder(value || '')
}
+ }, [value, includeProtocol, expandProjectFolder])
diff block
if (isLoggedIn()) {
handleRedirects();
+ } else {
+ setStep(1);
}
}, []);
Greptile
greptile
logic: Missing dependency array entries: queryParams and navigateToSelectOrganization
suggested fix
+ }, [queryParams, navigateToSelectOrganization]);
diff block
+import styled from '@emotion/styled';
+import { useEffect, useState } from 'react';
+
+import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
+import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
+import { ServiceCenterTabContent } from '@/settings/service-center/agents/components/ServiceCenterTabContent';
+import { ServiceCenterTabList } from '@/settings/service-center/agents/components/ServiceCenterTabList';
+import { SettingsServiceCenterFilterDropdown } from '@/settings/service-center/agents/components/SettingsServiceCenterFilterDropdown';
+import { useFindAllAgents } from '@/settings/service-center/agents/hooks/useFindAllAgents';
+import { Agent } from '@/settings/service-center/agents/types/Agent';
+import { useFindAllSectors } from '@/settings/service-center/sectors/hooks/useFindAllSectors';
+import { IntegrationType } from '@/settings/service-center/types/IntegrationType';
+import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
+import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
+import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
+import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
+
+const StyledShowServiceCenterTabs = styled.div<{ isMobile: boolean }>`
+ display: flex;
+ flex: 1 0 0;
+ flex-direction: column;
+ justify-content: start;
+ width: 100%;
+`;
+
+const StyledTabListContainer = styled.div`
+ align-items: center;
+ border-bottom: ${({ theme }) => `1px solid ${theme.border.color.light}`};
+ box-sizing: border-box;
+ display: flex;
+ gap: ${({ theme }) => theme.spacing(2)};
+ height: 40px;
+ overflow-x: auto;
+`;
+
+export const TAB_LIST_COMPONENT_ID = 'show-page-right-tab-list';
+
+type ServiceCenterTabsProps = {
+ isRightDrawer?: boolean;
+ loading?: boolean;
+};
+
+export const ServiceCenterTabs = ({
+ loading,
+ isRightDrawer = false,
+}: ServiceCenterTabsProps) => {
+ const [activeTabId] = useRecoilComponentStateV2(
+ activeTabIdComponentState,
+ TAB_LIST_COMPONENT_ID,
+ );
+ // const activeTabId = useRecoilValue(activeTabIdState);
+ // const { t } = useTranslation();
+
+ const isMobile = useIsMobile() || isRightDrawer;
+ const { agents, refetch } = useFindAllAgents();
+ const { sectors } = useFindAllSectors();
+ const { records: workspaceMembers } = useFindManyRecords<WorkspaceMember>({
+ objectNameSingular: CoreObjectNameSingular.WorkspaceMember,
+ });
+
+ const [agentsWithWorkspaceMembers, setAgentsWithWorkspaceMembers] = useState<
+ Agent[]
+ >([]);
+ const [selectedSectorId, setSelectedSectorId] = useState<string | null>(null);
+
+ const tabs = [
+ {
+ id: 'allAgents',
+ name: 'All',
+ icon: 'IconUsers',
+ },
+ {
+ id: IntegrationType.WHATSAPP,
+ name: 'Whatsapp',
+ icon: 'IconBrandWhatsapp',
+ },
+ // {
+ // id: IntegrationType.MESSENGER,
+ // name: 'Messenger',
+ // icon: 'IconBrandMessenger',
+ // },
+ ];
+
+ const filterAgentsBySector = (sectorId: string) =>
+ setSelectedSectorId(sectorId);
+
+ useEffect(() => {
+ // eslint-disable-next-line @nx/workspace-explicit-boolean-predicates-in-if
+ if (agents) {
+ const filteredAgents =
+ selectedSectorId && selectedSectorId !== ''
+ ? agents.filter((agent) =>
+ agent?.sectors?.some((sector) => sector.id === selectedSectorId),
+ )
+ : agents;
+
+ const filteredByIntegration = filteredAgents.filter((agent) =>
+ agent.inboxes?.some((inbox) => {
+ if (activeTabId === IntegrationType.WHATSAPP) {
+ return (
+ inbox.integrationType.toLowerCase() === IntegrationType.WHATSAPP
+ );
+ } else if (activeTabId === IntegrationType.MESSENGER) {
+ return (
+ inbox.integrationType.toLowerCase() === IntegrationType.MESSENGER
+ );
+ }
+ return true;
+ }),
+ );
+
+ const agentsData = filteredByIntegration.map((agent) => ({
+ ...agent,
+ workspaceMember: workspaceMembers.find(
+ (member) => member.id === agent.memberId,
+ ),
+ }));
+
+ setAgentsWithWorkspaceMembers(agentsData);
+ }
+ }, [agents, workspaceMembers, selectedSectorId, activeTabId]);
+
+ useEffect(() => {
+ refetch();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
Greptile
greptile
logic: Missing dependency 'refetch' in useEffect dependency array could cause stale data issues
diff block
const [runtime] = useState(() => new LocalRuntimeCore(opt, initialMessages));
+ useEffect(() => {
+ return () => {
+ runtime.threads.getMainThreadRuntimeCore().detach();
+ };
+ });
Greptile
greptile
logic: Effect missing dependency array. Add empty array to prevent running on every render.
suggested fix
useEffect(() => {
return () => {
runtime.threads.getMainThreadRuntimeCore().detach();
};
+ }, []);
diff block
useEffect(() => {
(async () => {
const response = await translateAPI(content, from_language, to_language);
- set_translate_result((await response.json()) as translateResult);
+ set_translate_result(response);
})();
}, []);
Greptile
greptile
logic: Missing dependency array items. Add content, from_language, and to_language to useEffect dependencies to prevent stale closures
suggested fix
+ useEffect(() => {
+ (async () => {
+ const response = await translateAPI(content, from_language, to_language);
set_translate_result(response);
+ })();
+ }, [content, from_language, to_language]);
diff block
export const RightClickMenu = observer(({ children }: RightClickMenuProps) => {
const editorEngine = useEditorEngine();
const [menuItems, setMenuItems] = useState<MenuItem[][]>([]);
+ const [settings, setSettings] = useState<FrameSettings>();
useEffect(() => {
updateMenuItems();
- }, [editorEngine.elements.selected, editorEngine.ast.mappings.layers]);
+ if (
+ editorEngine.webviews.selected.length > 0 &&
+ editorEngine.elements.selected.length === 0
+ ) {
+ setSettings(editorEngine.canvas.getFrame(editorEngine.webviews.selected[0].id));
+ }
+ }, [
+ editorEngine.elements.selected,
+ editorEngine.ast.mappings.layers,
+ editorEngine.webviews.selected,
+ ]);
Greptile
greptile
logic: useEffect has missing dependency editorEngine.canvas which is used in the effect. Add it to the dependency array.
suggested fix
+ useEffect(() => {
+ updateMenuItems();
if (
editorEngine.webviews.selected.length > 0 &&
editorEngine.elements.selected.length === 0
) {
setSettings(editorEngine.canvas.getFrame(editorEngine.webviews.selected[0].id));
}
}, [
editorEngine.elements.selected,
editorEngine.ast.mappings.layers,
editorEngine.webviews.selected,
+ editorEngine.canvas,
]);
diff block
children: 'Cancel',
}
+ // Resolve content, supporting both function and static content
+ const resolvedContent = typeof content === 'function' ? content(isLoading) : content
+
useEffect(() => {
setFormValues(initialValues)
Greptile
greptile
style: Missing dependency array for setFormValues - could cause unnecessary re-renders
suggested fix
+ setFormValues(initialValues)
+ }, [setFormValues, initialValues])
diff block
+import {
+ Action,
+ ActionPanel,
+ Form,
+ getSelectedFinderItems,
+ Icon,
+ showToast,
+ Toast,
+ useNavigation,
+ Clipboard,
+} from "@raycast/api";
+import { useCallback, useEffect, useMemo, useState } from "react";
+import { DEFAULT_MODEL } from "../../hooks/useModel";
+import { QuestionFormProps } from "../../type";
+import { checkFileValidity, formats } from "../../utils";
+import path from "node:path";
+
+export const QuestionForm = ({
+ initialQuestion,
+ selectedModel,
+ models,
+ onModelChange,
+ onSubmit,
+ isFirstCall,
+}: QuestionFormProps) => {
+ const { pop } = useNavigation();
+
+ const [question, setQuestion] = useState<string>(initialQuestion ?? "");
+ const [questionError, setQuestionError] = useState<string | undefined>();
+ const [attachmentError, setAttachmentError] = useState<string | undefined>();
+
+ const separateDefaultModel = models.filter((x) => x.id !== "default");
+ const defaultModel = models.find((x) => x.id === "default") ?? DEFAULT_MODEL;
+
+ const visionMap = useMemo(() => {
+ const map = new Map<string, boolean>();
+ models.forEach((m) => map.set(m.id, m.vision || false));
+ return map;
+ }, [models]);
+
+ const [files, setFiles] = useState<string[]>([]);
+ const [enableVision, setEnableVision] = useState(visionMap.get(selectedModel) || false);
+
+ const addFromSelected = useCallback(
+ (errCallback?: (reason: unknown) => void | Promise<void>) => {
+ getSelectedFinderItems()
+ .then((items) => items.map((item) => item.path))
+ .then((p) => setFiles(p.sort()))
+ .catch(errCallback);
+ },
+ [setFiles],
+ );
+
+ const addFromClipboard = useCallback(async () => {
+ const { text, file } = await Clipboard.read();
+ // console.log(`text`, text);
+ // console.log(`file`, file);
+ if (file && (text.startsWith("Image") || Object.keys(formats).includes(path.extname(file)))) {
+ setFiles((files) => [...new Set([...files, file!])].sort());
+ }
+ }, [setFiles]);
+
+ useEffect(() => {
+ if (isFirstCall && enableVision) {
+ addFromSelected(() => {});
+ addFromClipboard();
+ }
+ }, []);
Greptile
greptile
logic: Missing dependency array items: addFromSelected and addFromClipboard should be included in the useEffect dependency array
suggested fix
useEffect(() => {
if (isFirstCall && enableVision) {
addFromSelected(() => {});
addFromClipboard();
}
+ }, [isFirstCall, enableVision, addFromSelected, addFromClipboard]);
diff block
+import clsx from 'clsx'
+import { useActions, useValues } from 'kea'
+import { LemonTree, LemonTreeRef } from 'lib/lemon-ui/LemonTree/LemonTree'
+import { ButtonPrimitive } from 'lib/ui/Button/ButtonPrimitives'
+import { ContextMenuGroup, ContextMenuItem } from 'lib/ui/ContextMenu/ContextMenu'
+import { DropdownMenuGroup, DropdownMenuItem } from 'lib/ui/DropdownMenu/DropdownMenu'
+import { useEffect, useRef, useState } from 'react'
+
+import { projectTreeLogic } from '~/layout/panel-layout/ProjectTree/projectTreeLogic'
+import { joinPath, splitPath } from '~/layout/panel-layout/ProjectTree/utils'
+
+export interface FolderSelectProps {
+ /** The folder to select */
+ value?: string
+ /** Callback when a folder is selected */
+ onChange?: (folder: string) => void
+ /** Class name for the component */
+ className?: string
+}
+
+function getAllFolderIds(path?: string): string[] {
+ if (!path) {
+ return []
+ }
+ const splits = splitPath(path)
+ return splits.map((_, i) => 'project-folder/' + joinPath(splits.slice(0, i + 1)))
+}
+
+/** Input component for selecting a folder */
+export function FolderSelect({ value, onChange, className }: FolderSelectProps): JSX.Element {
+ const { projectTreeOnlyFolders, treeTableKeys } = useValues(projectTreeLogic)
+ const { createFolder, loadFolderIfNotLoaded } = useActions(projectTreeLogic)
+
+ const treeRef = useRef<LemonTreeRef>(null)
+
+ const [expandedFolders, setExpandedFolders] = useState<string[]>([])
+ const [touchedFolders, setTouchedFolders] = useState<string[]>([])
+
+ useEffect(() => {
+ if (!value) {
+ return
+ }
+ const allFolders = getAllFolderIds(value)
+ const newExpandedFolders = allFolders.filter((folder) => !expandedFolders.includes(folder))
+ if (newExpandedFolders.length > 0) {
+ setExpandedFolders([...expandedFolders, ...newExpandedFolders])
+ for (const folder of newExpandedFolders) {
+ if (!touchedFolders.includes(folder)) {
+ loadFolderIfNotLoaded(folder)
+ }
+ }
+ const newTouchedFolders = allFolders.filter((folder) => !touchedFolders.includes(folder))
+ setTouchedFolders([...touchedFolders, ...newTouchedFolders])
+ }
+ }, [value])
Greptile
greptile
logic: Missing dependency array items: expandedFolders and touchedFolders should be included in useEffect dependencies
diff block
+import { LocalStorage } from "@raycast/api";
+import { useCachedState } from "@raycast/utils";
+import { useEffect } from "react";
+import { Pollenflug } from "./types";
+
+export async function handlePin(pollenName: string) {
+ pollenName = pollenName.toLowerCase();
+
+ const pollenResponse = (await LocalStorage.getItem<string>("pollen")) || { pinned: [] };
+
+ const pollen = typeof pollenResponse === "string" ? JSON.parse(pollenResponse) : pollenResponse;
+
+ if (pollen.pinned.includes(pollenName)) {
+ pollen.pinned = pollen.pinned.filter((item: string) => item !== pollenName);
+ } else {
+ pollen.pinned.push(pollenName);
+ }
+
+ await LocalStorage.setItem("pollen", JSON.stringify(pollen));
+
+ return pollen.pinned;
+}
+
+export async function getPinned() {
+ const pollenResponse = (await LocalStorage.getItem<string>("pollen")) || { pinned: [] };
+ const pollen = typeof pollenResponse === "string" ? JSON.parse(pollenResponse) : pollenResponse;
+ return pollen.pinned;
+}
+
+export function usePinned(pollenflug?: Pollenflug) {
+ const [pinned, setPinned] = useCachedState<string[]>("pinned", []);
+
+ const [pinnedItems, setPinnedItems] = useCachedState<Pollenflug["pollen"]>("pinned_items", []);
+ const [unpinnedItems, setUnpinnedItems] = useCachedState<Pollenflug["pollen"]>("unpinned_items", []);
+
+ useEffect(() => {
+ getPinned().then((pinned) => setPinned(pinned));
+ }, []);
+
+ useEffect(() => {
+ console.log("updating pinned items", pinned);
+ if (!pollenflug || pollenflug.pollen === undefined) {
+ return;
+ }
+ const pinnedItems = pollenflug.pollen.filter((item) => pinned.includes(item.name.toLowerCase()));
+ const unpinnedItems = pollenflug.pollen.filter((item) => !pinned.includes(item.name.toLowerCase()));
+ setPinnedItems(pinnedItems);
+ setUnpinnedItems(unpinnedItems);
+ }, [pollenflug, pollenflug?.pollen, pinned]);
Greptile
greptile
logic: Effect has missing dependency setPinnedItems and setUnpinnedItems in dependency array
diff block
+import _ from "lodash";
+import { useEffect, useState } from "react";
+import { Detail, ActionPanel, Action, showToast, popToRoot } from "@raycast/api";
+import { OAuthService, withAccessToken, useCachedState, showFailureToast } from "@raycast/utils";
+
+import { useGet } from "./hooks";
+import { config } from "./config";
+
+export const githubService = OAuthService.github({
+ scope: "read:user user:email",
+});
+
+export const googleClientId = "797589717240-9353d3f7bnfssqgo6ci8kv4rfai9rfu6.apps.googleusercontent.com";
+export const googleService = OAuthService.google({
+ clientId: googleClientId,
+ scope: "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile",
+});
+
+function UserProfileDetails() {
+ // TODO:
+ // - decide on the refresh strategy for github
+
+ const { isLoading, response } = useGet("/auth/profile");
+
+ const markdown = `
+ <img src="${_.get(response, "user.imageUrl")}" width="300" height="300">
+ `;
+
+ const freeTierText = "Free Tier";
+ const subscriptionText = (() => {
+ const userSubscription: { endDate: number; cancelAt: number } | undefined = _.get(response, "user.subscription");
+ if (!userSubscription) return freeTierText;
+ const { cancelAt, endDate } = userSubscription;
+ if (cancelAt && cancelAt * 1000 > Date.now())
+ return `Cancelled (Active until ${new Date(cancelAt * 1000).toLocaleDateString()})`;
+ else if (endDate * 1000 > Date.now()) return `Active (until ${new Date(endDate * 1000).toLocaleDateString()})`;
+ else return `Expired (${new Date(endDate * 1000).toLocaleDateString()})`;
+ })();
+
+ const shouldUpgrade = subscriptionText === freeTierText || subscriptionText.startsWith("Expired");
+ return (
+ <Detail
+ isLoading={isLoading}
+ markdown={markdown}
+ metadata={
+ <Detail.Metadata>
+ <Detail.Metadata.Label title="Full Name" text={_.get(response, "user.name")} />
+ <Detail.Metadata.Label title="Email" text={_.get(response, "user.email")} />
+ <Detail.Metadata.Label title="Subscription" text={subscriptionText} />
+ </Detail.Metadata>
+ }
+ actions={
+ shouldUpgrade ? (
+ <ActionPanel title="Upgrade Subscription">
+ <Action.OpenInBrowser url={`${config.lpURL}/#pricing`} title="Upgrade Subscription" />
+ </ActionPanel>
+ ) : (
+ <ActionPanel title="Learn & Repeat Words">
+ <Action.OpenInBrowser url={`${config.lpURL}/learn`} title="Learn & Repeat Words" />
+ </ActionPanel>
+ )
+ }
+ />
+ );
+}
+
+export const UserProfilePageGithub = withAccessToken(githubService)(() => <UserProfileDetails />);
+export const UserProfilePageGoogle = withAccessToken(googleService)(() => <UserProfileDetails />);
+
+export function AuthorizationComponent({ authProvider }: { authProvider: "github" | "google" }) {
+ const [, setAuthProvider] = useCachedState<string>("authProvider", "");
+
+ const service = (() => {
+ if (authProvider === "github") return githubService;
+ else if (authProvider === "google") return googleService;
+ else throw new Error("Invalid auth provider");
+ })();
+ const [isLoading, setIsLoading] = useState<boolean>(true);
+
+ useEffect(() => {
+ setAuthProvider(authProvider);
+ (async () => {
+ try {
+ await service.authorize();
+ setIsLoading(false);
+ await showToast({ title: "Authorization Successful" });
+ } catch (error) {
+ console.error("error authorizaing user", error);
+ setIsLoading(false);
+ await showFailureToast({ title: "Authorization Failed" });
+ } finally {
+ popToRoot();
+ }
+ })();
+ }, [service]);
Greptile
greptile
logic: useEffect has missing dependency 'setAuthProvider' in dependency array
suggested fix
useEffect(() => {
setAuthProvider(authProvider);
(async () => {
try {
await service.authorize();
setIsLoading(false);
await showToast({ title: "Authorization Successful" });
} catch (error) {
console.error("error authorizaing user", error);
setIsLoading(false);
await showFailureToast({ title: "Authorization Failed" });
} finally {
popToRoot();
}
})();
+ }, [service, setAuthProvider]);
diff block
+"use client";
+
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { useEffect, useState } from "react";
+import TransactionSection from "../sections/transactions";
+import PendingTxnsBadge from "@/components/common/pendingtxnsbadge";
+import BridgeSection from "../sections/bridge";
+import { useTransactionsStore } from "@/stores/transactions";
+import TransactionModal from "../sections/bridge/review-and-submit";
+import { useCommonStore } from "@/stores/common";
+import AdvancedSettings from "./settings";
+import { validAddress } from "@/utils/common";
+import { Chain } from "@/types/common";
+import { z } from "zod";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { Input } from "../ui/input";
+import AvailWalletConnect from "../wallets/avail";
+import Eth from "../wallets/eth";
+
+const addressSchema = z.object({
+ ethAddress: z.string().refine((val) => validAddress(val, Chain.ETH), {
+ message: "Invalid ETH address",
+ }),
+ availAddress: z.string().refine((val) => validAddress(val, Chain.AVAIL), {
+ message: "Invalid Avail address",
+ }),
+});
+
+type AddressFormData = z.infer<typeof addressSchema>;
+
+export default function Container() {
+ const [activeTab, setActiveTab] = useState("transactions");
+ const { fetchAllTransactions, setTransactionLoader } = useTransactionsStore();
+ const {
+ reviewDialog: { isOpen: isModalOpen, onOpenChange: setIsModalOpen },
+ } = useCommonStore();
+
+ const {
+ register,
+ formState: { errors },
+ watch,
+ } = useForm<AddressFormData>({
+ resolver: zodResolver(addressSchema),
+ mode: "onChange",
+ });
+
+ const addresses = watch();
+
+ useEffect(() => {
+ // Fetch if either address is valid
+ if (
+ (addresses.ethAddress && validAddress(addresses.ethAddress, Chain.ETH)) ||
+ (addresses.availAddress &&
+ validAddress(addresses.availAddress, Chain.AVAIL))
+ ) {
+ fetchAllTransactions({
+ ethAddress: addresses.ethAddress || undefined,
+ availAddress: addresses.availAddress || undefined,
+ setTransactionLoader,
+ });
+ }
+ }, [addresses.ethAddress, addresses.availAddress]);
+
+ // Polling effect for updates
+ useEffect(() => {
+ // Start polling if either address is present
+ if (!addresses.ethAddress && !addresses.availAddress) return;
Greptile
greptile
logic: Missing dependency array for fetchAllTransactions and setTransactionLoader in useEffect hook
suggested fix
if (!addresses.ethAddress && !addresses.availAddress) return;
+ }, [addresses.ethAddress, addresses.availAddress, fetchAllTransactions, setTransactionLoader]);
diff block
+import useState from 'rc-util/lib/hooks/useState';
+import * as React from 'react';
+import Button from '../button';
+import type { ButtonProps, LegacyButtonType } from '../button/button';
+import { convertLegacyProps } from '../button/button';
+
+export interface ActionButtonProps {
+ type?: LegacyButtonType;
+ actionFn?: (...args: any[]) => any | PromiseLike<any>;
+ close?: Function;
+ autoFocus?: boolean;
+ prefixCls: string;
+ buttonProps?: ButtonProps;
+ emitEvent?: boolean;
+ quitOnNullishReturnValue?: boolean;
+ children?: React.ReactNode;
+}
+
+function isThenable(thing?: PromiseLike<any>): boolean {
+ return !!(thing && !!thing.then);
+}
+
+const ActionButton: React.FC<ActionButtonProps> = (props) => {
+ const clickedRef = React.useRef<boolean>(false);
+ const ref = React.useRef<HTMLInputElement>(null);
+ const [loading, setLoading] = useState<ButtonProps['loading']>(false);
+ const { close } = props;
+ const onInternalClose = (...args: any[]) => {
+ close?.(...args);
+ };
+
+ React.useEffect(() => {
+ let timeoutId: ReturnType<typeof setTimeout> | null = null;
+ if (props.autoFocus) {
+ timeoutId = setTimeout(() => {
+ ref.current?.focus();
+ });
+ }
+ return () => {
+ if (timeoutId) {
+ clearTimeout(timeoutId);
+ }
+ };
+ }, []);
Greptile
greptile
logic: Missing dependency array for autoFocus prop in useEffect, could cause stale closure issues
suggested fix
React.useEffect(() => {
let timeoutId: ReturnType<typeof setTimeout> | null = null;
if (props.autoFocus) {
timeoutId = setTimeout(() => {
ref.current?.focus();
});
}
return () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
};
+ }, [props.autoFocus]);