refactor(frontend): replace context api in new block menu with zustand store (#11120)

Currently, we use the context API for the block menu provider and to
access some of its state outside the blockMenuProvider wrapper. For
instance, in the tutorial, we need to move this wrapper higher up in the
tree, perhaps at the top of the builder tree. This will cause
unnecessary re-renders. Therefore, we should create a block menu zustand
store so that we can easily access it in other parts of the builder.

### Changes 🏗️
- Deleted `block-menu-provider.tsx` file.
- Updated BlockMenu, BlockMenuContent, BlockMenuDefaultContent, and
other components to utilize blockMenuStore instead of
BlockMenuStateProvider.
- Adjusted imports and context usage accordingly.

### Checklist 📋
- [x] Changes have been clearly listed.
- [x] Code has been tested and verified.
- [x] I’ve checked every part of the block menu where we used the
context API and it’s working perfectly.
- [x] Ability to use block menu state in other parts of the builder.
This commit is contained in:
Abhimanyu Yadav
2025-10-09 16:34:42 +05:30
committed by GitHub
parent ff72343035
commit 2e1d3dd185
15 changed files with 68 additions and 95 deletions

View File

@@ -7,7 +7,6 @@ import {
import { BlockMenuContent } from "../BlockMenuContent/BlockMenuContent";
import { ControlPanelButton } from "../ControlPanelButton";
import { useBlockMenu } from "./useBlockMenu";
import { BlockMenuStateProvider } from "../block-menu-provider";
import { LegoIcon } from "@phosphor-icons/react";
interface BlockMenuProps {
@@ -49,9 +48,7 @@ export const BlockMenu: React.FC<BlockMenuProps> = ({
className="absolute h-[80vh] w-[46.625rem] overflow-hidden rounded-[1rem] border-none p-0 shadow-[0_2px_6px_0_rgba(0,0,0,0.05)]"
data-id="blocks-control-popover-content"
>
<BlockMenuStateProvider>
<BlockMenuContent />
</BlockMenuStateProvider>
<BlockMenuContent />
</PopoverContent>
</Popover>
);

View File

@@ -1,13 +1,13 @@
"use client";
import React from "react";
import { useBlockMenuContext } from "../block-menu-provider";
import { BlockMenuSearchBar } from "../BlockMenuSearchBar/BlockMenuSearchBar";
import { Separator } from "@/components/__legacy__/ui/separator";
import { BlockMenuDefault } from "../BlockMenuDefault/BlockMenuDefault";
import { BlockMenuSearch } from "../BlockMenuSearch/BlockMenuSearch";
import { useBlockMenuStore } from "../../../stores/blockMenuStore";
export const BlockMenuContent = () => {
const { searchQuery } = useBlockMenuContext();
const { searchQuery } = useBlockMenuStore();
return (
<div className="flex h-full w-full flex-col">
<BlockMenuSearchBar />

View File

@@ -1,14 +1,15 @@
import React from "react";
import { DefaultStateType, useBlockMenuContext } from "../block-menu-provider";
import { AllBlocksContent } from "../AllBlocksContent/AllBlocksContent";
import { PaginatedBlocksContent } from "../PaginatedBlocksContent/PaginatedBlocksContent";
import { IntegrationsContent } from "../IntegrationsContent/IntegrationsContent";
import { MarketplaceAgentsContent } from "../MarketplaceAgentsContent/MarketplaceAgentsContent";
import { MyAgentsContent } from "../MyAgentsContent/MyAgentsContent";
import { SuggestionContent } from "../SuggestionContent/SuggestionContent";
import { useBlockMenuStore } from "../../../stores/blockMenuStore";
import { DefaultStateType } from "../types";
export const BlockMenuDefaultContent = () => {
const { defaultState } = useBlockMenuContext();
const { defaultState } = useBlockMenuStore();
return (
<div className="h-full flex-1 overflow-hidden">

View File

@@ -1,13 +1,13 @@
import { debounce } from "lodash";
import { useCallback, useEffect, useRef, useState } from "react";
import { useBlockMenuContext } from "../block-menu-provider";
import { useBlockMenuStore } from "../../../stores/blockMenuStore";
const SEARCH_DEBOUNCE_MS = 300;
export const useBlockMenuSearchBar = () => {
const inputRef = useRef<HTMLInputElement>(null);
const [localQuery, setLocalQuery] = useState("");
const { setSearchQuery, setSearchId, searchId } = useBlockMenuContext();
const { setSearchQuery, setSearchId, searchId } = useBlockMenuStore();
const searchIdRef = useRef(searchId);
useEffect(() => {

View File

@@ -1,14 +1,15 @@
import React from "react";
import { MenuItem } from "../MenuItem";
import { DefaultStateType, useBlockMenuContext } from "../block-menu-provider";
import { useBlockMenuSidebar } from "./useBlockMenuSidebar";
import { Skeleton } from "@/components/__legacy__/ui/skeleton";
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
import { useBlockMenuStore } from "../../../stores/blockMenuStore";
import { DefaultStateType } from "../types";
export const BlockMenuSidebar = () => {
const { data, setDefaultState, defaultState, isLoading, isError, error } =
useBlockMenuSidebar();
const { setIntegration } = useBlockMenuContext();
const { setIntegration } = useBlockMenuStore();
if (isLoading) {
return (
<div className="w-fit space-y-2 px-4 pt-4">

View File

@@ -1,9 +1,9 @@
import { useGetV2GetBuilderItemCounts } from "@/app/api/__generated__/endpoints/default/default";
import { useBlockMenuContext } from "../block-menu-provider";
import { CountResponse } from "@/app/api/__generated__/models/countResponse";
import { useBlockMenuStore } from "../../../stores/blockMenuStore";
export const useBlockMenuSidebar = () => {
const { defaultState, setDefaultState } = useBlockMenuContext();
const { defaultState, setDefaultState } = useBlockMenuStore();
const { data, isLoading, isError, error } = useGetV2GetBuilderItemCounts({
query: {

View File

@@ -1,15 +1,15 @@
import { Button } from "@/components/__legacy__/ui/button";
import React, { Fragment } from "react";
import { IntegrationBlock } from "../IntergrationBlock";
import { useBlockMenuContext } from "../block-menu-provider";
import { Skeleton } from "@/components/__legacy__/ui/skeleton";
import { useIntegrationBlocks } from "./useIntegrationBlocks";
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
import { InfiniteScroll } from "@/components/contextual/InfiniteScroll/InfiniteScroll";
import { useNodeStore } from "../../../stores/nodeStore";
import { useBlockMenuStore } from "../../../stores/blockMenuStore";
export const IntegrationBlocks = () => {
const { integration, setIntegration } = useBlockMenuContext();
const { integration, setIntegration } = useBlockMenuStore();
const {
allBlocks,
status,

View File

@@ -1,11 +1,11 @@
import { useGetV2GetBuilderBlocksInfinite } from "@/app/api/__generated__/endpoints/default/default";
import { BlockResponse } from "@/app/api/__generated__/models/blockResponse";
import { useBlockMenuContext } from "../block-menu-provider";
import { useBlockMenuStore } from "../../../stores/blockMenuStore";
const PAGE_SIZE = 10;
export const useIntegrationBlocks = () => {
const { integration } = useBlockMenuContext();
const { integration } = useBlockMenuStore();
const {
data: blocks,

View File

@@ -1,12 +1,12 @@
import React from "react";
import { useBlockMenuContext } from "../block-menu-provider";
import { scrollbarStyles } from "@/components/styles/scrollbars";
import { IntegrationBlocks } from "../IntegrationBlocks/IntegrationBlocks";
import { PaginatedIntegrationList } from "../PaginatedIntegrationList/PaginatedIntegrationList";
import { cn } from "@/lib/utils";
import { useBlockMenuStore } from "../../../stores/blockMenuStore";
export const IntegrationsContent = () => {
const { integration } = useBlockMenuContext();
const { integration } = useBlockMenuStore();
if (!integration) {
return <PaginatedIntegrationList />;

View File

@@ -1,13 +1,13 @@
import React from "react";
import { Integration } from "../Integration";
import { useBlockMenuContext } from "../block-menu-provider";
import { InfiniteScroll } from "@/components/contextual/InfiniteScroll/InfiniteScroll";
import { usePaginatedIntegrationList } from "./usePaginatedIntegrationList";
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
import { blockMenuContainerStyle } from "../style";
import { useBlockMenuStore } from "../../../stores/blockMenuStore";
export const PaginatedIntegrationList = () => {
const { setIntegration } = useBlockMenuContext();
const { setIntegration } = useBlockMenuStore();
const {
allProviders: providers,
providersLoading,

View File

@@ -1,14 +1,15 @@
import React from "react";
import { IntegrationChip } from "../IntegrationChip";
import { Block } from "../Block";
import { DefaultStateType, useBlockMenuContext } from "../block-menu-provider";
import { useSuggestionContent } from "./useSuggestionContent";
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
import { blockMenuContainerStyle } from "../style";
import { useNodeStore } from "../../../stores/nodeStore";
import { useBlockMenuStore } from "../../../stores/blockMenuStore";
import { DefaultStateType } from "../types";
export const SuggestionContent = () => {
const { setIntegration, setDefaultState } = useBlockMenuContext();
const { setIntegration, setDefaultState } = useBlockMenuStore();
const { data, isLoading, isError, error, refetch } = useSuggestionContent();
const addBlock = useNodeStore((state) => state.addBlock);

View File

@@ -1,71 +0,0 @@
"use client";
import { createContext, ReactNode, useContext, useState } from "react";
export enum DefaultStateType {
SUGGESTION = "suggestion",
ALL_BLOCKS = "all_blocks",
INPUT_BLOCKS = "input_blocks",
ACTION_BLOCKS = "action_blocks",
OUTPUT_BLOCKS = "output_blocks",
INTEGRATIONS = "integrations",
MARKETPLACE_AGENTS = "marketplace_agents",
MY_AGENTS = "my_agents",
}
interface BlockMenuContextType {
searchQuery: string;
setSearchQuery: React.Dispatch<React.SetStateAction<string>>;
searchId: string | undefined;
setSearchId: React.Dispatch<React.SetStateAction<string | undefined>>;
defaultState: DefaultStateType;
setDefaultState: React.Dispatch<React.SetStateAction<DefaultStateType>>;
integration: string | undefined;
setIntegration: React.Dispatch<React.SetStateAction<string | undefined>>;
}
export const BlockMenuContext = createContext<BlockMenuContextType>(
{} as BlockMenuContextType,
);
interface BlockMenuStateProviderProps {
children: ReactNode;
}
export function BlockMenuStateProvider({
children,
}: BlockMenuStateProviderProps) {
const [searchQuery, setSearchQuery] = useState("");
const [searchId, setSearchId] = useState<string | undefined>(undefined);
const [defaultState, setDefaultState] = useState<DefaultStateType>(
DefaultStateType.SUGGESTION,
);
const [integration, setIntegration] = useState<string | undefined>(undefined);
return (
<BlockMenuContext.Provider
value={{
searchQuery,
setSearchQuery,
searchId,
setSearchId,
defaultState,
setDefaultState,
integration,
setIntegration,
}}
>
{children}
</BlockMenuContext.Provider>
);
}
export function useBlockMenuContext(): BlockMenuContextType {
const context = useContext(BlockMenuContext);
if (!context) {
throw new Error(
"useBlockMenuContext must be used within a BlockMenuStateProvider",
);
}
return context;
}

View File

@@ -0,0 +1,10 @@
export enum DefaultStateType {
SUGGESTION = "suggestion",
ALL_BLOCKS = "all_blocks",
INPUT_BLOCKS = "input_blocks",
ACTION_BLOCKS = "action_blocks",
OUTPUT_BLOCKS = "output_blocks",
INTEGRATIONS = "integrations",
MARKETPLACE_AGENTS = "marketplace_agents",
MY_AGENTS = "my_agents",
}

View File

@@ -0,0 +1,34 @@
import { create } from "zustand";
import { DefaultStateType } from "../components/NewBlockMenu/types";
type BlockMenuStore = {
searchQuery: string;
searchId: string | undefined;
defaultState: DefaultStateType;
integration: string | undefined;
setSearchQuery: (query: string) => void;
setSearchId: (id: string | undefined) => void;
setDefaultState: (state: DefaultStateType) => void;
setIntegration: (integration: string | undefined) => void;
reset: () => void;
};
export const useBlockMenuStore = create<BlockMenuStore>((set) => ({
searchQuery: "",
searchId: undefined,
defaultState: DefaultStateType.SUGGESTION,
integration: undefined,
setSearchQuery: (query) => set({ searchQuery: query }),
setSearchId: (id) => set({ searchId: id }),
setDefaultState: (state) => set({ defaultState: state }),
setIntegration: (integration) => set({ integration }),
reset: () =>
set({
searchQuery: "",
searchId: undefined,
defaultState: DefaultStateType.SUGGESTION,
integration: undefined,
}),
}));