creating new textbox component every time state changes when i used textbox component as one item in toolbar

Query:
I’m working with a component in Syncfusion, and I’m facing an issue.

Problem:
When I try to enter a value in the textbox, it keeps losing focus every time.

  • I’ve already tried using useCallback and React.memo, but the issue persists.
  • I also attempted implementing a manual focusIn method, but the behavior isn’t smooth—there’s a noticeable blinking every time.

Request:
Could you please help me identify the root cause and suggest a solution to ensure the textbox retains its focus without the blinking effect?

const ToolB = ({ selectedRecords }: ToolBProps) => {
  const [toolbarState, setToolbarState] = useState<ToolbarState>(ToolbarState.DEFAULT);
  useEffect(() => {
    const state =
      selectedRecords.length > 1 ? ToolbarState.MULTI_SELECT : selectedRecords.length === 1 ? ToolbarState.SINGLE_SELECT : ToolbarState.DEFAULT;
    setToolbarState(state);
  }, [selectedRecords]);
  const handleVisibilty = (id: string) => {
    switch (id) {
      case 'cut':
        return toolbarState === ToolbarState.SINGLE_SELECT;
      case 'copy':
        return toolbarState === ToolbarState.MULTI_SELECT;
      case 'paste':
        return toolbarState === ToolbarState.DEFAULT;
      case 'bold':
        return toolbarState === ToolbarState.SINGLE_SELECT;
      case 'italic':
        return toolbarState === ToolbarState.MULTI_SELECT;
      default:
        return false;
    }
  };
  const [searchValue, setSearchValue] = useState('');
    const searchBoxRef = useRef<TextBoxComponent>(null);
  const searchInputTemplate = () => (
    <div>
      <TextBoxComponent
      ref={searchBoxRef}
        input={(e) => {
          setSearchValue(e.value);
          console.log(e.value);
        }}
        created={() => {
            searchBoxRef.current?.addIcon('prepend', 'e-icons e-search pr-0');
            searchBoxRef.current?.addIcon('append', 'e-icons e-close');
            searchBoxRef.current?.focusIn();
          }}
        placeholder="Search"
        value={searchValue}
      />
    </div>
  );
  const items = [
    { text: '', id: 'cut', template: searchInputTemplate, visible: handleVisibilty('cut') },
    { text: 'MULTI_SELECT', id: 'copy', visible: handleVisibilty('copy') },
    { text: 'DEFAULT', id: 'paste', visible: handleVisibilty('paste') },
    { text: 'SINGLE_SELECT', id: 'bold', visible: handleVisibilty('bold') },
    { text: 'MULTI_SELECT', id: 'italic', visible: handleVisibilty('italic') },
  ];
  return (
    <ToolbarComponent items={items} statelessTemplates={['directiveTemplates']}>
    </ToolbarComponent>
  );
};

3 Replies

SR Swathi Ravi Syncfusion Team January 10, 2025 09:58 AM UTC

Hi Goutham,

Thank you for reaching out!

In React, when there is a state change, the entire component re-renders, which might cause the flickering effect you observed. To overcome this issue, we recommend using the HTML-based Toolbar rendering approach, which we support.

We have shared the relevant User Guide for your reference. Please review it and let us know if you need further assistance.


Regards,
Swathi



GO Goutham replied to Swathi Ravi January 22, 2025 06:54 AM UTC

But in html render approach,i can't use props of component like visible,prefixIconCss
is there any way to stop glitch of textbox while searching when we used with toolbarcomponent instead of html render approach



SR Swathi Ravi Syncfusion Team January 23, 2025 12:47 PM UTC

Goutham,

This is likely happening because the entire component is re-rendering when the state changes, causing the TextBox to be recreated. Let's try to optimize this by moving the TextBox component outside of the main render cycle and using React's useRef to maintain its instance. Here's a modified version of your code that should help solve this issue:

import { ToolbarComponent } from '@syncfusion/ej2-react-navigations';
import { useState, useEffect, useRef, useCallback, useMemo } from "react";
import * as React from "react";
import { createRoot } from 'react-dom/client';
import { TextBoxComponent } from '@syncfusion/ej2-react-inputs';

const SearchInput = React.memo(({ onInput }) => {
  const searchBoxRef = useRef(null);

  useEffect(() => {
    if (searchBoxRef.current) {
      searchBoxRef.current.addIcon('prepend', 'e-icons e-search pr-0');
      searchBoxRef.current.addIcon('append', 'e-icons e-close');
      searchBoxRef.current.focusIn();
    }
  }, []);

  return (
    <TextBoxComponent
      ref={searchBoxRef}
      input={onInput}
      placeholder="Search"
    />
  );
});

const ReactApp = ({ selectedRecords = [] }) => {
  const ToolbarState = {
    DEFAULT: 'DEFAULT',
    SINGLE_SELECT: 'SINGLE_SELECT',
    MULTI_SELECT: 'MULTI_SELECT'
  };

  const [toolbarState, setToolbarState] = useState(ToolbarState.DEFAULT);
  const [searchValue, setSearchValue] = useState('');

  useEffect(() => {
    const state =
      selectedRecords.length > 1 ? ToolbarState.MULTI_SELECT :
        selectedRecords.length === 1 ? ToolbarState.SINGLE_SELECT :
          ToolbarState.DEFAULT;
    setToolbarState(state);
  }, [selectedRecords]);

  const handleVisibilty = useCallback((id) => {
    switch (id) {
      case 'cut':
        return toolbarState === ToolbarState.SINGLE_SELECT;
      case 'copy':
        return toolbarState === ToolbarState.MULTI_SELECT;
      case 'paste':
        return toolbarState === ToolbarState.DEFAULT;
      case 'bold':
        return toolbarState === ToolbarState.SINGLE_SELECT;
      case 'italic':
        return toolbarState === ToolbarState.MULTI_SELECT;
      default:
        return false;
    }
  }, [toolbarState]);

  const handleSearchInput = useCallback((e) => {
    setSearchValue(e.value);
    console.log(e.value);
  }, []);

  const searchInputTemplate = useCallback(() => (
    <SearchInput onInput={handleSearchInput} />
  ), [handleSearchInput]);

  const items = useMemo(() => [
    { text: '', id: 'cut', template: searchInputTemplate, visible: [handleVisibilty('cut')] },
    { text: 'MULTI_SELECT', id: 'copy', visible: [handleVisibilty('copy')] },
    { text: 'DEFAULT', id: 'paste', visible: [handleVisibilty('paste')] },
    { text: 'SINGLE_SELECT', id: 'bold', visible: [handleVisibilty('bold')] },
    { text: 'MULTI_SELECT', id: 'italic', visible: [handleVisibilty('italic')] },
  ], [searchInputTemplate, handleVisibilty]);

  return (
    <ToolbarComponent items={items} />
  );
};

Key changes and explanations:
  • We've created a separate SearchInput component that's memoized using React.memo. This component will only re-render when its props change.
  • The TextBoxComponent is now rendered inside this SearchInput component, and its ref and initial setup are handled within this component's useEffect hook.
  • We've moved the handleSearchInput function outside of the searchInputTemplate and memoized it with useCallback.
  • The searchInputTemplate now simply returns the SearchInput component with the handleSearchInput function passed as a prop.
  • We've wrapped the items array in a useMemo hook to prevent unnecessary re-creation of this array on every render.
  • The handleVisibilty function is now memoized with useCallback to prevent unnecessary re-creation.

These changes should prevent the TextBox from losing focus when you type, as the component instance is now preserved between renders. The blinking effect should also be eliminated as we're not forcing a re-focus on every render.


Loader.
Up arrow icon