Динамическое добавление и удаление входных элементов с помощью React

#javascript #reactjs

#javascript #reactjs

Вопрос:

Допустим, у меня есть таблица:

 <div>
      <tr>
        <td>
          <p id='1' className="foo"> Boom </p>
        </td>
        <td>
          <p id='2' className="foo"> Bang </p>
        </td>
        <td>
          <p id='3' className="foobar"> Pew Pew </p>
        </td>
      </tr>
</div>
  

Я хочу, чтобы данные внутри него были доступны для редактирования на месте. Таким образом, я хочу заменить <p> элемент на an <input> , а затем <p> снова заменить его, но с новым значением. Я делал это с помощью jQuery, а теперь сделал это с помощью того, что мне кажется простым JS, но с React. Код выглядит следующим образом:

 import React, { Component } from "react";
import "./App.css";

class App extends Component {

  handleClick(e) {
    if (e.target.className === 'foo'){

      let element = document.getElementById(e.target.id)
      let element_value = element.innerText
      let parent_element = element.parentNode
      let new_element = document.createElement('input')

      parent_element.removeChild(element)
      parent_element.appendChild(new_element)
      new_element.setAttribute('class', 'input') 
      new_element.setAttribute('id', e.target.id)
      new_element.setAttribute('value', element_value)

    } else if (e.target.className === 'input') {
      
      let element = document.getElementById(e.target.id)
      let element_value = element.value
      let parent_element = element.parentNode
      let new_element = document.createElement('p')


      parent_element.removeChild(element)
      parent_element.appendChild(new_element)
      new_element.setAttribute('class', 'foo') 
      new_element.setAttribute('id', e.target.id)
      new_element.innerText = element_value
    }
  };

  componentDidMount() {
    document.addEventListener('dblclick', this.handleClick)
  }

  componentWillUnmount() {
    document.removeEventListener('dblclick', this.handleClick)
  }

  render() {
    return (
    <div>
      <tr>
        <td>
          <p id='1' className="foo"> Boom </p>
        </td>
        <td>
          <p id='2' className="foo"> Bang </p>
        </td>
        <td>
          <p id='3' className="foobar"> Pew Pew </p>
        </td>
      </tr>
    </div>
    )  
  }
}

export default App
  

Однако это не кажется мне хорошей практикой. Не могли бы вы дать мне советы о том, как улучшить / изменить мой подход? Спасибо.

Ответ №1:

Вероятно, лучшим подходом является создание управляемого компонента, который обрабатывает всю логику для редактируемой ячейки и сохраняет значения в родительской. Я создал изолированную среду, которую вы можете посмотреть здесь, но я также добавлю код сюда.

Таким образом, компонент cell предоставляет все необходимые элементы представления, а родительский элемент управляет логикой и данными для всех ячеек.

Таким образом, редактируемая ячейка обрабатывает функциональность переключения между представлениями:

 const EditableCell = ({ id, onEdit, className, value }) => {
  const [isEditing, setIsEditing] = useState(false);

  const onClick = useCallback(() => {
    setIsEditing(true);
  }, []);

  const onFinishedEditing = useCallback(() => {
    setIsEditing(false);
  }, []);

  const onKeyDown = useCallback(
    (e) => {
      if (e.key === "Enter") {
        onFinishedEditing();
      }
    },
    [onFinishedEditing]
  );

  return (
    <td>
      {isEditing ? (
        <input
          value={value}
          onChange={(e) => onEdit(e.target.value, id)}
          onBlur={onFinishedEditing}
          onKeyDown={onKeyDown}
          autoFocus
        />
      ) : (
        <p {...{ id, className, onClick }}>{value}</p>
      )}
    </td>
  );
};
  

А затем приложение сохраняет данные ячеек и отображает EditableCell для каждой из них:

 export default function App() {
  // This stores the cells values and properties, you can
  // add or remove cells here are needed
  const [cellValues, setCellValues] = useState([
    { id: "1", class: "foo", value: "Boom" },
    { id: "2", class: "foo", value: "Bang" },
    { id: "3", class: "foobar", value: "Pew Pew" }
  ]);

  const onEdit = (value, id) => {
    setCellValues(
      cellValues.map((cellVal) =>
        cellVal.id === id ? { ...cellVal, value } : cellVal
      )
    );
  };

  return (
    <div>
      Click a cell to edit
      <tr>
        {cellValues.map((cellVal) => (
          <EditableCell
            id={cellVal.id}
            value={cellVal.value}
            className={cellVal.class}
            onEdit={onEdit}
          />
        ))}
      </tr>
    </div>
  );
}
  

Это может не совсем соответствовать желаемой функциональности, но должно дать вам отправную точку

Комментарии:

1. Спасибо, это мне поможет 🙂

Ответ №2:

Я пообещал себе, что буду делать добрые дела хотя бы по одному в день.Я знаю, что вы пишете в классе, но я так сильно придерживаюсь крючков, что … извините, чувак: P

он вызывает onChange, когда во время редактирования вы нажимаете enter.

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

const Td = ({ children, editable = false, onChange, className, id }) => {
  const cell = useRef();
  const [edit, setEdit] = useState(false);
  const [value, setValue] = useState(() => {
    while (typeof children !== "string") {
      children = children.props.children;
    }
    return children;
  });
  const [oldValue, setOldValue] = useState(value);

  useEffect(() => {
    if (!cell.current) return;
    const onEditMode = () => editable amp;amp; setEdit(true);

    const target = cell.current;

    target.addEventListener("click", onEditMode);

    return () => {
      target.removeEventListener("click", onEditMode);
    };
  }, [cell, setEdit, editable]);

  const paragraph = useMemo(() => (
      <p id="1" className="foo">
        {value}
      </p>
    ),[value]);

  const input = useMemo(() => {
    const update = (value) => {
      setEdit(false);
      if (onChange amp;amp; typeof onChange === "function") {
        onChange({
          id,
          newValue: value,
          oldValue: oldValue
        });
        setOldValue(value);
      }
    }

    return (
      <input
        value={value}
        onChange={ e => setValue(e.target.value)}
        onKeyDown={ e => e.key === "Enter" amp;amp; update(value)}/>
      )
    },[value, setEdit, onChange, id, oldValue, setOldValue]);

  return (
    <td ref={cell} className={className}>
      {edit ? input : paragraph}
    </td>
  );
};

class App extends Component {
  componentDidMount() {
  }

  componentWillUnmount() {

  }

  tableCellValueChange({ id, newValue, oldValue }) {
    console.log(
      `table cell id: ${id} value changed from ${oldValue} to ${newValue}`
    );
  }

  render() {
    return (
      <div>
        <table>
          <thead></thead>
          <tbody>
            <tr>
              <Td 
                onChange={this.tableCellValueChange}
                id="special"
                editable>
                  <p>Bang </p>
              </Td>
              <Td onChange={this.tableCellValueChange} editable>
                <p>Bang</p>
              </Td>
              <Td editable={false} className="forbar">
                Pew Pew
              </Td>
            </tr>
          </tbody>
        </table>
      </div>
    );
  }
}

export default App;

  

здесь у вас есть живой пример песочницы

Комментарии:

1. спасибо, приятель, хуки — действительно интересная функция.