Ooker
Ooker

Reputation: 2994

How to deactivate a function when a focusOut event happens?

Goal

I want to have two search bars, and each of them can:

Because of the last bullet I don't want to use <datalist> tag as it's limited in style. I also use Deno Fresh, which is based on Preact, which is based on React. At the time I wasn't aware of guides on navigate through list by arrow keys in React, only in vanilla JS, and I think using the vanilla version will be simpler, so currently I'm using it.

If you are interested in how to achieve the goal in general and not the question in title, check out my final solution.

Problem

When a list is navigated by keyboard, the other is also triggered. My idea is to have the script targets different lists, and when users unfocus an input field it will be deactivated. I try having this on the script:

if (list === 'deactivate') return

And on each handler I have:

onBlur={(e) => script('deactivate')}

But it still doesn't work. Do you have any suggestion?

Code

search.jsx:

export default function SearchBar() {
  const [keyword1, setKeyword1] = useState("");
  const [keyword2, setKeyword2] = useState("");
  
  const list1 = [ 'rabbits', 'raccoons', 'reindeer', 'red pandas', 'rhinoceroses', 'river otters', 'rattlesnakes', 'roosters' ] 
  const list2 = [ 'jacaranda', 'jacarta', 'jack-o-lantern orange', 'jackpot', 'jade', 'jade green', 'jade rosin', 'jaffa' ]

  return (
    <div className="search-bar-container">
      <input
        type="text"
        placeholder={'Search list 1'}
        value={keyword1}
        onInput={(e) => {
          setKeyword1(e.target.value);
          script('1');
        }}
        onBlur={(e) => script('deactivate')}
        />
      <br />
      <ul id={'Suggested list 1'}>
        {keyword1 !== '' ? list1.filter(keyword => keyword.includes(keyword1)).map(
          (item) => <li>{item}</li>,
        ) : ''}
      </ul>
      <div>
        Your selected item is :<span id="Item 1"></span>
      </div>
      <input
        type="text"
        placeholder={'Search list 2'}
        value={keyword2}
        onInput={(e) => {
          setKeyword2(e.target.value);
          script('2');
        }}
        onBlur={(e) => script('deactivate') }
      />
      <br />
      <ul id={'Suggested list 2'}>
        {keyword2 !== '' ? list2.filter(keyword => keyword.includes(keyword2)).map(
          (item) => <li>{item}</li>,
        ): ''}
      </ul>
      <div>
        Your selected item is :<span id="Item 2"></span>
      </div>
    </div>
  );
}

script.ts:

export function script(list: "1" | "2" | 'deactivate') {
  if (list === 'deactivate') return
  if (window !== undefined) {
      const ul = document.getElementById(`Suggested list ${list}`);
      const result = document.getElementById(`Item ${list}`);
//the rest of the script

Full code. Somehow CodeSandbox keeps having twind error even when I don't. This does run in my machine.

Screenshot (GIF)

Upvotes: 0

Views: 108

Answers (1)

jepozdemir
jepozdemir

Reputation: 509

You need to manage the active state for each list separately. You can add state variables to track the active list and then conditionally execute the navigation logic based on the active list. Here's your modified code:

export function script(list: "1" | "2", selectedItem: string) {
  console.log(`Selected item for list ${list}: ${selectedItem}`);
  // Your script logic here
}

And search.jsx:

import { useState } from "react";
import { script } from "./script"; // Import the script function

export default function SearchBar() {
  const [keyword1, setKeyword1] = useState("");
  const [keyword2, setKeyword2] = useState("");
  const [activeList, setActiveList] = useState(null); // State to track active list
  const [selectedItem1, setSelectedItem1] = useState(null); // State to track selected item for list 1
  const [selectedItem2, setSelectedItem2] = useState(null); // State to track selected item for list 2

  const list1 = ['rabbits', 'raccoons', 'reindeer', 'red pandas', 'rhinoceroses', 'river otters', 'rattlesnakes', 'roosters'];
  const list2 = ['jacaranda', 'jacarta', 'jack-o-lantern orange', 'jackpot', 'jade', 'jade green', 'jade rosin', 'jaffa'];

  const handleKeyDown = (e, list) => {
    if (e.key === "ArrowDown" || e.key === "ArrowUp") {
      e.preventDefault();
      setActiveList(list); // Set active list
    } else if (e.key === "Enter" && activeList === list) {
      // Handle selection when Enter key is pressed
      if (list === '1') {
        setSelectedItem1(keyword1);
        script('1', keyword1);
      } else if (list === '2') {
        setSelectedItem2(keyword2);
        script('2', keyword2);
      }
    }
  };

  const handleItemClick = (item, list) => {
    if (list === '1') {
      setSelectedItem1(item);
    } else if (list === '2') {
      setSelectedItem2(item);
    }
    script(list, item);
  };

  return (
    <div className="search-bar-container">
      <input
        type="text"
        placeholder={'Search list 1'}
        value={keyword1}
        onInput={(e) => {
          setKeyword1(e.target.value);
          setActiveList('1'); // Set active list
        }}
        onBlur={() => setActiveList(null)} // Deactivate list when input loses focus
        onKeyDown={(e) => handleKeyDown(e, '1')} // Pass list identifier to handleKeyDown
      />
      <br />
      <ul id={'Suggested list 1'} style={{ display: activeList === '1' ? 'block' : 'none' }}> {/* Conditionally render based on active list */}
        {keyword1 !== '' ? list1.filter(keyword => keyword.includes(keyword1)).map(
          (item, index) => (
            <li key={index} onClick={() => handleItemClick(item, '1')}>{item}</li>
          )
        ) : ''}
      </ul>
      <div>
        Your selected item is :<span id="Item 1">{selectedItem1}</span>
      </div>
      <div className="search-bar-container">
        <input
          type="text"
          placeholder={'Search list 2'}
          value={keyword2}
          onInput={(e) => {
            setKeyword2(e.target.value);
            setActiveList('2'); // Set active list
          }}
          onBlur={() => setActiveList(null)} // Deactivate list when input loses focus
          onKeyDown={(e) => handleKeyDown(e, '2')} // Pass list identifier to handleKeyDown
        />
        <br />
        <ul id={'Suggested list 2'} style={{ display: activeList === '2' ? 'block' : 'none' }}> {/* Conditionally render based on active list */}
          {keyword2 !== '' ? list2.filter(keyword => keyword.includes(keyword2)).map(
            (item, index) => (
              <li key={index} onClick={() => handleItemClick(item, '2')}>{item}</li>
            )
          ) : ''}
        </ul>
        <div>
          Your selected item is :<span id="Item 2">{selectedItem2}</span>
        </div>
      </div>
    </div>
  );
}

Upvotes: 2

Related Questions