Skip to content

chore: stabilize React context values and callback references to prev…#277

Merged
appflowy merged 5 commits intomainfrom
optimize_react_code
Mar 6, 2026
Merged

chore: stabilize React context values and callback references to prev…#277
appflowy merged 5 commits intomainfrom
optimize_react_code

Conversation

@appflowy
Copy link
Contributor

@appflowy appflowy commented Mar 5, 2026

…ent unnecessary re-renders

Description


Checklist

General

  • I've included relevant documentation or comments for the changes introduced.
  • I've tested the changes in multiple environments (e.g., different browsers, operating systems).

Testing

  • I've added or updated tests to validate the changes introduced for AppFlowy Web.

Feature-Specific

  • For feature additions, I've added a preview (video, screenshot, or demo) in the "Feature Preview" section.
  • I've verified that this feature integrates seamlessly with existing functionality.

Summary by Sourcery

Stabilize React context values, callbacks, and scroll handlers across chat, database, editor, and app overlay components to reduce unnecessary re-renders and improve UI responsiveness.

Enhancements:

  • Memoize context provider values and callback props in chat, AI assistant, database views, prompt modal, and app overlay components to avoid creating new references on each render.
  • Extract reusable handler functions in board, grid, file media, and editor toolbar components to stabilize event and ref callbacks and improve readability.
  • Improve scroll- and resize-related hooks and handlers (including passive listeners and ResizeObserver usage) to make sticky headers and scroll detection more efficient and reliable.
  • Refine prompt loading in the prompt modal provider by deriving built-in prompts via state, handling parse failures gracefully, and exposing a memoized context API.

@sourcery-ai
Copy link

sourcery-ai bot commented Mar 5, 2026

Reviewer's Guide

Refactors multiple React components and providers to stabilize context values, event handlers, and callback references using useMemo/useCallback, reduce inline closures, and improve scroll/event handling to avoid unnecessary re-renders and side-effects across chat, database, calendar, and app overlay features.

Class diagram for stabilized AI assistant contexts and view loading

classDiagram
  class AIAssistantProvider {
    -children ReactNode
    -request RequestInstance
    -viewId string
    -selectedModelName string
    -isFetching boolean
    -isApplying boolean
    -applyingState any
    -assistantType string
    -placeholderContent string
    -comment string
    -responseMode string
    -responseFormat string
    -isGlobalDocument boolean
    -error Error
    -scrollContainer HTMLElement
    -openDiscard boolean
    -completionHistoryRef CompletionItem[]
    +hasAIAnswer() boolean
    +handleKeyDown(event KeyboardEvent)
    +handleCloseDiscard() void
  }

  class ModelRequestInstance {
    +getModelList() Promise~Model[]~
    +getCurrentModel() Promise~string~
    +setCurrentModel(modelName string) Promise~void~
  }

  class ModelSelectorValue {
    +selectedModelName string
    +setSelectedModelName(name string) void
    +requestInstance ModelRequestInstance
  }

  class WriterContextValue {
    +viewId string
    +fetchViews() Promise~View[]~
    +placeholderContent string
    +comment string
    +improveWriting() void
    +assistantType string
    +isFetching boolean
    +isApplying boolean
    +askAIAnything() void
    +continueWriting() void
    +explain() void
    +fixSpelling() void
    +makeLonger() void
    +makeShorter() void
    +askAIAnythingWithRequest() void
    +setOpenDiscard(open boolean) void
    +applyingState any
    +setRagIds(ids string[]) void
    +exit() void
    +setEditorData(data any) void
    +keep() void
    +accept() void
    +rewrite() void
    +stop() void
    +responseMode string
    +setResponseMode(mode string) void
    +responseFormat string
    +setResponseFormat(format string) void
    +isGlobalDocument boolean
    +error Error
    +scrollContainer HTMLElement
    +hasAIAnswer() boolean
    +selectedModelName string
    +setSelectedModelName(name string) void
  }

  class ViewLoaderProvider {
    +getView(viewId string) Promise~View~
    +fetchViews() Promise~View[]~
  }

  AIAssistantProvider o--> ModelRequestInstance : creates
  AIAssistantProvider o--> ModelSelectorValue : memoized
  AIAssistantProvider o--> WriterContextValue : memoized
  AIAssistantProvider o--> ViewLoaderProvider : configures
  ModelSelectorValue ..> ModelRequestInstance : uses
  WriterContextValue ..> ModelRequestInstance : uses request.fetchViews
Loading

Class diagram for stabilized chat and prompt modal providers

classDiagram
  class Main {
    -workspaceId string
    -chatId string
    -requestInstance ChatRequestInstance
    -currentUser User
    -openingViewId string
    -selectionMode boolean
    -loadDatabasePrompts LoadDatabasePrompts
    -testDatabasePromptConfig TestDatabasePromptConfig
    +getView(viewId string, forceRefresh boolean) Promise~View~
    +fetchViews(forceRefresh boolean) Promise~View[]~
  }

  class ChatContextValue {
    +workspaceId string
    +chatId string
    +requestInstance ChatRequestInstance
    +currentUser User
    +openingViewId string
    +onOpenView(viewId string) void
    +onCloseView() void
    +selectionMode boolean
    +onOpenSelectionMode() void
    +onCloseSelectionMode() void
    +loadDatabasePrompts LoadDatabasePrompts
    +testDatabasePromptConfig TestDatabasePromptConfig
  }

  class ChatContentWithModelSync {
    +currentUser User
    +selectionMode boolean
  }

  class ChatModelRequestInstance {
    +getModelList() Promise~Model[]~
    +getCurrentModel() Promise~string~
    +setCurrentModel(modelName string) Promise~void~
  }

  class ChatModelSelectorValue {
    +selectedModelName string
    +setSelectedModelName(name string) void
    +requestInstance ChatModelRequestInstance
    +chatId string
  }

  class PromptModalProvider {
    -workspaceId string
    -loadDatabasePrompts LoadDatabasePrompts
    -testDatabasePromptConfig TestDatabasePromptConfig
    -isOpen boolean
    -currentPromptId string
    -prompts AiPrompt[]
    -currentDatabaseConfig PromptDatabaseConfiguration
    -fields PromptDatabaseField[]
    +openModal() void
    +closeModal() void
    +saveDatabaseConfig(config PromptDatabaseConfiguration) Promise~void~
    +reloadDatabasePrompts() Promise~void~
  }

  class PromptModalContextValue {
    +isOpen boolean
    +currentPromptId string
    +updateCurrentPromptId(id string) void
    +prompts AiPrompt[]
    +openModal() void
    +closeModal() void
    +databaseConfig PromptDatabaseConfiguration
    +fields PromptDatabaseField[]
    +testDatabasePromptConfig TestDatabasePromptConfig
    +saveDatabaseConfig(config PromptDatabaseConfiguration) Promise~void~
    +reloadDatabasePrompts() Promise~void~
  }

  class BuiltInPromptLoader {
    +getBuiltInPrompts() AiPrompt[]
  }

  Main o--> ChatContextValue : memoized
  Main o--> PromptModalProvider : renders
  Main o--> ViewLoaderProvider : configures

  ChatContentWithModelSync o--> ChatModelRequestInstance : memoized
  ChatContentWithModelSync o--> ChatModelSelectorValue : memoized

  PromptModalProvider o--> PromptModalContextValue : memoized
  PromptModalProvider ..> BuiltInPromptLoader : uses getBuiltInPrompts
Loading

Flow diagram for scroll detection hook with debounced button visibility

flowchart TD
  A[start useScrollDetection] --> B[resolve scrollElement from containerRef or getScrollParent]
  B --> C{scrollElement exists?}
  C -- no --> Z[end]
  C -- yes --> D[define handleScroll]
  D --> E[on scroll: set button opacity 0 and pointer-events none]
  E --> F{existing showTimeoutRef?}
  F -- yes --> G[clearTimeout showTimeoutRef]
  F -- no --> H[skip]
  G --> I[start new timeout 1000ms]
  H --> I
  I --> J[after 1000ms: set button opacity 1 and pointer-events auto]
  J --> K[reset showTimeoutRef to null]
  K --> L[add scroll listener with passive true]
  L --> M[cleanup effect]
  M --> N{showTimeoutRef not null?}
  N -- yes --> O[clearTimeout and reset showTimeoutRef]
  N -- no --> P[skip]
  O --> Q[remove scroll listener with same options]
  P --> Q
  Q --> Z[end]
Loading

File-Level Changes

Change Details Files
Stabilize AI writer/chat context and model selector values with memoized objects and callbacks.
  • Replaced inline ModelSelectorContext.Provider values in AIAssistantProvider with memoized model request and selector objects using useMemo.
  • Wrapped WriterContext value in AIAssistantProvider in useMemo to avoid recreating context objects on each render.
  • Memoized getView and fetchViews callbacks for ViewLoaderProvider inside AIAssistantProvider.
  • Refined keydown effect dependencies in AIAssistantProvider to only depend on stop where appropriate.
  • In chat main view, memoized model selector request instance and context value for ChatContentWithModelSync using useMemo.
  • Replaced direct props spreading into ChatContext.Provider with a memoized chatContextValue, and memoized getView/fetchViews callbacks used by ViewLoaderProvider.
  • Updated PromptModalProvider to use React state for prompts and currentPromptId instead of refs, introduced getBuiltInPrompts helper, and memoized the context value plus open/close handlers.
src/components/chat/provider/ai-assistant-provider.tsx
src/components/chat/chat/main.tsx
src/components/chat/provider/prompt-modal-provider.tsx
Stabilize board/grid scroll, hover, and delete handlers via useCallback and reuse of scroll-sync callbacks.
  • Extracted mouse enter/leave handlers in Group board component into memoized callbacks and refactored complex ref assignment and scroll-sync logic into dedicated memoized handlers.
  • Reused a memoized handleScrollLeft function for multiple consumers (Columns, DatabaseStickyHorizontalScrollbar) in Group to avoid recreating inline closures.
  • Created memoized mouse enter/leave handlers and scroll/scrollLeft handlers in GridVirtualizer to synchronize sticky headers and bottom scrollbars without inline functions.
  • Replaced inline onClose/onDeleted lambdas for DeleteRowConfirm in Group with memoized callbacks that close the dialog and clear selected cards.
src/components/database/components/board/group/Group.tsx
src/components/database/components/grid/grid-table/GridVirtualizer.tsx
Stabilize item-level handlers in file media list and selection toolbar to prevent re-renders and focus issues.
  • Introduced a FileMediaListItem component that memoizes per-item onUpdateName, onDelete, and onPreview handlers with useCallback, reducing new closures in the list mapping.
  • Refactored SelectionToolbar to extract the onMouseDown logic into a memoized handleMouseDown callback rather than inline handler.
src/components/database/components/cell/file-media/FileMediaList.tsx
src/components/editor/components/toolbar/selection-toolbar/SelectionToolbar.tsx
Memoize database conditions and app overlay context values and close handlers for modal components.
  • Wrapped DatabaseConditionsContext provider value in useMemo to stabilize filter/conditions context between renders.
  • In AppOverlayProvider, added memoized close handlers for various modals (rename, delete, manage space, create space, delete space) to avoid recreating inline callbacks and passed them into the corresponding components.
src/components/database/DatabaseViews.tsx
src/components/app/app-overlay/AppOverlayProvider.tsx
Improve calendar scroll detection and sticky header behavior with debounced visibility and passive listeners.
  • Enhanced useScrollDetection hook by storing the timeout id in a ref, clearing timeouts between scroll events, and using passive scroll listeners while ensuring cleanup.
  • Updated useCalendarStickyHeader to add scroll listeners as passive and replaced a non-standard resize event listener on the scroll element with a ResizeObserver plus window resize listener.
src/components/database/fullcalendar/hooks/useScrollDetection.ts
src/components/database/fullcalendar/hooks/useCalendarStickyHeader.ts
Stabilize login modal close handler in app configuration.
  • Extracted the LoginModal onClose handler into a memoized closeLoginModal callback and used it in AppConfig instead of an inline lambda.
src/components/main/AppConfig.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@appflowy appflowy force-pushed the optimize_react_code branch from 07f3573 to b6762d5 Compare March 6, 2026 02:20
appflowy added 4 commits March 6, 2026 12:04
  - preserve first-message chat renames and stable model reads
  - guard async chat settings and notification updates after unmount
  - fix board/grid scroll listener cleanup and align toolbar fallback
  - add cypress stability coverage for chat, database, editor, and image toolbar flows
  - replace legacy AppContext access with auth/navigation/outline/operations/sync hooks
  - make optional auth and workspace dependencies explicit at call sites
  - split board selection state from board actions
  - update Storybook and modal/view wiring for the new context boundaries
@appflowy appflowy merged commit 5dea0e5 into main Mar 6, 2026
13 of 14 checks passed
@appflowy appflowy deleted the optimize_react_code branch March 6, 2026 11:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant