Search code examples
javascriptreactjsarraysobject

Add item value to item type in React


I have this object that can have multiple values inside:

const [sort, setSort] = useState({
    "city": [],
    "price": [],
    "year": []
});

When you click "add", I want "city" to have this value for example: ["Los Angeles"] or both ["Los Angeles", "New York"]

How can I do that?

I have tried this but it doesn't work. I'm newbie in javascript/reactjs.

import { useState, useEffect } from "react";

function Items() {
    const [sort, setSort] = useState({
        "city": [],
        "price": [],
        "year": []
    });

    useEffect(() => {
        console.log(sort);
    }, [sort])


    function addItem(itemType, itemValue)
    {
        for (var type in sort)
        {
            if(type.hasOwnProperty(type))
            {
                if(type.toLowerCase() === itemType.toLowerCase())
                {
                    sort.type.push(itemValue);
                    setSort(sort);
                    break;
                }
            }
        }
    }

    return (
        <>
            <div className="item" onClick={(e) => addItem(e, "City", "Los Angeles")}>
                <span>Add LA</span>
            </div>
            <div className="item" onClick={(e) => addItem(e, "City", "New York")}>
                <span>Add NY</span>
            </div>
        </>
    )
}

export default Items

Solution

  • You have a few issues:

    1. First, addItem should be a callback, so wrap it in the useCallback hook.
    2. Next, You should use the callback version of setSort so that you do not need sort as a dependency
    3. Finally, you can simplify your state setting logic (ES5/6)

    Note: You should avoid using var; stick with const and let

    import { useState, useEffect, useCallback } from "react";
    
    const Items = () => {
      const [sort, setSort] = useState({
        city: [],
        price: [],
        year: []
      });
    
      useEffect(() => {
        console.log(sort);
      }, [sort]);
    
      const addItem = useCallback((_event, itemType, itemValue) => {
        setSort((currentSort) => {
          const key = itemType.toLowerCase(),
            existing = currentSort[key] ?? [];
          return { ...currentSort, [key]: [...existing, itemValue] };
        });
      }, []);
    
      return (
        <>
          <div className="item" onClick={(e) => addItem(e, "City", "Los Angeles")}>
            <span>Add LA</span>
          </div>
          <div className="item" onClick={(e) => addItem(e, "City", "New York")}>
            <span>Add NY</span>
          </div>
        </>
      );
    }
    
    export default Items;
    

    Here is a demo of the code above:

    const { Fragment, useState, useEffect, useCallback } = React;
    
    const Items = () => {
      const [sort, setSort] = useState({
        city: [],
        price: [],
        year: []
      });
    
      useEffect(() => {
        console.log(JSON.stringify(sort));
      }, [sort]);
    
      const addItem = useCallback((_event, itemType, itemValue) => {
        setSort((currentSort) => {
          const key = itemType.toLowerCase(),
            existing = currentSort[key] || [];
          return { ...currentSort, [key]: [...existing, itemValue] };
        });
      }, []);
    
      return (
        <Fragment>
          <div className="item" onClick={(e) => addItem(e, "City", "Los Angeles")}>
            <span>Add LA</span>
          </div>
          <div className="item" onClick={(e) => addItem(e, "City", "New York")}>
            <span>Add NY</span>
          </div>
        </Fragment>
      );
    };
    
    const App = () => (
      <div>
        <Items />
      </div>
    );
    
    ReactDOM
      .createRoot(document.getElementById('root'))
      .render(<App />);
    <div id="root"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>

    Here is an example of not allowing duplicates. Just use the Set object to store the existing array, modify it, and convert it back into an array.

    const { Fragment, useState, useEffect, useCallback } = React;
    
    const Items = () => {
      const [sort, setSort] = useState({
        city: [],
        price: [],
        year: []
      });
    
      useEffect(() => {
        console.log(JSON.stringify(sort));
      }, [sort]);
    
      const addItem = useCallback((_event, itemType, itemValue) => {
        setSort((currentSort) => {
          const key = itemType.toLowerCase(),
            existing = new Set(currentSort[key] || []);
          existing.add(itemValue);
          return { ...currentSort, [key]: [...existing] };
        });
      }, []);
      
      const removeItem = useCallback((_event, itemType, itemValue) => {
       setSort((currentSort) => {
          const key = itemType.toLowerCase(),
            existing = new Set(currentSort[key] || []);
          existing.delete(itemValue);
          return { ...currentSort, [key]: [...existing] };
        });
      }, []);
    
      return (
        <div className="grid">
          <div className="item" onClick={(e) => addItem(e, "City", "Los Angeles")}>
            <span>Add LA</span>
          </div>
          <div className="item" onClick={(e) => addItem(e, "City", "New York")}>
            <span>Add NY</span>
          </div>
          <div className="item" onClick={(e) => removeItem(e, "City", "Los Angeles")}>
            <span>Remove LA</span>
          </div>
          <div className="item" onClick={(e) => removeItem(e, "City", "New York")}>
            <span>Remove NY</span>
          </div>
        </div>
      );
    };
    
    const App = () => (
      <div>
        <Items />
      </div>
    );
    
    ReactDOM
      .createRoot(document.getElementById('root'))
      .render(<App />);
    html, body, #root {
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
    }
    
    #root {
      display: flex;
      justify-content: center;
    }
    
    .grid {
      display: grid;
      grid-template-columns: repeat(2, auto);
      grid-row-gap: 0.5rem;
      grid-column-gap: 2rem;
    }
    
    .item {
      display: flex;
      justify-content: center;
      align-items: center;
    }
    <div id="root"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>