Currently I have implemented it using plain javascript array. But there seems to be few problems.
onChange, onDelete has to iterate all over the array which could be better like O(1)
When some item gets deleted key property of other items get changed. I'm not sure if it's bad or not.
Using immutable.js can get rid of problem 1. Is number 2 really a problem? Is there a better alternative than immutable.js?
Maybe I'm not asking the right questions, what's the best way?
import React, { useState } from "react";
export default function() {
const [rows, setRows] = useState([""]);
const onChange = (e, i) => {
setRows(rows.map((row, index) => (index !== i ? row : e.target.value)));
};
const onDelete = i => {
setRows(rows.filter((_, index) => i !== index));
};
return (
<>
{rows.map((row, index) => {
return (
<div key={index}>
<input value={row} onChange={e => onChange(e, index)} />
{index !== 0 && (
<button onClick={() => onDelete(index)}>
- delete row
</button>
)}
</div>
);
})}
<button onClick={() => setRows(rows.concat([""]))}>
+ add row
</button>
</>
);
}
UPDATE
I tried using immutable-js OrderedMap. Now key property of elements won't change and onChange and onDelete won't iterate over everything. Is this better than before?
import React, { useState } from "react";
import { OrderedMap } from "immutable";
export default function() {
const [inputState, setInputState] = useState({
rows: OrderedMap(),
nextKey: Number.MIN_VALUE,
});
function onChange(k, v) {
setInputState({
...inputState,
rows: inputState.rows.update(k, () => v),
});
}
function addRow() {
const { rows, nextKey } = inputState;
setInputState({
rows: rows.set(nextKey, ""),
nextKey: nextKey + 1,
});
}
function deleteItem(k) {
setInputState({
...inputState,
rows: inputState.rows.delete(k),
});
}
return (
<>
{inputState.rows.entrySeq().map(([k, v]) => {
return (
<div key={k}>
<input
value={v}
onChange={({ target: { value } }) => {
onChange(k, value);
}}
/>
<button onClick={() => deleteItem(k)}>-</button>
</div>
);
})}
<button onClick={addRow}>+ add row</button>
</>
);
}
UPDATE: 2
Also tried using plain javascript Map.
import React, { useState } from "react";
export default function() {
const [inputState, setInputState] = useState({
rows: new Map(),
nextKey: Number.MIN_VALUE,
});
function onChange(k, v) {
const { rows, nextKey } = inputState;
rows.set(k, v);
setInputState({
nextKey,
rows,
});
}
function addRow() {
const { rows, nextKey } = inputState;
rows.set(nextKey, "");
setInputState({
rows,
nextKey: nextKey + 1,
});
}
function deleteItem(k) {
const { rows, nextKey } = inputState;
rows.delete(k);
setInputState({
nextKey,
rows,
});
}
const uiList = [];
for (const [k, v] of inputState.rows.entries()) {
uiList.push(
<div key={k}>
<input
value={v}
onChange={({ target: { value } }) => {
onChange(k, value);
}}
/>
<button onClick={() => deleteItem(k)}>-</button>
</div>
);
}
return (
<>
{uiList}
<button onClick={addRow}>+ add row</button>
</>
);
}
UPDATE 3
Using plain array but with key property.
import React, { useState } from "react";
export default function() {
const [inputState, setInputState] = useState({
rows: [],
nextKey: Number.MIN_VALUE,
});
function onChange(i, v) {
const { rows, nextKey } = inputState;
rows[i].value = v;
setInputState({
nextKey,
rows,
});
}
function addRow() {
const { rows, nextKey } = inputState;
rows.push({ key: nextKey, value: "" });
setInputState({
rows,
nextKey: nextKey + 1,
});
}
function deleteItem(i) {
const { rows, nextKey } = inputState;
rows.splice(i, 1);
setInputState({
nextKey,
rows,
});
}
return (
<>
{inputState.rows.map(({ key, value }, index) => {
return (
<div key={key}>
<input
value={value}
onChange={({ target: { value } }) => {
onChange(index, value);
}}
/>
<button onClick={() => deleteItem(index)}>-</button>
</div>
);
})}
<button onClick={addRow}>+ add row</button>
</>
);
}
Instead of using external js, i just do the little modification of the code, instead of doing the map or filter, you can use directly with splice,
the below code will help to do the deletion fast and it will keep the same index after deletion also.
import React, { useState } from "react";
export default function() {
const [rows, setRows] = useState([""]);
const onChange = (e, i) => {
const {value} = e.target;
setRows(prev => {
prev[i] = value;
return [...prev];
});
};
const onDelete = i => {
setRows(prev => {
prev.splice(i,1, undefined);
return [...prev];
});
};
return (
<>
{rows.map((row, index) => (typeof row !== "undefined") && (
<div key={index}>
<input value={row} onChange={e => onChange(e, index)} />
{index !== 0 && (
<button onClick={() => onDelete(index)}>
- delete row
</button>
)}
</div>
))}
<button onClick={() => setRows(rows.concat([""]))}>
+ add row
</button>
</>
);
}
So far, best choices are
Generate key to use as key.
Use map to have efficient update and delete.
Don't mutate the state.
The immutable library fits the point 2 and 3 perfectly. Since it can update and delete without mutation efficiently it seems like a good choice.
import React, { useState, useEffect } from "react";
import { OrderedMap } from "immutable";
function UsingImmutableJS({ onChange }) {
const [inputState, setInputState] = useState({
rows: OrderedMap(),
nextKey: 0,
});
useEffect(() => {
if (onChange) onChange([...inputState.rows.values()]);
}, [inputState, onChange]);
function onInputChange(k, v) {
setInputState(prev => ({
...prev,
rows: prev.rows.update(k, () => v),
}));
}
function addRow() {
setInputState(prev => ({
nextKey: prev.nextKey + 1,
rows: prev.rows.set(prev.nextKey, ""),
}));
}
function deleteItem(k) {
setInputState(prev => ({
...prev,
rows: prev.rows.delete(k),
}));
}
return (
<>
{inputState.rows.entrySeq().map(([k, v]) => {
return (
<div key={k}>
<input
value={v}
onChange={({ target: { value } }) => {
onInputChange(k, value);
}}
/>
<button onClick={() => deleteItem(k)}>-</button>
</div>
);
})}
<button onClick={addRow}>+ add row</button>
<button onClick={() => console.log([...inputState.rows.values()])}>
print
</button>
</>
);
}
Without index we can do using the Map and Map retrieval is faster, below code you can try.
import React, { useState } from "react";
export default function() {
const [rows, setRows] = useState(new Map());
const onUpdate = (key, value) => {
setRows(prev => new Map(prev).set(key, { key, value}));
}
const onDelete = (key) => {
setRows(prev => new Map(prev).delete(key));
}
return (
<>
{[...rows].map((row, index) => (typeof row[1].value !== "undefined") && (
<div key={index}>
<input value={row[1].value} onChange={e => onUpdate(row[1].key, e.target.value)} />
<button onClick={() => onDelete(row[1].key)}>- delete row</button>
</div>
))}
<button onClick={() => onUpdate(rows.size, "")}>
+ add row
</button>
</>
);
}
Related
I'm making a todo list in react js. Each time a new todo item is created, some buttons are appended next to it along with a edit input text box. I'm trying to avoid using refs but purely usestate for my case, however I can't figure out how to do it. At its current state, all edit text inputs are using the same state and that brings focus loss along with other issues. I'd highly appreciate any suggetsions.
import "./theme.css"
import * as appStyles from "./styles/App.module.css"
import * as todoStyles from "./styles/Todo.module.css"
import { useState } from "react"
const initialState = [
{
id: "1",
name: "My first ToDo",
status: "new",
},
]
export function App() {
const [numofItems, setNumofItems] = useState(2)
const [newToDo, setnewToDo] = useState('');
const [todos, setTodos] = useState(initialState);
const [editTodo, setEditTodo] = useState({name: ""});
const onAddTodo = () => {
setnewToDo("");
setTodos((old) => [
...old,
{ id: numofItems.toString(), name: newToDo, status: "new" },
])
setNumofItems(numofItems + 1);
}
deleteList = () =>{
setTodos([]);
}
const handleEdit = (id, description) =>{
let el = todos.map((item) => {if(item.id === id) {item.name = description} return item});
setTodos(el);
setEditTodo('');
}
const handleMove = (id, position) =>{
const search = obj => obj.id === id;
const todoIndex = todos.findIndex(search);
if(position === "up"){
if (todos[todoIndex - 1] === undefined) {
} else {
const newTodo1 = [...todos];
const temp1 = newTodo1[todoIndex - 1];
const temp2 = newTodo1[todoIndex]
newTodo1.splice(todoIndex - 1, 1, temp2);
newTodo1.splice(todoIndex, 1, temp1);
setTodos([...newTodo1]);
}
}
else if(position === "down"){
if (todos[todoIndex + 1] === undefined) {
} else {
const newTodo1 = [...todos];
const temp1 = newTodo1[todoIndex + 1];
const temp2 = newTodo1[todoIndex]
newTodo1.splice(todoIndex + 1, 1, temp2);
newTodo1.splice(todoIndex, 1, temp1);
setTodos([...newTodo1]);
}
}
}
const Todo = ({ record }) => {
return <li className={todoStyles.item}>{record.name}
<button className={appStyles.editButtons} onClick={() => deleteListItem(record.id)} >Delete</button>
<button className={appStyles.editButtons} onClick={() => handleEdit(record.id, editTodo.name)}>Edit</button>
<button className={appStyles.editButtons} onClick={() => handleMove(record.id, "down")}>Move down</button>
<button className={appStyles.editButtons} onClick={() => handleMove(record.id, "up")}>Move up</button>
<input className={appStyles.input}
type = "text"
name={`editTodo_${record.id}`}
value = {editTodo.name}
onChange={event => {event.persist();
setEditTodo({name: event.target.value});}}
/></li>
}
const deleteListItem = (todoid) => {
setTodos(todos.filter(({id}) => id !== todoid))
}
return (
<>
<h3 className={appStyles.title}>React ToDo App</h3>
<ul className={appStyles.list}>
{todos.map((t, idx) => (
<Todo key={`todo_${idx}`} record={t} />
))}
</ul>
<div className={appStyles.actions}>
<form>
<label>
Enter new item:
<input className={appStyles.input} type="text" name="newToDo" value={newToDo} onChange={event => setnewToDo(event.target.value)}/>
</label>
</form>
<button
className={appStyles.button}
onClick={onAddTodo}
>
Add
</button>
<br></br>
<button className={appStyles.button} onClick={this.deleteList}>
Delete List
</button>
</div>
</>
)
}
Never define components in the body of another component. It will result in unmount/mount of that element every time it's rendered.
Here is how you can split up the Todo component from you App:
const Todo = ({ record, onDelete, onEdit, onMove }) => {
const [inputValue, setInputValue] = useState(record.name);
return (
<li className={todoStyles.item}>
{record.name}
<button className={appStyles.editButtons} onClick={() => onDelete()}>
Delete
</button>
<button
className={appStyles.editButtons}
onClick={() => onEdit(inputValue)}
>
Edit
</button>
<button className={appStyles.editButtons} onClick={() => onMove("down")}>
Move down
</button>
<button className={appStyles.editButtons} onClick={() => onMove("up")}>
Move up
</button>
<input
className={appStyles.input}
type="text"
value={inputValue}
onChange={(event) => {
setInputValue(event.target.value);
}}
/>
</li>
);
};
function App() {
return (
<>
<ul className={appStyles.list}>
{todos.map((t, idx) => (
<Todo
key={`todo_${idx}`}
record={t}
onDelete={() => deleteListItem(t.id)}
onEdit={(description) => handleEdit(t.id, description)}
onMove={(position) => handleMove(t.id, position)}
/>
))}
</ul>
</>
);
}
Note: I've shown only the interesting bits, not your entire code.
If you're going to do it this way I would suggest using useReducer instead of useState.
const initialState = [
{
id: "1",
name: "My first ToDo",
status: "new",
},
]
export const types = {
INIT: 'init',
NEW: 'new'
}
export default function (state, action) {
switch (action.type) {
case types.INIT:
return initialState;
case types.NEW:
return { ...state, { ...action.item } };
default:
return state;
}
}
Now in your component you can use it like this:
import {useReducer} from 'react';
import reducer, { initialState, types } from './wherever';
const [state, dispatch] = useReducer(reducer, initialState);
const handleSubmit = (event) => {
event.preventDefault();
dispatch({ type: types.NEW, item: event.target.value });
}
I'm trying to get the updated/newly created records and send it to the backend in "queryparam"
import React, { useState, useEffect } from "react";
//import { Container, Row, Col } from "reactstrap";
// import Box from "#mui/material/Box";
// import "bootstrap/dist/css/bootstrap.css";
// import "./index.css";
const Index = () => {
const [formValues, setFormValues] = useState([
{ orderno: 0, inputValue1: "", inputValue2: "", checked: false }
]);
const [isDisabled, setDisabled] = useState(false);
// const [inputVal1, setInputval1] = useState();
const [isChanged, setIsChanged] = useState([]);
const [error, setError] = useState(false);
const [orderNumber, setOrderNumber] = useState(1);
const addFormFields = () => {
// if (error) {
// setDisabled(false)
// }
// else {
// setDisabled(true)
// }
setFormValues((prevState) => [
...prevState,
{
orderno: orderNumber,
inputValue1: "",
inputValue2: "",
checked: false
}
]);
setOrderNumber((prev) => prev + 1);
};
const removeFormFields = (i) => {
let newFormValues = [...formValues];
newFormValues.splice(i, 1);
setFormValues(newFormValues);
setOrderNumber((prev) => prev - 1);
};
const onChangeFieldValue = (index, key, value) => {
setFormValues((prevState) => {
let copyState = [...prevState];
if (value?.length > 0) {
setError(false);
} else {
setError(true);
}
copyState[index][key] = value;
return copyState;
});
};
const saveFields = (e) => {
const queryparam = {
inputData: formValues
};
setIsChanged(queryparam);
setIsChanged((prevState, nextState) => {
let copyState = [];
if (prevState === nextState) {
copyState = [...prevState];
} else {
copyState = [...nextState];
}
return copyState;
});
console.log(isChanged, "lllllllll");
};
// useEffect(() => {
// saveFields()
// }, [isChanged])
return (
<>
{formValues.map((element, index) => (
<div className="form-inline" key={index}>
{/* <Container>
<Row>
<Col xs="12" sm="6"> */}
<label>{index + 1}</label>
<input
type="text"
value={element.inputVal1}
onChange={(e) =>
onChangeFieldValue(index, "inputValue1", e.target.value)
}
/>
<input
type="text"
value={element.inputVal2}
required
onChange={(e) =>
onChangeFieldValue(index, "inputValue2", e.target.value)
}
/>
{/* </Col>
<Col xs="12" sm="6">
<Box> */}
<button
className={`button ${error ? "add" : "btn-secondary"}`}
type="button"
disabled={error}
onClick={(e) => addFormFields(e)}
>
Add{console.log(isDisabled, "ooooooo", error)}
</button>
<button
type="button"
className="button remove"
onClick={() => removeFormFields(index)}
>
Remove
</button>
{/* </Box>
</Col>
</Row>
</Container> */}
</div>
))}
{/* <Row>
<Col sm="6" md={{ size: 4, offset: 2 }}>
<Box> */}
<button
type="button"
className="button save"
onClick={(e) => saveFields(e)}
>
Save
</button>
<button
type="button"
className="button remove"
//onClick={(e) => cancelFields(e)}
>
cancel
</button>
{/* </Box>
</Col>
</Row> */}
</>
);
};
export default Index;
https://codesandbox.io/s/black-fire-ixeir?file=/src/App.js:3662-3701
In the above link,
Step1 : when I add values for inputs "123" in input1 and "345" in input2.Then when I click on "Save" the values sent are {"input1":"123","input2":"345"}.
Step2: Again I try to add one row for inputs "456" in input1 and "678" in input2.Then when I click on save the values sent are {"input1":"456","input2":"678"}.
When I edit the existing row, for example the first row values and when I click on "Save" then only the first row value should be sent as the second row values hasn't changed.Also, If I add new rows then the newly added only should be sent if the existing row values aren't changed. Is there any way to send only the updated/newly created values to the backend using react hook
You could use a separate changes object to track changes by orderno property; saved during add/update/remove, and committed when submitting.
const [changes, setChanges] = useState({});
...
const addFormFields = () => {
const newItem = {
orderno: orderNumber,
inputValue1: "",
inputValue2: "",
checked: false,
type: "add"
};
setFormValues((values) => [...values, newItem]);
setChanges((changes) => ({
...changes,
[newItem.orderno]: newItem
}));
setOrderNumber((prev) => prev + 1);
};
const removeFormFields = (index) => {
const item = {
...formValues[index],
type: "remove"
};
setFormValues((values) => values.filter((el, i) => i !== index));
setChanges((changes) => ({
...changes,
[item.orderno]: item
}));
};
const onChangeFieldValue = (index, key, value) => {
const item = {
...formValues[index],
[key]: value,
type: "edit"
};
setFormValues((prevState) => {
if (value?.length > 0) {
setError(false);
const copyState = [...prevState];
copyState[index] = item;
return copyState;
} else {
setError(true);
return prevState;
}
});
setChanges((changes) => ({
...changes,
[item.orderno]: item
}));
};
const saveFields = (e) => {
const queryparam = {
inputData: Object.values(changes)
};
console.log("Changes to commit", queryparam);
setChanges({});
};
I am building a clone of the Google Keep app with react js. I added all the basic functionality (expand the create area, add a note, delete it) but I can't seem to manage the edit part. Currently I am able to edit the inputs and store the values in the state, but how can I replace the initial input values for the new values that I type on the input?
This is Note component
export default function Note(props) {
const [editNote, setEditNote] = useState(false);
const [currentNote, setCurrentNote] = useState({
id: props.id,
editTitle: props.title,
editContent: props.content,
});
const handleDelete = () => {
props.deleteNote(props.id);
};
const handleEdit = () => {
setEditNote(true);
setCurrentNote((prevValue) => ({ ...prevValue }));
};
const handleInputEdit = (event) => {
const { name, value } = event.target;
setCurrentNote((prevValue) => ({
...prevValue,
[name]: value,
}));
};
const updateNote = () => {
setCurrentNote((prevValue, id) => {
if (currentNote.id === id) {
props.title = currentNote.editTitle;
props.content = currentNote.editContent;
} else {
return { ...prevValue };
}
});
setEditNote(false);
};
return (
<div>
{editNote ? (
<div className='note'>
<input
type='text'
name='edittitle'
defaultValue={currentNote.editTitle}
onChange={handleInputEdit}
className='edit-input'
/>
<textarea
name='editcontent'
defaultValue={currentNote.editContent}
row='1'
onChange={handleInputEdit}
className='edit-input'
/>
<button onClick={() => setEditNote(false)}>Cancel</button>
<button onClick={updateNote}>Save</button>
</div>
) : (
<div className='note' onDoubleClick={handleEdit}>
<h1>{props.title}</h1>
<p>{props.content}</p>
<button onClick={handleDelete}>DELETE</button>
</div>
)}
</div>
);
}
And this is the Container component where I am renderind the CreateArea and mapping the notes I create. I tried to map the notes again with the new values but it wasn't working.
export default function Container() {
const [notes, setNotes] = useState([]);
const addNote = (newNote) => {
setNotes((prevNotes) => {
return [...prevNotes, newNote];
});
};
const deleteNote = (id) => {
setNotes((prevNotes) => {
return prevNotes.filter((note, index) => {
return index !== id;
});
});
};
// const handleUpdateNote = (id, updatedNote) => {
// const updatedItem = notes.map((note, index) => {
// return index === id ? updatedNote : note;
// });
// setNotes(updatedItem);
// };
return (
<div>
<CreateArea addNote={addNote} />
{notes.map((note, index) => {
return (
<Note
key={index}
id={index}
title={note.title}
content={note.content}
deleteNote={deleteNote}
//handleUpdateNote={handleUpdateNote}
/>
);
})}
</div>
);
}
There are a couple of mistakes in your code.
The state properties are in the camel case
const [currentNote, setCurrentNote] = useState({
...
editTitle: props.title,
editContent: props.content,
});
But the names of the input are in lowercase.
<input
name='edittitle'
...
/>
<textarea
name='editcontent'
...
/>
Thus in handleInputEdit you don't update the state but add new properties: edittitle and editcontent. Change the names to the camel case.
In React you cant assign to the component prop values, they are read-only.
const updateNote = () => {
...
props.title = currentNote.editTitle;
props.content = currentNote.editContent;
You need to use the handleUpdateNote function passed by the parent component instead. You have it commented for some reason.
<Note
...
//handleUpdateNote={handleUpdateNote}
/>
Check the code below. I think it does what you need.
function Note({ id, title, content, handleUpdateNote, deleteNote }) {
const [editNote, setEditNote] = React.useState(false);
const [currentNote, setCurrentNote] = React.useState({
id,
editTitle: title,
editContent: content,
});
const handleDelete = () => {
deleteNote(id);
};
const handleEdit = () => {
setEditNote(true);
setCurrentNote((prevValue) => ({ ...prevValue }));
};
const handleInputEdit = (event) => {
const { name, value } = event.target;
setCurrentNote((prevValue) => ({
...prevValue,
[name]: value,
}));
};
const updateNote = () => {
handleUpdateNote({
id: currentNote.id,
title: currentNote.editTitle,
content: currentNote.editContent
});
setEditNote(false);
};
return (
<div>
{editNote ? (
<div className='note'>
<input
type='text'
name='editTitle'
defaultValue={currentNote.editTitle}
onChange={handleInputEdit}
className='edit-input'
/>
<textarea
name='editContent'
defaultValue={currentNote.editContent}
row='1'
onChange={handleInputEdit}
className='edit-input'
/>
<button onClick={() => setEditNote(false)}>Cancel</button>
<button onClick={updateNote}>Save</button>
</div>
) : (
<div className='note' onDoubleClick={handleEdit}>
<h1>{title}</h1>
<p>{content}</p>
<button onClick={handleDelete}>DELETE</button>
</div>
)}
</div>
);
}
function CreateArea() {
return null;
}
function Container() {
const [notes, setNotes] = React.useState([
{ title: 'Words', content: 'hello, bye' },
{ title: 'Food', content: 'milk, cheese' }
]);
const addNote = (newNote) => {
setNotes((prevNotes) => {
return [...prevNotes, newNote];
});
};
const deleteNote = (id) => {
setNotes((prevNotes) => {
return prevNotes.filter((note, index) => {
return index !== id;
});
});
};
const handleUpdateNote = ({ id, title, content }) => {
const _notes = [];
for (let i = 0; i < notes.length; i++) {
if (i === id) {
_notes.push({ id, title, content });
} else {
_notes.push(notes[i]);
}
}
setNotes(_notes);
};
return (
<div>
<CreateArea addNote={addNote} />
{notes.map((note, index) => {
return (
<Note
key={index}
id={index}
title={note.title}
content={note.content}
deleteNote={deleteNote}
handleUpdateNote={handleUpdateNote}
/>
);
})}
</div>
);
}
function App() {
return (
<div>
<Container />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://unpkg.com/react#17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>
Also, you can store the notes in an object or hash map instead of an array. For example
const [notes, setNotes] = React.useState({
'unique_id': { title: 'Words', content: 'hello, bye' }
});
Then in handleUpdateNote you have
setNotes((prev) => ({ ...prev, unique_id: { title, content } }))
I need to click on the button to delete the line with the selected checkbox.
I don't know how to use the filter method. I read the documentation, but there is little information there. help me correct the code
class Table extends Component {
constructor(props) {
super(props);
this.state = {
droplets: [],
allSelected: false,
isChecked: false
}
this.toggleSelectAll = this.toggleSelectAll.bind(this);
this.toggleSelect = this.toggleSelect.bind(this);
this.handleChecked = this.handleChecked.bind(this);
**this.handleDelete = this.handleDelete.bind(this);**
}
async componentDidMount() {
const res = await fetch('http://api.npoint.io/324f4ca2cdd639760638');
const droplets = await res.json();
this.setState({ 'droplets': droplets })
}
toggleSelect(dropletToToggle) {
this.setState({isChecked: !this.state.isChecked});
this.setState((prevState) => {
const newDroplets = prevState.droplets.map((dplt) => {
if (dplt === dropletToToggle) {
return { ...dplt, checked: !dplt.checked };
}
return dplt;
});
return {
...prevState,
droplets: newDroplets,
allSelected: newDroplets.every((d) => !!d.checked)
};
});
}
toggleSelectAll() {
this.setState({isChecked: !this.state.isChecked});
this.setState((prevState) => {
const toggle = !prevState.allSelected;
const newDroplets = prevState.droplets.map((x) => ({
...x,
checked: toggle
}));
return {
...prevState,
droplets: newDroplets,
allSelected: toggle
};
});
}
handleChecked () {
this.setState({isChecked: !this.state.isChecked});
}
**handleDelete = isChecked => {
this.setState(state => {
const { droplets } = state;
const filteredDroplets = droplets.filter(item => item.id !== isChecked);
return {
droplets: filteredDroplets
};
});
};**
render() {
return (
<div className="body">
<div className="title">Таблица пользователей</div>
<table className="panel">
<Tablehead
toggleSelectAll={this.toggleSelectAll}
allSelected={this.state.allSelected}
/>
<tbody className="row">
<TableBody
droplets={this.state.droplets}
toggleSelect={this.toggleSelect}
/>
</tbody>
</table>
**<button className="button" onClick = {this.handleDelete} >Удалить выбранные</button>**
</div>
);
}
}
the second file in which the item to delete
const TableBody = ({ droplets, toggleSelect}) => {
return (
<>
{droplets.map((droplet, item) => (
<tr className={s.area} key={item.id} >
<td>
<Checkbox
handleClick={() => toggleSelect(droplet)}
isChecked={!!droplet.checked}
/>
</td>
<td>{droplet.num}</td>
<td>{droplet.first_name + " " + droplet.last_name}</td>
<td>{date_form(droplet.date_of_birth)}</td>
<td>{height_form(droplet.height)}</td>
<td>{weight_form(droplet.weight)}</td>
<td>{salary_form(droplet.salary)}</td>
<td>
<button type="submit" className={s.button}>
<Edit />
</button>
</td>
<td>
<button type="submit" className={s.button}>
<Trash />
</button>
</td>
</tr>
))}
</>
);
};
https://codesandbox.io/s/sweet-butterfly-0s4ff?file=/src/Table.jsx
I have altered your sandbox and added some functionality in order to delete the rows.
The comparison is based on full name but you can change it to whatever fits your needs.
https://codesandbox.io/s/keen-silence-i51wz
On your handleDelete function your filter condition seems wrong
Currently you have:
// Filters all droplets that have an id different to the value of isChecked.
const filteredDroplets = droplets.filter(item => item.id !== isChecked);
And it should be
// Returns all the droplets that are not checked
// Meaning that all the checked items are dropped from the array
const filteredDroplets = droplets.filter(item => !item.isChecked);
How to add sort to the data by using a button
import React, { useState } from "react";
import { Container, Row, Col } from "reactstrap";
import EmployeeCard from "./EmployeeCard";
import data from "./data.json";
import Modal from "./Modal";
import Subheader from "./Subheader";
import "./Employeelist.css";
function Employeelist() {
const [state, setState] = useState({ search: null });
const searchSpace = event => {
let keyword = event.target.value;
setState({ search: keyword });
};
const employeeCards = data.employee
.filter(data => {
if (state.search == null) return data;
else if (
data.firstName.toLowerCase().includes(state.search.toLowerCase()) ||
data.lastName.toLowerCase().includes(state.search.toLowerCase())
) {
return data;
}
})
.map(person => {
return (
<Col sm="4">
<EmployeeCard key={person.id} person={person} />
</Col>
);
});
return (
<div>
<input
type="text"
placeholder="Search"
class="elementStyle"
onChange={e => searchSpace(e)}
/>
<br />
<br />
<br />
<Container fluid>
<Row>{employeeCards}</Row>
</Container>
</div>
);
}
export default Employeelist;
Add some state to hold the sorted/unsorted data and a sortActive boolean, and a handler to toggle the sort active or not. Use an effect to sort/unsort using the sortActive as a dependency.
Also fixed other discrepancies, I tried noting them.
function Employeelist() {
const [state, setState] = useState({ search: null });
const searchSpace = event => {
let keyword = event.target.value;
setState({ search: keyword });
};
// state to hold sorted/unsorted data array, sortActive, and handler
const [sortedData, setSortedData] = useState(data.employee);
const [sortActive, setSortActive] = useState(false);
const toggleSortActiveHandler = () => setSortActive(a => !a);
useEffect(() => {
if (sortActive) {
setSortedData(
// Gotcha: array::sort is an in-place sort, meaning it mutates
// the existing array.
// Spreading it into a new array then sorting addresses this.
[...data.employee].sort((a, b) => {
if (a.lastName < b.lastName) return -1;
if (a.lastName > b.lastName) return 1;
return 0;
})
);
} else {
setSortedData(data.employee);
}
}, [sortActive]);
const employeeCards = sortedData
.filter(data => {
// filter returns true/false if element should be filtered
if (state.search == null) return true;
return (
data.firstName.toLowerCase().includes(state.search.toLowerCase()) ||
data.lastName.toLowerCase().includes(state.search.toLowerCase())
);
})
.map(person => {
return (
<Col key={person.id} sm="4"> { /* <-- react key on outer most element */ }
<EmployeeCard person={person} />
</Col>
);
});
return (
<div>
<input
type="text"
placeholder="Search"
className="elementStyle"
onChange={e => searchSpace(e)}
/>
<br />
<br />
<br />
<Container fluid>
<Row>{employeeCards}</Row>
</Container>
{ /* button to toggle the sort */ }
<button type="button" onClick={toggleSortActiveHandler}>
Toggle Alpha Sort
</button>
</div>
);
}