Skip to main content

Overview

The useBlockSelected hook determines whether a specific block is currently selected (active) in the editor. This is useful for highlighting, showing block-specific controls, or conditional rendering.

Usage

import { useBlockSelected } from '@yoopta/editor';

function BlockWrapper({ blockId, children }) {
  const isSelected = useBlockSelected({ blockId });

  return (
    <div className={isSelected ? 'block-selected' : 'block'}>
      {children}
      {isSelected && <button>Delete</button>}
    </div>
  );
}

Signature

function useBlockSelected(
  props: { blockId: string; at?: number } | { at: number; blockId?: string }
): boolean

Parameters

You must provide either blockId or at (or both):
blockId
string
The unique identifier of the block to check. If provided without at, this will be used to find the block.
at
number
The path index (order) of the block to check. If provided without blockId, this will be used to find the block.

Returns

boolean - true if the block is currently selected, false otherwise

Error Handling

The hook will throw an error if neither blockId nor at is provided:
// ❌ Error: useBlockSelected must receive either blockId or at
useBlockSelected({}); 

Examples

Selection by Block ID

function BlockControls({ blockId }: { blockId: string }) {
  const isSelected = useBlockSelected({ blockId });

  if (!isSelected) return null;

  return (
    <div className="block-controls">
      <button>Move Up</button>
      <button>Move Down</button>
      <button>Delete</button>
    </div>
  );
}

Selection by Path Index

function BlockByIndex({ index }: { index: number }) {
  const isSelected = useBlockSelected({ at: index });

  return (
    <div style={{ 
      backgroundColor: isSelected ? '#e3f2fd' : 'transparent' 
    }}>
      Block at position {index}
    </div>
  );
}

Highlight Selected Block

function HighlightableBlock({ blockId, children }) {
  const isSelected = useBlockSelected({ blockId });

  return (
    <div
      style={{
        border: isSelected ? '2px solid #2196f3' : '2px solid transparent',
        borderRadius: '4px',
        padding: '8px',
        transition: 'border-color 0.2s',
      }}
    >
      {children}
    </div>
  );
}

Show/Hide Block Actions

function BlockWithActions({ blockId, children }) {
  const isSelected = useBlockSelected({ blockId });
  const editor = useYooptaEditor();

  const handleDelete = () => {
    editor.deleteBlock({ blockId });
  };

  const handleDuplicate = () => {
    editor.duplicateBlock({ blockId });
  };

  return (
    <div className="block-container">
      {children}
      {isSelected && (
        <div className="block-actions">
          <button onClick={handleDuplicate}>Duplicate</button>
          <button onClick={handleDelete}>Delete</button>
        </div>
      )}
    </div>
  );
}

Selection Indicator

function SelectionIndicator({ blockId }: { blockId: string }) {
  const isSelected = useBlockSelected({ blockId });

  return (
    <div className="selection-indicator">
      {isSelected ? (
        <span style={{ color: '#2196f3' }}>✓ Selected</span>
      ) : (
        <span style={{ color: '#999' }}>○ Not selected</span>
      )}
    </div>
  );
}

Drag Handle Visibility

function DragHandle({ blockId }: { blockId: string }) {
  const isSelected = useBlockSelected({ blockId });
  const [isHovered, setIsHovered] = useState(false);

  const showHandle = isSelected || isHovered;

  return (
    <div 
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => setIsHovered(false)}
    >
      {showHandle && (
        <button className="drag-handle" draggable>
          ⋮⋮
        </button>
      )}
    </div>
  );
}

Combined with Block Data

function SmartBlockWrapper({ blockId, children }) {
  const isSelected = useBlockSelected({ blockId });
  const block = useBlockData(blockId);

  if (!block) return null;

  const isHeading = block.type.startsWith('Heading');
  const showOutline = isSelected && isHeading;

  return (
    <div
      style={{
        outline: showOutline ? '2px dashed #ff9800' : 'none',
        padding: showOutline ? '8px' : '0',
      }}
    >
      {children}
      {isSelected && (
        <div className="block-info">
          Type: {block.type} | Depth: {block.meta.depth}
        </div>
      )}
    </div>
  );
}

Use Cases

  • Visual Feedback: Highlight the active block with borders or backgrounds
  • Contextual Controls: Show block-specific actions only when selected
  • Drag Handles: Display drag handles for block reordering
  • Selection State UI: Show selection indicators in block lists
  • Keyboard Navigation: Style blocks differently during keyboard navigation
  • Block-specific Toolbars: Show formatting options for selected blocks
  • Analytics: Track which blocks users interact with most

How Selection Works

The hook compares the current editor path (editor.path.current) with the block’s order:
// Simplified implementation
const useBlockSelected = ({ blockId, at }) => {
  const editor = useYooptaEditor();
  
  let block;
  if (blockId) {
    block = editor.children[blockId];
  }
  if (at !== undefined) {
    block = Blocks.getBlock(editor, { at });
  }
  
  return editor.path.current === block?.meta.order;
};

Performance Considerations

This hook will re-render whenever the editor’s selection changes. For components that render many blocks:
  • Use React.memo to prevent unnecessary re-renders
  • Consider virtualization for long documents
  • Memoize heavy computations based on selection state
const BlockWrapper = React.memo(({ blockId, children }) => {
  const isSelected = useBlockSelected({ blockId });
  
  return (
    <div className={isSelected ? 'selected' : ''}>
      {children}
    </div>
  );
});

See Also

Build docs developers (and LLMs) love