Home > Net >  Add item value to item type in React
Add item value to item type in React

Time:01-25

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

CodePudding user response:

You can't change the state value directly. So you need to copy the state, then do changes and then updateState

change this part of code

sort.type.push(itemValue);
setSort(sort);

To this

setSort(oldData => {
    const newData = {...oldData};
    newData[itemType].push(itemValue);

    return newData;
});

if you have nested objects, make sure to do deepClone of your object instead of spread operator

CodePudding user response:

This will sort it

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

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


  function addItem(itemType, itemValue) {
    console.log("itemType", itemType, itemValue);
    // check for property
    if (sort?.[itemType] !== undefined) {
      const items = sort?.[itemType];

      if (
        !items.map((e) => e.toLowerCase()).includes(itemValue.toLowerCase())
      ) {
        // creating new array on change
        setSort((s) => {
          return {
            ...s,
            // set updated property
            [itemType]: [...sort?.[itemType], itemValue]
          };
        });
      }
    }
  }

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

You can check the new beta docs on how to work with arrays

Hope it helps

CodePudding user response:

The easiest method maybe to take the existing state from setSort and use that to build (using the spread syntax) a new object.

// Accept the type and value
function addItem(itemType, itemValue) {
  
  // For convenience create a key by lowercasing the type
  const key = itemType.toLowerCase();

  // Take the previous state, and...
  setSort(prev => {

    // ...return a new object by spreading out
    // that previous state, and then using the key to
    // update that array but creating a new one by
    // also spreading out that property into a new array
    // and adding the new value to it
    return {
      ...prev,
      [key]: [ ...prev[key], itemValue ]
    }
  });
}

Full example. Note: you don't need to pass the event into the function if you don't use it.

const { useEffect, useState } = React;

function Items() {

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

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

  function addItem(itemType, itemValue) {
    const key = itemType.toLowerCase();
    setSort(prev => {
      return {
        ...prev,
        [key]: [ ...prev[key], itemValue ]
      }
    });
  }

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

ReactDOM.render(
  <Items />,
  document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

CodePudding user response:

Don't change state directly, React.js will not re-render if not use setState, When you have a object like that "sort" you could consider use useReducer.

CodePudding user response:

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>

  • Related