English 中文(简体)
React useCallback - Select component rerendering after selecting option - not wanted
原标题:

I have a HTML select drop down list (SortBy component), after selecting an option, the component rerenders, this is not wanted, I want to see the option I selected but it keeps reseting (rerendering) to the first option. I ve tried useCallback with no luck

When A-Z or Price: Low to high is selected, I want that value retained in the HTML select box but it defaults back to Relevance

function App() {
  const [items, setItems] = useState(products);
  console.log(products);

  const onChange = useCallback((e) => {
      const { value } = e.target;
      let arr = items;

      switch (value) {
        case ("relevance", "saleFirst"):
          arr = saleItemsFirst(items, false);
          break;
        case "ascend":
          arr = sortByName(items);
          break;
        case "price":
          arr = sortByPrice(items);
          break;
  
        default:
          arr = saleItemsFirst(items);
          break;
      }

      setItems([...arr]);
    },
    [items]
  );

  const SortBy = useCallback(() => {
    return (
      <select name="filters" onChange={onChange}>
        <option value="relevance">Relevance</option>
        <option value="ascend">A-Z</option>
        <option value="priceLow">Price: Low to high</option>
        <option value="saleFirst">Sales items first</option>
      </select>
    );
  }, [onChange]);

  return (
    <div className="App">
      <SortBy />
      <hr />
      <section>
        {items.map((val) => (
          <div>
            <span>{val.productName}</span> <div>${val.price}</div>
            <hr />
          </div>
        ))}
      </section>
      ......
问题回答

The Problem: SortBy is getting re-defined whenever items changes because items is in the dependency array for your useCallback, which tells it when to re-run, re-defining the returned function reference. When that happens, it s a whole new component instance, and it s being remounted with initial values.

items has to be in your dependency array if you define a function this way, otherwise, when items changes, your memoized (useCallback d) component will be stale.

The Solution: Rather than declaring SortBy inside of the App component, you ll need to declare it outside, and pass in any dependencies it needs as props. That way, the SortBy component will be a module-level constant, and it will only mount once when App mounts.

Takeaway: It s generally a bad practice to define components inside of other components. Avoid it. The only possible exception are very dimple (like, one-liner) components that have no state concerns. Even then I d advise defining them outside other components.

I d echo what Faust says and strongly advise you to move your components out of App, but I d also suggest that you rethink the way you re keeping track of the sort.

Instead of keeping the sorted list items in state you could just keep track of which sort option is selected and memoize the sorted item list, updating it when the selected sort changes:

// we need a list of items to display
const items = Array.from({ length: 5 }, (_, i) => `Item ${i + 1}`);

// a mapping of sort names to sort functions
const sortOptions = {
  ascending: (a, b) => a.localeCompare(b),
  descending: (a, b) => b.localeCompare(a),
  random: () => Math.random() - 0.5,
}

// the component
function MyList () {

  // keep track of which sort name is selected
  const [sortName, setSortName] = React.useState();
  
  // re-sort the item list when the sort name changes
  const sortedItems = React.useMemo(() => {
    // look up the sort function by name and pass it to array.sort()
    return [...items].sort(sortOptions[sortName]);
  }, [sortName]);
  
  return (
    <div>
      {/* update the sort name on change, and set the value of the select */}
      <select onChange={(e) => setSortName(e.target.value)} value={sortName}>
      
        {/* render an option for each item in sortOptions */}
        {Object.keys(sortOptions).map(sortName => (
          <option key={sortName}>{sortName}</option>
        ))}
        
      </select>
      
      {/* render the list of items */}
      <ol>
        { sortedItems.map(item => <li key={item}>{item}</li>) }
      </ol>
    </div>
  );
}


ReactDOM.render(<MyList />, document.getElementById( root ))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>


<div id="root"></div>




相关问题
CSS working only in Firefox

I am trying to create a search text-field like on the Apple website. The HTML looks like this: <div class="frm-search"> <div> <input class="btn" type="image" src="http://www....

image changed but appears the same in browser

I m writing a php script to crop an image. The script overwrites the old image with the new one, but when I reload the page (which is supposed to pickup the new image) I still see the old one. ...

Firefox background image horizontal centering oddity

I am building some basic HTML code for a CMS. One of the page-related options in the CMS is "background image" and "stretch page width / height to background image width / height." so that with large ...

Separator line in ASP.NET

I d like to add a simple separator line in an aspx web form. Does anyone know how? It sounds easy enough, but still I can t manage to find how to do it.. 10x!

热门标签