Skip to content

Why react-simplikit matters

Among the many React-based libraries, why should you choose react-simplikit? Let's explore our core values and understand why using react-simplikit is equivalent to writing React in a React-like way.

Declarative Interface

React components have evolved from class components to function components.

With the introduction of function components and declarative API hooks, we can now abstract state and lifecycle-related logic that was previously written in a complex way in class components.

However, React components are still complex. Since React provides minimal interfaces, components with even slightly complex functionality may require dozens of states, handlers, and side effect definitions based on state changes.

At some point, components become mixed with concerns and are written imperatively, making it increasingly difficult to understand what the component doing and what logic is running.

react-simplikit provides appropriate abstractions for frequently used but complex-to-implement features. This allows you to maintain intuitive readability even when writing components with complex logic.

react-simplikit presents interfaces that declaratively solve various problems commonly encountered during actual service development.

Based on this, it guides developers to write more declarative React components.

tsx
function AutoCompleteInput() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState<SearchResult[]>([]);
  const [isLoading, setIsLoading] = useState(false);
  const [isOpen, setIsOpen] = useState(false);
  const searchTimeoutRef = useRef<NodeJS.Timeout>(null);
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const handleClickOutside = (e: MouseEvent) => {
      if (
        containerRef.current &&
        !containerRef.current.contains(e.target as Node)
      ) {
        setIsOpen(false);
      }
    };

    document.addEventListener('click', handleClickOutside);
    return () => document.removeEventListener('click', handleClickOutside);
  }, []);

  useEffect(() => {
    if (searchTimeoutRef.current) {
      clearTimeout(searchTimeoutRef.current);
    }

    if (query.trim().length === 0) {
      setResults([]);
      return;
    }

    setIsLoading(true);
    searchTimeoutRef.current = setTimeout(async () => {
      try {
        const response = await fetch(`/api/search?q=${query}`);
        const data = await response.json();
        setResults(data);
      } catch (error) {
        console.error('Failed to fetch results:', error);
      } finally {
        setIsLoading(false);
      }
    }, 300);

    return () => {
      if (searchTimeoutRef.current) {
        clearTimeout(searchTimeoutRef.current);
      }
    };
  }, [query]);

  return (
    <div ref={containerRef} className="relative">
      <input
        type="text"
        value={query}
        onChange={e => {
          setQuery(e.target.value);
          setIsOpen(true);
        }}
        onFocus={() => setIsOpen(true)}
        placeholder="Enter search term"
      />
      {isOpen && (isLoading || results.length > 0) && (
        <div>
          {isLoading ? (
            <div className="p-2">Searching...</div>
          ) : (
            results.map((result, idx) => (
              <Fragment key={result.id}>
                <div
                  onClick={() => {
                    setQuery(result.title);
                    setIsOpen(false);
                  }}
                >
                  {result.title}
                </div>
                {idx !== results.length - 1 && <Divider />}
              </Fragment>
            ))
          )}
        </div>
      )}
    </div>
  );
}
tsx
function AutoCompleteInput() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState<SearchResult[]>([]);
  const [isLoading, startLoading] = useLoading();
  const [isOpen, openSearchBox, closeSearchBox] = useBooleanState(false);

  const searchBoxState = useMemo(() => {
    if (!isOpen) return 'CLOSE';

    if (isLoading) return 'LOADING';

    if (results.length > 0) return 'RESULT_EXISTS';

    return 'EMPTY';
  }, [isOpen, isLoading, results]);

  const searchResults = useDebounce(async (searchQuery: string) => {
    if (searchQuery.trim().length === 0) {
      setResults([]);
      return;
    }

    const response = await fetch(`/api/search?q=${searchQuery}`)
      .then(res => res.json())
      .catch(error => {
        console.error('Failed to fetch results:', error);
        return [];
      });

    setResults(response);
  }, 300);

  const containerRef = useRef<HTMLDivElement>(null);
  useOutsideClickEffect(containerRef.current, () => closeSearchBox());

  return (
    <div ref={containerRef} className="relative">
      <input
        type="text"
        value={query}
        onChange={e => {
          setQuery(e.target.value);
          openSearchBox();
          startLoading(searchResults(e.target.value));
        }}
        onFocus={openSearchBox}
        placeholder="Enter search term"
      />
      <SwitchCase
        value={searchBoxState}
        caseBy={{
          LOADING: () => <div>Searching...</div>,
          EMPTY: () => <div>No results found.</div>,
          RESULT_EXISTS: () => (
            <Separated with={<Divider />}>
              {results.map(result => (
                <Fragment key={result.id}>
                  <div
                    onClick={() => {
                      setQuery(result.title);
                      closeSearchBox();
                    }}
                  >
                    {result.title}
                  </div>
                </Fragment>
              ))}
            </Separated>
          ),
          CLOSE: () => null,
        }}
      />
    </div>
  );
}

Small Bundle Size

Fast response time is crucial for web services. That's why small bundle size is very important for react-simplikit, a library for building web services. react-simplikit strives to provide the smallest possible bundle size now and in the future.

Compared to react-use, react-simplikit has up to about 89% smaller size:

react-simplikitreact-useDifference
Unpacked Size131 kB454 kB-71.1%
Minified Size8.5 kB78.2 kB-89.1%
Gzipped Size2.8 kB22 kB-87.2%
Average Size per Function
(Minified Size)
350 byte680 byte-48.5%

Released under the MIT License.