tbleckert / react-select-search

⚡️ Lightweight select component for React
https://react-select-search.com
MIT License
675 stars 149 forks source link

Confirming Approach for Query Change Notification & Option Selection #174

Closed AlexBasile123 closed 3 years ago

AlexBasile123 commented 3 years ago

First, I want to say that I really like the minimal approach and yet a lot of features.

I've been playing around w/ it for a while and now questioning whether it can handle my needs.

My needs are pretty simple:

  1. Type into search text box. Updates the dropdown options and grid below
  2. Dropdown options are constructed from 3 collections (top 3 of each), based on the value in the search text box
  3. Selecting a option from dropdown, should update the grid below and (would be nice to) show selection in search text box
  4. The selection can be cleared by clicking on an X or something

Originally I was looking for an event on each key press. Then reading through the past issues I've picked up on using getOptions() to get search query (per key stroke) and update both the options (from 3 collections) and update grid below.

Shortly after I ran into infinite rerendering issue, where updating the options, updates the state, which updates this component and it's options, rinse repeat. I've resolved it by separating the context of state and dispatch; as well as checking that the previous query was different from the current query.

Afterwards, working on selecting an option was straightforward at first. i.e. onChange(). But then ran into an issue w/ showing the selection in the search text box. The search text box would clear and typing into it again wouldn't work the same. I've tried closeOnSelect=false, which would keep at least show the selection option.

Then going back to the api I thought maybe there's a better way to interact w/ this component:

  1. Use getOptions() to just save the query typed into the search text box. (Although maybe there's an easier why to get that value?)
  2. Try out filterOptions, and move the logic for determining the options there But something isn't setup right as it keeps freezing up on me. I had to kill the browser tab and start again

I'm assuming my needs are not unusual and my approach may needs some work. If not, maybe I'm asking for too much from this compact component. Thank you in advance for your advise! Really appreciate it.

------- Snippet ----- <SelectSearch value={selectedValue} options={searchEntities} search closeOnSelect={false} placeholder="Type in your search criteria" onChange={(query) => { previousSearchQuery = ''; // attempt to block reloading getOptions setSearchQuery(query); } } filterOptions={(options) => { return new Promise((resolve, reject) => { change(searchQuery) // updates dropdown options and grid below .then(resolve(searchEntities)) .catch(reject);
}) } } getOptions={(query) => { return new Promise((resolve, reject) => { searchQuery = query; // assign to state resolve(searchEntities); }); }} />

icaroscherma commented 3 years ago

Hey @AlexBasile123 , just a few recommendations:

So, to fix your issue, I believe you will need:

import React;
import SelectSearch, {fuzzySearch} from 'react-select-search';

const [value, setValue] = React.useState('');
const options = [
  // your options here
];

return <div>
  <SelectSearch
    value={value}
    options={options}
    onChange={setValue}
    search
    filterOptions={fuzzySearch}
    closeOnSelect={false}
    placeholder="Type in your search criteria"
  />
</div>;
AlexBasile123 commented 3 years ago

@icaroscherma Thanks! Will try it out.

icaroscherma commented 3 years ago

@AlexBasile123 Please don't forget to let us know if it worked out for you. ;)

AlexBasile123 commented 3 years ago

@icaroscherma Thanks, your advise helped a lot! Select Search component is working much better when using the proper events. i.e. rather than using getOptions to get the search text I'm now using filterOptions for both getting the search text and setting up the options.

Although I did have to add a guard that checks if the user has entered anything new in the textbox; by comparing previousSearchText to searchText. Otherwise, fizzySearch is called everytime a state changes, and that can lead to infinite rerendering. This type of guard was especially useful during page load, when the options aren't suppose to have values yet at this point.

I'm now working on keeping the value in the search text box when user clicks away. That is, the text box will show selected value when option is selected or while user is typing (has focus). But will loose the value if user clicks away (lost focus). It's obviously important to hold that value for continuity. i.e. user wants to add or remove letters.

I think we can close this ticket as your answer resolves the approach question posted. Thanks again!

icaroscherma commented 3 years ago

Are you using Custom Hooks ? Because the way you described it doesn't seem that you're using import SelectSearch..., it seems that you're using import {useSelect.... Otherwise there's no need to do this whole thing, the library already takes care of all of those events seamlessy.

--

I'm doing a custom component (hook-based) from scratch using tailwind as css classes to demonstrate to other people how powerful the library is even if you need to change/monkeypatch a ton of stuff. @tbleckert ( didn't want to ping him :P ) was helping me debug/fix a few small issues I was finding on my way. Probably the next couple days I will be PR-ing it.

AlexBasile123 commented 3 years ago

Hey @AlexBasile123 , just a few recommendations:

  • If you check issue #171 I showed how to use fuzzySearch as your main search filter engine
  • When you're pasting some code on Github, please use one of the flavoured markdown helpers:

    • For inline code, like this: useSelect({}); you should one ONE grave accent character at the beginning and another one at the end of your code.
    • For block code (what I used in issue 171 and what I will use below), you have to start the line with 3 grave accent character, tell which language you're working on, break line, paste your code and then 3 grave accent characters again.
  • getOptions it's a way to use ajax to query your results, if you have the whole list with you, just use options and both search and filterOptions will do the trick for you.

So, to fix your issue, I believe you will need:

import React;
import SelectSearch, {fuzzySearch} from 'react-select-search';

const [value, setValue] = React.useState('');
const options = [
  // your options here
];

return <div>
  <SelectSearch
    value={value}
    options={options}
    onChange={setValue}
    search
    filterOptions={fuzzySearch}
    closeOnSelect={false}
    placeholder="Type in your search criteria"
  />
</div>;
icaroscherma commented 3 years ago

I just see a quote from my first response, did you mistakenly comment without typing anything? 😛

AlexBasile123 commented 3 years ago

@icaroscherma Yeah, it's good to hear that the library is supposed to handle it!

I'm not doing any kind of custom hooks AFAIK. I'm a bit above noob in react so maybe I'm not fully aware. This is how I'm importing SelectSearch: import SelectSearch from 'react-select-search';

The complete imports are:

import React, { useContext } from 'react';
import SelectSearch from 'react-select-search';
import { GlobalContext } from "../context/GlobalState";
import { DispatchContext } from "../context/GlobalState";
import './select-search.css';
import Fuse from 'fuse.js';

I am using Redux/Reducer to manage state; vs. your recommendation of useState. But I don't think that state management choice would be the reason. Please correct me if I'm wrong.

Here's my latest configurations. Please let me know if you need any other info.

<SelectSearch
    value={selectedValue}
    className="select-search select-search--multiple"
    options={searchEntities}
    renderOption={renderOption}
    search
    closeOnSelect={false}
    placeholder="Search something"
    onChange=
        {(optionSelected) => {
            lock = true; // added my own guard to limit state change race conditions
            setSearchQuery(optionSelected);
            lock = false;
        }
    }
    filterOptions={fuzzySearch} // my own implementation that creates the options and changes state
/>
icaroscherma commented 3 years ago

Can you provide a small sample of how your options dataset are looking like? Then I can build a single page with build-in handlers that you can tackle on it.

I'm not doing any kind of custom hooks AFAIK.

Yup, you're not using from what I've seen in your last comment. :P

--

A quick somewhat offtopic tip is that your lock var is being changed inside a scope that won't be trackable, this is why you should use Redux or Hooks (i.e. [lock,setLock] = useState(false);) , because if you need something triggered/re-rendered when it changes, it will trigger properly.

Everything else is looking fine, I just don't see why re-implement fuzzysearch and search input in this case. :)

AlexBasile123 commented 3 years ago

Thanks for the quick response! I can replicate it w/ your sample data. Also repeatable when I comment out onChange and filterOptions

const options = [
    { name: 'Annie Cruz', value: 'annie.cruz', photo: 'https://randomuser.me/api/portraits/women/60.jpg' },
    { name: 'Eli Shelton', disabled: true, value: 'eli.shelton', photo: 'https://randomuser.me/api/portraits/men/7.jpg' },
    { name: 'Loretta Rogers', value: 'loretta.rogers', photo: 'https://randomuser.me/api/portraits/women/51.jpg' },
    { name: 'Lloyd Fisher', value: 'lloyd.fisher', photo: 'https://randomuser.me/api/portraits/men/34.jpg' },
    { name: 'Tiffany Gonzales', value: 'tiffany.gonzales', photo: 'https://randomuser.me/api/portraits/women/71.jpg' },
 ];

Thanks for the advise of using lock var. I only need it in this component. Trying to limit exposure to rerendering. Seems to be doing the trick, but will keep it in mind. Btw, deselecting option is not a showstopper for me. Please feel free to get to it when it's a good time for you.

icaroscherma commented 3 years ago

@AlexBasile123 Sorry it took forever to create this simple example for you, I've been busy + sick lately. But better days are ahead of us.

https://codesandbox.io/s/select-search-3-test-skjec

In the codesandbox example I'm using both single and multiple selection, in both you can search and it works.

@tbleckert But I noticed that if multiple={true} or closeOnSelect={false}, it actually keeps it open forever, which is somewhat unexpected for me, shouldn't it close anyway as you click out of the <Select> area?

tbleckert commented 3 years ago

@icaroscherma This is a tricky one. As this project started out, the goal was to be a drop-in replacement for the HTML select. Therefor when you set multiple, the select box is always open. Later on more options came, mainly closeOnSelect and printOptions.

So, in this case closeOnSelect is unrelated, since as you stated, it's always open.

Moving on to printOptions you have these settings available:

auto is the default. Auto basically means "please, decide for me". So circling back to the original idea, auto means this:

So for this use case, you would set printOptions="on-focus". closeOnSelect will only close the select if it can be closed.

Hope that makes sense, might have to clarify it in the docs. Would be great to get feedback on this as well, maybe it only makes sense in my head :P Now that I think of it, might be better to drop the logic and just let the user decide.

And as always thanks for the help, really appreciate it.

@AlexBasile123 Thanks for the feedback and let me know if I can assist with anything! But looks like @icaroscherma got you covered.

icaroscherma commented 3 years ago

Yup, it makes sense. I remember checking those params before but I totally forgot about them today. =) I updated the codesandbox link with the parameters I think he's looking for.

AlexBasile123 commented 3 years ago

@AlexBasile123 Sorry it took forever to create this simple example for you, I've been busy + sick lately. But better days are ahead of us.

https://codesandbox.io/s/select-search-3-test-skjec

In the codesandbox example I'm using both single and multiple selection, in both you can search and it works.

@tbleckert But I noticed that if multiple={true} or closeOnSelect={false}, it actually keeps it open forever, which is somewhat unexpected for me, shouldn't it close anyway as you click out of the <Select> area?

@icaroscherma No worries. Thanks for putting it together. Hope you're feeling better!