This is my App
import React, { useState } from "react";
import Input from "./components/Input";
function App() {
const [newRow, setNewRow] = useState([]);
const addRow = (event) => {
event.preventDefault();
setNewRow(
newRow.concat(<Input key={newRow.length} myKey={newRow.length} />)
);
};
return (
<div>
<button onClick={addRow}>Add row</button>
<ul>
<Input key={newRow.length} myKey={newRow.length}/>
{newRow}
</ul>
</div>
);
}
export default App;
And this is my Input component
import "./Input.css";
import React, { useState } from "react";
const Input = (props) => {
const [updatedList, setUpdatedList] = useState([])
const deleteRow = (event) => {
event.preventDefault();
const key = props.myKey;
setUpdatedList(updatedList.splice(key, 1));
console.log(updatedList);
};
const disableRow = (event) => {
event.preventDefault();
};
return (
<li className="item">
<select>
<option value="+">+</option>
<option value="-">-</option>
</select>
<input type="text" />
<button onClick={deleteRow}>Delete</button>
<button onClick={disableRow}>Disable</button>
</li>
);
};
export default Input;
I guess I'm supposed to delete and disable the List HTML element through its key but I'm getting an empty array when I console.log(updatedList). So how can I delete from the original array in the parent component from the child component?
Add a deleteRow function to your App component.
// App.js
const deleteRow = (key) => {
setNewRow(newRow.filter(input => input.key != key));
}
Pass it as a prop to your child component and call it onClick.
// Input.js
const deleteRow = (event) => {
event.preventDefault();
const key = props.myKey;
props.deleteRow(key);
};
updatedList is unnecessary as you already have an array in the parent component.
Maybe have a look at context: https://reactjs.org/docs/context.html
It could help with your issue
The const [updatedList, setUpdatedList] = useState([]) is complete redundant. Instead, Lift the state by passing the newRow and the setNewRow directly into to the input component via props and manipulate it from there. Or pass in a function that manipulates it into the input component.
Also, you shouldn't call splice directly on the updatedList because it changes the contents of the list, and splice is an array which is a reference type.
It might be better to use filter(() => boolean) which returns a new array instead of mutation the current on. Or creating a new variable,
const updatedListCopy = [...updatedList]
and then call splice on that.
You should write deleteRow() as props from app.js
When you declare deleteRow() inside of individual Input component, that could not manipulate or update the parent values.
disableRow() is only usable for individual Input component, while deleteRow() relative with Parent newRow
App.js
import React, { useState } from "react";
import Input from "./components/Input";
function App() {
const [newRow, setNewRow] = useState([]);
const addRow = (event) => {
event.preventDefault();
setNewRow(
newRow.concat(<Input key={newRow.length} myKey={newRow.length} deleteRow={deleteRow} />)
);
};
const deleteRow = (key) => {
console.log(key)
}
return (
<div>
<button onClick={addRow}>Add row</button>
<ul>
<Input key={newRow.length} myKey={newRow.length}/>
{newRow}
</ul>
</div>
);
}
export default App;
In your component
import "./Input.css";
import React, { useState } from "react";
const Input = (props) => {
const [updatedList, setUpdatedList] = useState([])
const disableRow = (event) => {
event.preventDefault();
};
return (
<li className="item">
<select>
<option value="+">+</option>
<option value="-">-</option>
</select>
<input type="text" />
<button onClick={()=>props.deleteRow(props.key)}>Delete</button>
<button onClick={disableRow}>Disable</button>
</li>
);
};
export default Input;
Related
I am try to add search feature to an existing lists of robot names.
In order to do so I am trying to useState hooks. I have an App component and Header component which has the input tag for search field.
Error I am getting is 'InputEvent' is assigned a value but never used.
Below is the code for App component (main component).
import "./App.css";
import Header from "./Header";
import Robo from "./Robo";
import { robots } from "./robots";
import { useState } from "react";
function App() {
const [query, setQuery] = useState("");
const InputEvent = (e) => {
const data = e.target.value;
setQuery(data);
const extraction = robots
.filter((curElem, index) =>
robots[index].name.toLowerCase().includes(query)
)
.map((curElem, index) => {
return (
<Robo
key={robots[index].id}
id={robots[index].id}
name={robots[index].name}
email={robots[index].email}
/>
);
});
return (
<div className="App">
<Header query={query} InputEvent={InputEvent} />
<div className="robo-friends-container">{extraction};</div>
</div>
);
};
}
export default App;
Child component
import React from "react";
import "./header.css";
const Header = ({ query, InputEvent }) => {
return (
<>
<div className="headerText">ROBO FRIENDS</div>
<div>
<input
type="text"
id="lname"
name="lname"
placeholder="Search"
value={query}
onChange={InputEvent}
/>
</div>
</>
);
};
export default Header;
Here is my answer in stackblitz app
https://stackblitz.com/edit/stackoverflow-robots-filter?file=App.tsx,Robo.tsx,Header.tsx,robots.ts
I have altered the code a bit.. you can fork the project and play with it..
You can add debounce option to your input, which prevents unwanted re-renders
Adding the changes:
function App() {
const [query, setQuery] = useState(undefined);
const [filteredRobots, setFilteredRobots] = useState([]);
useEffect(() => {
console.log(query);
const filteredRobots = robots.filter((robot) => {
return robot.name.includes(query);
});
if (filteredRobots.length) {
setFilteredRobots(filteredRobots);
}
}, [query]);
const onQueryChange = (e) => {
const data = e.target.value;
setQuery(data);
};
const renderRobots = () => {
if (!query || !query.length) {
return <p>{'Search to find Robots'}</p>;
}
if (filteredRobots && filteredRobots.length && query && query.length) {
return filteredRobots.map((filteredRobot) => (
<Robo
key={filteredRobot.id} //id is unique key in your data
name={filteredRobot.name}
id={filteredRobot.id}
email={filteredRobot.email}
/>
));
}
return <p>{'No Robots Found'}</p>;
};
return (
<div className="App">
<Header query={query} InputEvent={onQueryChange} />
{renderRobots()}
</div>
);
}
Problems in your code:
Const InputChange is a function that can be used as prop for any React component .. but you have added InputChange inside the InputChange named function itself which is incorrect
Extraction is a jsx variable which is created from Array.filter.. on each item, filter passes a item[index] to the filter function.. you dont want to do robots[index].name.toLowerCase().includes(query).. instead you could have done curElem.name.toLowerCase().includes(query) and same applies for Array.map
I'm trying to create an edit feature to my todo-list but i'm kind of stuck and receiving a weird behaviour.
I'm filtering the array using the id's but what happens is that the entire array is changing instead of one element inside of it.
What supposed to happen is when clicking the edit button, the element im clicking on should change to an input (not the entire array)
thanks for any kind of help!
App:
import React, { useState } from "react";
import Header from "./UI/Header";
import TodoList from "./Components/TodoList";
import AddTodo from "./Components/AddTodo";
import { v4 as uuidv4 } from "uuid";
function App() {
const [todos, setTodos] = useState([]);
const [editTodo, setEditTodo] = useState(false);
const onAddHandler = (text) => {
setTodos([
...todos,
{
name: text,
id: uuidv4(),
},
]);
};
const deleteTodoHandler = (id) => {
setTodos(todos.filter((todo) => todo.id !== id));
};
const editTodoHandler = (id) => {
todos.filter((todo) => todo.id === id);
setEditTodo(!editTodo);
};
return (
<div>
<div className="App">
<AddTodo onAddHandler={onAddHandler} />
<Header />
<TodoList
todos={todos}
deleteTodoHandler={deleteTodoHandler}
editTodoHandler={editTodoHandler}
editTodo={editTodo}
/>
</div>
</div>
);
}
export default App;
TodoList.js :
import React, { useState } from "react";
import Todo from "./Todo";
const TodoList = (props) => {
return (
<Todo todo={props.todo}>
{props.todos.map((todo) => {
return (
<p>
{props.editTodo ? <input /> : <span>{todo.name}</span>}
<button onClick={() => props.deleteTodoHandler(todo.id)}>
Delete
</button>
<button onClick={() => props.editTodoHandler(todo.id)}>Edit</button>
</p>
);
})}
</Todo>
);
};
export default TodoList;
When you set the editTodo property to true, the TodoList component re-renders and loops through the todo array again, changing every <span> to an <input>. You're going to have to pass the id of the todo that you want to edit, and add a condition to only change that single item to an <input>.
I am trying to display the SingleLineText component below that has one input field when a click occurs in the parent component called TextInput. However, after a click, the state is not changed by useState and as a result the child component named SingleLineText is not displayed.
TextInput component is pasted below right after SingleLineText component.
SingleLineText component:
import React from "react";
const SingleLineText = () => {
return(
<form>
<input />
</form>
)
}
export default SingleLineText;
TextInput the Parent component for SingleLineText component:
import React, {useState, useEffect} from "react";
import SingleLineText from "./SingleLineText"
const TextInput = (props) => {
const [showSingleText, setShowSingleText] = useState(false);
useEffect( () => {
}, [showSingleText])
const handleFieldDisplay = (event) => {
if (event.target.value == "Single line text") {
cancel();
setShowSingleText(showSingleText => !showSingleText);
//setShowSingleText(showSingleText => true);
}
}
const cancel = () => {
props.setShowInput(!props.showInput);
}
return (
<>
<div className="dropdown">
<ul key={props.parentIndex}>
{
props.fieldType.map( (val, idx) => {
return(
<option key={idx} value={val} className="dropdown-item" onClick={handleFieldDisplay}> {val} </option>
)
})
}
</ul>
</div>
{showSingleText && <SingleLineText />}
</>
)
}
export default TextInput;
Topmost or grand parent component:
import React, { useState, useEffect, useRef } from "react"
import PropTypes from "prop-types"
import TextInput from "components/pod_table/fields/inputs/TextInput";
const DynamicFields = (props) => {
const fieldType = ['Checkbox', 'Dropdown', 'boolean', 'Single line text'];
const [showDynamicField, setShowDynamicField ] = useState(false);
const[showInput, setShowInput] = useState(false);
useEffect(() => {}, [showDynamicField, showInput]);
const handleShowDynamicField = (event) => {
setShowDynamicField(!showDynamicField);
}
const handleSubDisplay = (event) => {
if (event.target.value == "customise field type") {
setShowDynamicField(!showDynamicField);
setShowInput(!showInput);
}
}
return (
<React.Fragment>
<div>
<i className="bi bi-chevron-compact-down" onClick={handleShowDynamicField}></i>
{ showDynamicField &&
(<div className="dropdown">
<ul key={props.parentIndex}>
{
optionsHash.map( (val, idx) => {
return(
<option key={idx} value={val} className="dropdown-item" onClick={handleSubDisplay}> {val} </option>
)
})
}
</ul>
</div>) }
{showInput && <TextInput fieldType={fieldType} setShowInput={setShowInput} showInput={showInput} /> }
</div>
</React.Fragment>
)
}
The problem is that in the handleFieldDisplayFunction you are calling the cancel function which changes the parent state showInput and hence unmounts the TextInput component itself, so the showSingleText state changes inside TextInput component doesn't have any effect.
Design your code in a way that you are not required to unmount TextInput when you click on an option within it
const handleFieldDisplay = (event) => {
if (event.target.value == "Single line text") {
setShowSingleText(showSingleText => !showSingleText);
}
}
I am trying to write the very first to-do application in REACT. I want to add functionality to delete to-do item when the user clicks on the delete icon. When I click on delete icon it only removes the text. Here I would like to delete the entire item. Can someone please suggest?
App.js
import './App.css';
import { useState } from 'react';
import TodoList from './TodoList';
import { v4 as uuidv4 } from 'uuid';
function App() {
const [input, setInput] = useState('');
const [todos, setTodo] = useState([]);
const addTodo = (e) => {
e.preventDefault();
const id = uuidv4();
setTodo([...todos, { id: id, text: input}])
// setTodo({todos: [...todos, input], id })
setInput('');
}
const deleteTodo = (id) => {
console.log("id" + id);
const filteredItem = todos.filter(todo => todo.id !== id);
setTodo([filteredItem]);
}
return (
<div className="App">
<form>
<input type="text" value={input} onChange={(e) => setInput(e.target.value)}/>
<button type="submit" onClick={addTodo}>Enter</button>
</form>
<TodoList todos={todos} deletetodo={deleteTodo}/>
</div>
);
}
export default App;
TodoList.js
import React from 'react'
import DeleteIcon from '#material-ui/icons/Delete';
import EditIcon from '#material-ui/icons/Edit';
const todo = ({todos, deletetodo}) => {
return (
<div>
{todos.map(todo => (
<li key={todo.id}>
{todo.text}
<div>
<DeleteIcon onClick={(todo) => deletetodo(todo.id)}/>
<EditIcon/>
</div>
</li>
))}
</div>
)
}
export default todo;
There are a few problems with your code. I will start with the most obvious. You re-render your App on EVERY change of the input field. That's just unnecessary. So insated of storing the value of the input in a state variable, I would use useRef(). So you only really need one state variable, one that stores the list of todos.
Second, your filter is correct, but then you incorrectly set the state variable with the filtered result:
const filteredItem = todos.filter(todo => todo.id !== id);
setTodo([filteredItem]);
It will already return an array and there is no need to wrap it into another one.
With those 2 main issue fixed, here is a working example along with a Sandbox:
import React, { useState } from "react";
import { v4 as uuidv4 } from "uuid";
import "./styles.css";
const TodoList = ({ todos, deletetodo }) => {
return (
<div>
{todos.map((todo) => (
<li key={todo.id}>
{todo.text}
<div>
<button onClick={() => deletetodo(todo.id)}>delete</button>
<button>edit</button>
</div>
</li>
))}
</div>
);
};
export default function App() {
const [todos, setTodo] = useState([]);
const input = React.useRef();
const addTodo = (e) => {
e.preventDefault();
const id = uuidv4();
setTodo([...todos, { id: id, text: input.current.value }]);
input.current.value = "";
};
const deleteTodo = (id) => {
setTodo(todos.filter((item) => item.id !== id));
};
return (
<div className="App">
<form>
<input ref={input} type="text" />
<button type="submit" onClick={addTodo}>
Enter
</button>
</form>
<TodoList todos={todos} deletetodo={deleteTodo} />
</div>
);
}
You have a mistake in how you're setting todo in deleteTodo:
const deleteTodo = (id) => {
console.log("id" + id);
const filteredItem = todos.filter(todo => todo.id !== id);
// Mistake! Your filteredItem is an array, you're putting your array into an array.
setTodo([filteredItem]);
}
Consequently, when you pass it further down, your component tries to get [filteredItem].text, which is undefined and React sees an empty string.
Fix:
setTodo(filteredItem);
There are multiple issues within the code:
First one is setting the values after deleting the row:
should be like this : setTodo(filteredItem);
Second issue was calling the onClick function, you already have the id with you so no need to re-call it again:
<div>
{todos.map(todoss =>
<li onClick={() => deletetodo(todoss.id)} key={todoss.id}>
{todoss.text}
</li>
)}
</div>
Hello guys,
I'm relatively new to react hooks, and back in my time(one year ago), when I was passing props through
component, I would then use them with "props.something."
Now I have done a "todo list" and I can use my props in another way, by referencing the props in an objet in parameter :
const Form = ({ addTodo }) => {
addTodo(x)
}
What is that ? Why we don't use this.props anymore, why the object in parameter ? Is the old way passing props dead ? Is this because of react Hooks ?
To illustrate more my exemple here the two component talking to eachother.
The first one :
import React, {useState} from 'react';
import Form from './Form';
const Affichage = () => {
const [todos, setTodos] = useState([
'1',
'2',
'3',
'4'
])
const addTodo = text => {
const newTodos = [...todos, text];
setTodos(newTodos)
}
return (
<div>
<Form addTodo={addTodo} />
<ul>
{todos.map((item, index) =>{
return(
<li key={index}>
{item}
</li>
)
})}
</ul>
</div>
)
}
export default Affichage;`
The second one :
import React, {useState} from 'react';
const Form = ({ addTodo }) => {
const [value, setValue] = useState('');
const handleSubmit = e => {
e.preventDefault();
// console.log(value);
addTodo(value);
}
return(
<form onSubmit={handleSubmit}>
<input type="text" onChange={e => setValue(e.target.value)}/>
<button>Envoyer</button>
</form>
)
}
export default Form;
If someone passing by could enlight me it would awesome ☺
Nothing changed. It's just a shortcut for destructuring
const Component = props =>{
const { foo } = props
}
Is the equivalent of
const Component = ({ foo }) =>{
}