ReactJS: управление несколькими входами флажков с помощью useState - PullRequest
2 голосов
/ 28 февраля 2020

У меня есть следующий пример компонента, который использует несколько флажков для выбора элементов для удаления из списка объектов:

import React, { useState } from "react";
import "./styles.css";

const data = [
  {
    name: "test1",
    result: "pass"
  },
  {
    name: "test2",
    result: "pass"
  },
  {
    name: "test3",
    result: "pass"
  },
  {
    name: "test4",
    result: "pass"
  },
  {
    name: "test5",
    result: "pass"
  }
];

export default function App() {
  const [allChecked, setAllChecked] = useState(false);
  const [isChecked, setIsChecked] = useState({});
  const [formData, setFormData] = useState(data);

  const handleAllCheck = e => {
    setAllChecked(e.target.checked);
  };

  const handleSingleCheck = e => {
    setIsChecked({ ...isChecked, [e.target.name]: e.target.checked });
  };

  const onDelete = () => {
    console.log(isChecked);
    const newData = data.filter(
      item => !Object.keys(isChecked).includes(item.name)
    );
    console.log(newData);
    setFormData(newData);
  };

  return (
    <div className="App">
      <div>
        <label>All</label>
        <input
          name="checkall"
          type="checkbox"
          checked={allChecked}
          onChange={handleAllCheck}
        />
        <label />
      </div>
      {formData.map((test, index) => (
        <div key={index}>
          <label>{test.name}</label>
          <input
            type="checkbox"
            name={test.name}
            checked={allChecked ? true : isChecked[test.name]}
            onChange={handleSingleCheck}
          />
        </div>
      ))}
      <button onClick={() => onDelete()}>DELETE</button>
    </div>
  );
}

Это в основном работает, за исключением проверки всех. Кажется, onChange не будет обновляться при использовании useState. Мне нужно иметь возможность выбрать все объекты или снять отметки, чтобы пометить их для удаления.

Любая помощь очень ценится.

CodeSandbox Пример: https://codesandbox.io/s/modest-hodgkin-kryco

ОБНОВЛЕНИЕ:

Хорошо, после некоторой помощи Ричарда Матсена,

Вот новое решение без прямой манипуляции с DOM:

import React, { useState, useEffect } from "react";
import "./styles.css";

const data = [
  {
    name: "test1",
    result: "pass"
  },
  {
    name: "test2",
    result: "pass"
  },
  {
    name: "test3",
    result: "pass"
  },
  {
    name: "test4",
    result: "pass"
  },
  {
    name: "test5",
    result: "pass"
  }
];

export default function App() {
  const [allChecked, setAllChecked] = useState(false);
  const [isChecked, setIsChecked] = useState();
  const [loading, setLoading] = useState(true);
  const [formData, setFormData] = useState(data);

  const handleAllCheck = e => {
    setAllChecked(e.target.checked);
  };

  const handleSingleCheck = e => {
    setIsChecked({ ...isChecked, [e.target.name]: e.target.checked });
  };

  const onDelete = () => {
      const itemList = Object.keys(isChecked).map((key:any) => {
        if (isChecked[key] === true) {
          return key
        }
      })
      const result = formData.filter((item:any) => !itemList.includes(item.name))
      console.log(result)
      setFormData(result)
    }

  useEffect(() => {
    if (!loading) {
    setIsChecked(current => {
      const nextIsChecked = {}
      Object.keys(current).forEach(key => {
        nextIsChecked[key] = allChecked;
      })
      return nextIsChecked;
    });
    }
  }, [allChecked, loading]);

  useEffect(() => {
    const initialIsChecked = data.reduce((acc,d) => {
      acc[d.name] = false;
      return acc;
    }, {})
    setIsChecked(initialIsChecked)
    setLoading(false)
  }, [loading])

  return (
    <div className="App">
      <div>
        <label>All</label>
        <input
          name="checkall"
          type="checkbox"
          checked={allChecked}
          onChange={handleAllCheck}
        />
        <label />
      </div>
      {!loading ? formData.map((test, index) => (
      <div key={index}>
        <label>{test.name}</label>
        <input
          type="checkbox"
          name={test.name}
          checked={isChecked[test.name]}
          onChange={handleSingleCheck}
        />
      </div>
      )): null}
      <button onClick={() => onDelete()}>DELETE</button>
    </div>
  );
}

коды и ящик рабочего раствора: https://codesandbox.io/s/happy-rubin-5zfv3

Ответы [ 4 ]

3 голосов
/ 29 февраля 2020

Основная проблема c в том, что checked={allChecked ? true : isChecked[test.name]} останавливает действие отмены проверки - если allChecked истинно, не имеет значения, какое значение имеет isChecked[test.name], выражение всегда будет истинным.

Вы должны полагаться только на isChecked в качестве значения и рассматривать изменение allChecked как побочный эффект.

  useEffect(() => {
    setIsChecked(current => {
      const nextIsChecked = {}
      Object.keys(current).forEach(key => {
        nextIsChecked[key] = allChecked;
      })
      return nextIsChecked;
    });
  }, [allChecked]);

  ...

  {formData.map((test, index) => (
    <div key={index}>
      <label>{test.name}</label>
      <input
        type="checkbox"
        name={test.name}
        checked={isChecked[test.name]}
        onChange={handleSingleCheck}
      />
    </div>
  ))}

Также появляется это предупреждение

Предупреждение. Компонент изменяет неконтролируемый ввод типа флажка для управления. Элементы ввода не должны переключаться с неконтролируемого на управляемый (или наоборот). Выберите между использованием контролируемого или неконтролируемого входного элемента в течение срока службы компонента.

Так что в основном это говорит о том, что не инициализируйте isChecked в {}, потому что свойство проверенного ввода изначально не определено. Используйте это вместо

{
  test1: false,
  test2: false,
  test3: false,
  test4: false,
  test5: false,
}

или так

const data = { ... }

const initialIsChecked = data.reduce((acc,d) => {
  acc[d.name] = false;
  return acc;
}, {})

export default function App() {
  const [allChecked, setAllChecked] = useState(false);
  const [isChecked, setIsChecked] = useState(initialIsChecked);
...
1 голос
/ 28 февраля 2020

Проблема с вашим кодом заключалась в том, как вы обрабатывали allChecked. Я внес некоторые изменения в ваш код, и теперь он работает.

const data = [
  {
    name: "test1",
    result: "pass"
  },
  {
    name: "test2",
    result: "pass"
  },
  {
    name: "test3",
    result: "pass"
  },
  {
    name: "test4",
    result: "pass"
  },
  {
    name: "test5",
    result: "pass"
  }
];

function App() {
  const [allChecked, setAllChecked] = useState(false);
  // using an array to store the checked items
  const [isChecked, setIsChecked] = useState([]);
  const [formData, setFormData] = useState(data);

  const handleAllCheck =  e => {
      if (allChecked) {
        setAllChecked(false);
        return setIsChecked([]);
      }
      setAllChecked(true);
      return setIsChecked(formData.map(data => data.name));
    };

  const handleSingleCheck = e => {
    const {name} = e.target;
    if (isChecked.includes(name)) {
      setIsChecked(isChecked.filter(checked_name => checked_name !== name));
      return setAllChecked(false);
    }
    isChecked.push(name);
    setIsChecked([...isChecked]);
    setAllChecked(isChecked.length === formData.length)
  };

  const onDelete = () => {
    const data_copy = [...formData];
    isChecked.forEach( (checkedItem) => {
        let index = formData.findIndex(d => d.name === checkedItem)
        delete data_copy[index]
      }
    )
    setIsChecked([])
    // filtering out the empty elements from the array
    setFormData(data_copy.filter(item => item));
    setAllChecked(isChecked.length && isChecked.length === data.length);
  };

  return (
    <div className="App">
      <form>
        <label>All</label>
        <input
          name="checkall"
          type="checkbox"
          checked={allChecked}
          onChange={handleAllCheck}
        />
        { formData.map((test, index) => (
          <div
          key={index}
          >
            <label>{test.name}</label>
            <input
              type="checkbox"
              name={test.name}
              checked={isChecked.includes(test.name)}
              onChange={handleSingleCheck}
            />
          </div>
          ))
        }
        <label />
      </form>
      <button onClick={onDelete}>DELETE</button>
    </div>
  );
}


0 голосов
/ 29 февраля 2020

Ну, после некоторого времени работы я придумал:

import React, { useState } from "react";
import "./styles.css";
import { useFormInputs } from "./checkHooks";

const data = [
  {
    name: "test1",
    result: "pass"
  },
  {
    name: "test2",
    result: "pass"
  },
  {
    name: "test3",
    result: "pass"
  },
  {
    name: "test4",
    result: "pass"
  },
  {
    name: "test5",
    result: "pass"
  }
];

export default function App() {
  const [fields, handleFieldChange] = useFormInputs({
    checkedAll: false
  });

  const allcheck = () => {
    const checkdata = document.querySelectorAll(".checkers").length;
    const numChecks = Array.from(new Array(checkdata), (x, i) => i);
    numChecks.map(item => {
      console.log(item);
      async function checkThem() {
        let element = await document.getElementsByClassName("checkers")[item];
        element.click();
      }
      return checkThem();
    });
  };

  return (
    <div className="App">
      <div>
        <label>All</label>
        <input name="checkall" type="checkbox" onChange={allcheck} />
        <label />
      </div>
      {data.map((test, index) => (
        <div key={index}>
          <label>{test.name}</label>
          <input
            className="checkers"
            type="checkbox"
            name={test.name}
            onChange={handleFieldChange}
          />
        </div>
      ))}
    </div>
  );
}

Relevent codesandbox: https://codesandbox.io/s/admiring-waterfall-0vupo

Любые предложения приветствуются. Также спасибо за помощь, ребята!

0 голосов
/ 28 февраля 2020

Я думаю, что вы должны объединить все переменные состояния StateChecked и isChecked, потому что они представляют собой одно и то же, но вы денормализуете его, создав два разных переменных! Я предлагаю сохранить isChecked и изменить все его записи, когда вы нажимаете ввод allChecked. Затем вы можете использовать производный var allChecked (определенный в вашем компоненте или с помощью ловушки useMemo), чтобы узнать, проверены ли все ваши проверки или нет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...