Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

remove value from form within component

I am trying to let users remove values from there current settings.

I have a series of values users can have for there settings that are displayed in a pill format that they can click X on to delete, i.e.,

export default function Pill({ value, handleChange }) {

  // const [times, setTimes] = useState([]);

    return (
      <div className="bg-gray-600 text-white text-xs px-2 py-0.5 w-max-content rounded-lg align-middle mr-1 mb-1">
        {value}
        <svg
          xmlns="http://www.w3.org/2000/svg"
          width="16"
          height="16"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          strokeWidth="2"
          strokeLinecap="round"
          strokeLinejoin="round"
          className="inline-block align-middle cursor-pointer"
          onClick={() => handleChange(value, "remove")}
        >
          <line x1="18" y1="6" x2="6" y2="18" />
          <line x1="6" y1="6" x2="18" y2="18" />
        </svg>
      </div>
    );
  }

This is used as a component as below,

{substanceDetails &&
              substanceDetails.map((subst) => (
                <Pill
                  registerInput={register}
                  optionLabel="substance"
                  value={subst.substance}
                  key={subst.substance}
                  optionName="substance"
                  allOptions={["Dexamphetamine", "Ritalin"]}
                  handleChange={handleChange}
                />
              ))}

Given the handle change, which needs to be changed, but it would look something like this,

const handleChange = (value, mode) => {
    let updatedValues = values;
    if (mode === 'remove') {
      updatedValues = values.filter((elem) => elem !== value);
    } else if (!updatedValues.includes(value)) updatedValues = [...values, value];
    setValues(updatedvalues);
  };
like image 399
LeCoda Avatar asked Oct 13 '21 10:10

LeCoda


2 Answers

Mutation issue

You are mutating state in your handleChange function:

const handleChange = (value, mode) => {
    let updatedValues = values; //takes the reference to state variable
    if (mode === 'remove') {
      updatedValues = values.filter((elem) => elem !== value); //mutation
    } else if (!updatedValues.includes(value)) updatedValues = [...values, value];
    setValues(updatedvalues);
  };

You can use something like this:

const handleChange = (value, mode) => {
    if(mode==="remove") setValues(vals => vals.includes(value) ? vals.filter(v => v !== value) : [...vals, value])
  };

If it is an array of objects:

.includes() won't work as expected if your array elements are not primitive values. You can use .find() or .some() in that case:

Example of filtering by the field duration for simplicity:

const handleChange = (value, mode) => {
    if(mode==="remove") setValues(vals => vals.find(v=> v.duration === value.duration) ? vals.filter(v => v.duration !== value.duration) : [...vals, value])
  };
like image 148
Sinan Yaman Avatar answered Sep 18 '22 16:09

Sinan Yaman


After reading your code, I found the main problem might not be the mutation issue, but you didn't manage the state very well.

First, your substanceDetails should be stored in YourSubstanceListComponent component, not in your Pill component, something like this. This is the main reason why your first approach does not work.

// in YourSubstanceListComponent
const YourSubstanceListComponent = () = {
  const [values, setValues] = useState([]);
  const handleChange = (value, mode,optionName) => {
    // Should call `setValues` in this function
    const newValues = [];// TODO
    setValues(newValues);
  }

  return (
    <>
      {values.map(v => <Pill {...otherPillProps} value={v.substance} />)}
    </>
  )
}

Then, your handleChange should be placed in YourSubstanceListComponent. The reason why is you might don't want to pass setValues to your Pill component.

Next is the part about getting new removed values items. According the docs:

  • Array.find
  • Array.filter

They don't modify the original array. So you can use them like in your code and it should work.

But, I found some more problems in your code and I think you should check it again.

  • Array.reduce will return your array to a single value. I saw that you used it to get a new array from an array. It makes no sense since you can use map (when the number of elements does not change) or filter (when the number of elements will change). I rarely use any other methods apart from map and filter in all my life.
  • In your updated handleChange function, I don't see the optionName variable. Where does it come from? Maybe you didn't update your Pill component.
like image 42
ShinaBR2 Avatar answered Sep 18 '22 16:09

ShinaBR2