I have a UserLists component where the user types into an input and adds the value onto the bottom of the screen.
The input value is added into the whitelist state. The state is then mapped and creates more inputs where the user can decide to delete said input or edit it.
I am having trouble deleting the inputs. I thought I could delete each input individually by splicing the state, but my implementation of the deleteItem deletes multiple inputs when any single one of them is clicked.
I also cannot edit any of the inputs because their value is set by my addItem function.
import React, { useEffect, useState } from "react";
export const UserLists = () => {
const [whitelist, setWhiteList] = useState([]);
const addItem = () => {
let newValue = document.getElementById("whiteList").value;
setWhiteList([...whitelist, newValue]);
};
useEffect(() => {
console.log(whitelist, "item changed");
}, [whitelist]);
const deleteItem = (index) => {
let test = whitelist.splice(index, 1);
setWhiteList(test);
console.log("index:", index);
};
const editItem = () => {};
return (
<div>
<h2>WhiteList</h2>
<input id="whiteList" type="text" />
<button onClick={addItem}>Add</button>
{whitelist.map((item, index) => {
return (
<div>
<input type="text" value={item} onChange={editItem} />
<button onClick={() => deleteItem(index)}>Delete</button>
<p>{index}</p>
</div>
);
})}
</div>
);
};
How can I revise my code to successfully individually delete and edit inputs?
My codesandbox
You need to change your editItem and deleteItem functions in order to make your edit and delete functionality work properly. Here's a code sandbox link to the solution to your problem:
https://codesandbox.io/s/whitelist-forked-y51w8
Don't do:
let test = whitelist.slice(index, 1);
setWhiteList(test);
Do this instead:
whitelist.splice(index, 1);
setWhiteList([...whitelist]);
Related
I'm triyng to remove an object property using spread syntax rendering a React component. I wonder if there is a way to achieve without adding to much extra code. I'm using {reset,...inputName}
I have a custom hook (useField) for every input in my form. useField also has a reset function for my reset button. But I want to remove the reset property only for the inputs element.
custom hook useField
export const useField = (type) => {
const [value, setValue] = useState('')
const onChange = (event) => {
setValue(event.target.value)
}
const reset = () => {
setValue('')
}
return {
type,
value,
onChange,
reset
}
}
react form
const MyForm = (props) => {
const content = useField('text')
const author = useField('text')
const info = useField('text')
const handleClick = () => {
content.reset()
author.reset()
info.reset()
}
return (
<div>
<h2>create a new anecdote</h2>
<form onSubmit={handleSubmit}>
<div>
content
<input {reset,...content} />
</div>
<div>
author
<input {reset,...author} />
</div>
<div>
url for more info
<input {reset,...info} />
</div>
<button>create</button>
<input type="reset" value='reset' onClick={handleClick} />
</form>
</div>
)
}
For future reference, what may work for OP are changes similar to below:
const { reset: resetContent, ...content} = useField('text')
const { reset: resetAuthor, ...author} = useField('text')
const { rest: resetInfo, ...info} = useField('text')
const handleClick = () => {
resetContent();
resetAuthor();
resetInfo();
};
.
.
.
<div>
content
<input {...content} />
</div>
.
.
.
Explanation
the object returned from useField is destructured
the reset prop is separated and renamed as resetContent (or resetAuthor, resetInfo, as required)
the rest of the props go into the content variable (or author, info variables, as required)
when rendering in the JSX, the content is used
Thus, effectively the reset prop from useField was 'removed' (technically, it was just separated, though) in the new content object.
I have a main react component which appends child components say <Child /> on button click
My Child component is of the format
<form>
<input .... />
<button type="submit">Submit</button>
<form>
Now I need to get the value of these input elements from every Child component which is appended but am not able to figure out a way to do so.
I won't know how many Child components would be added as the user can click the button any number of times to append yet another <Child /> so I can't have a fixed number of variables exported from the Child component to a variable in parent component.
Any suggestions would be highly appreciated.
Edit:
Code to append the child:
The submit function:
const [val, setVal] = useState([]);
const submit = () => {
setVal([...val, <Child />]);
}
Append button:
<Button onClick={submit}>Add Child</Button>
To render:
{val}
Since val is an array, it prints all the components inside it
I took some liberties because I don't know what would be the expected output of all the forms.
What I basically did is to create a map-like structure where I will map each child form with its value/s (depending on your needs could be modified) and I passed a submit function to the child components in order to store the values on the parent.
In that way on a child submit I will be able to get the values passed from the child as well as its index.
Parent component
const Parent = () => {
const [val, setVal] = useState([]);
// Map like structure to store the values (is just an example, you can use whatever you want)
const [mapOfValues, setMapOfValues] = useState({});
// Pass submit function as prop
const submit = () => {
setVal([...val, <Child submit={onChildSubmit} />]);
};
// Submit function that stores the value for the child
const onChildSubmit = (childIndex, value) => {
setMapOfValues({
...mapOfValues,
[childIndex]: value
});
};
return (
<div className="App">
{val.map((value, index) =>
// Key to prevent React warning and childIndex to map the value to the child form
React.cloneElement(value, { key: index, childIndex: index })
)}
<button onClick={submit}>Add</button>
</div>
);
}
Child component
const Child = (props) => {
const [value, setValue] = useState("");
const submitForm = (e) => {
e.preventDefault();
props.submit(props.childIndex, value);
};
return (
<form onSubmit={submitForm}>
<input onChange={(e) => setValue(e.target.value)} value={value} />
<button>Submit</button>
</form>
);
};
Once you are done, and you want to submit from the parent component, you could use a reduce, map or whatever you need to format the values as you want.
The link to the sandbox is this
If you have any other question let me know.
Simplified Code Sample right here
WORDS:
In short: My items state is resetting to [] with each NEW checkbox clicked and I dont understand why. But instead I want to use the spread operator and useState hooks to push an new item into the array so it's an array of objects.
Current behavior in detail: I'm creating an object and setting it in state using all (and I mean ALL) manner of React useState hooks like this: setItems((prevState) => [...prevState, { [evt.target.value]: evt.target.checked }]); As I check one item it's added and items becomes an array of objects (it being added over and over again is not the problem; I'll add a check for that later). BUT Here's the problem: when I click a NEW checkbox the items array is set back to [] and isnt concatenated with the prev items—even though I'm using prevState, spread operator, an arrow func as a wrapper, and all that jazz.
Desired behavior: Every time I check a checkbox, I want to update items [] to push a new object into it, which represents all items that have ever been checked. Before you say anything about duplicating: I'll add the check to see if an item is already in the array, and just update it if so. And before I add all items to cart, I'll strip all objects with checked = false states.
Can you help me understand what react lifecycle fundamentals I'm missing here; why is this happening? And how can I fix it?
CODE:
Where this is happening:
Simplified version of InputComponent
const InputComponent = ({ type, itemId, handleSearchQuery, onSubmit }) => {
const [items, setItems] = useState([]);
const captureInput = (evt) => {
if (evt.target.type === 'checkbox') {
setItems((prevState) => [...prevState, { [evt.target.value]: evt.target.checked }]);
}
};
const renderCheckbox = () => {
return (
<form>
<input type={type} name={itemId} value={itemId} onChange={setItem} />
<input name={itemId} type='submit' value='Add to Cart' />
</form>
);
};
return (
<div className='input-bar'>
{renderCheckbox()}
</div>
);
};
export default InputComponent;
Where this component is used:
import React from 'react';
import InputComponent from './InputComponent';
import './ResultsRenderer.css';
function ResultsRenderer({ data }) {
const renderListings = () => {
let listings = data ? data.Search : null;
return listings
? listings.map((item) => {
return (
<div className='cart-row'>
<InputComponent type='checkbox' className='cart-checkbox' itemId={item.imdbID} />
<div key={item.imdbID} className={item.imdbID}>
<img src={`${item.Poster}`} alt={item.Title} />
<div>
Title<em>{item.Title}</em>
</div>
<div>{item.Year}</div>
<em>{item.imdbID}</em>
</div>
</div>
);
})
: null;
};
return <>{renderListings()}</>;
}
export default ResultsRenderer;
items state is doing its job perfectly fine, you misunderstood the situation.
you're using items state inside InputComponent and for each listings item there is one InputComponent and each one have their own items, I think you meant to use items state inside ResultsRenderer Component to chase all selected items.
here is the changes you need to do:
const InputComponent = ({ type, itemId, setItems }) => {
const captureInput = (evt) => {
if (evt.target.type === "checkbox") {
setItems((prevState) => [
...prevState,
{ [evt.target.value]: evt.target.checked }
]);
}
};
return (
<div className="input-bar">
<form>
<input
type={type}
name={itemId}
value={itemId}
onChange={captureInput}
/>
<input name={itemId} type="submit" value="Add to Cart" />
</form>
</div>
);
};
export default InputComponent;
function ResultsRenderer() {
const [items, setItems] = useState([]);
useEffect(() => {
console.log(items);
}, [items]);
const renderListings = () => {
let listings = [
{ itemId: 1, title: "Hello" },
{ itemId: 2, title: "World" }
];
return listings
? listings.map((item) => {
return (
<div className="cart-row">
<InputComponent
type="checkbox"
className="cart-checkbox"
itemId={item.itemId}
setItems={setItems}
/>
<div key={item.itemId} className={item.itemId}>
<div>
Title<em>{item.Title}</em>
</div>
</div>
</div>
);
})
: null;
};
return <>{renderListings()}</>;
}
and here is the working demo: https://codesandbox.io/s/boring-cookies-t0g4e?file=/src/InputComponent.jsx
I have an array of tags that take an input and update when the user presses enter. For some reason, the user needs to press enter twice before anything happens.
const [ tags, setTags ] = useState([]);
const addTag = (inputEvent) => {
if (inputEvent.key === 'Enter') {
setTags([ ...tags, inputEvent.target.value ]);
inputEvent.target.value = '';
}
};
return(
<input
type="text"
placeholder="Press enter"
onKeyUp={(inputEvent) => addTag(inputEvent)}
/>
)
Need more context to be sure. But i would guess this is a classic stale state problem. Instead of setTags([ ...tags, inputEvent.target.value ]), try use the callback function signature:
setTags(tags => [ ...tags, inputEvent.target.value ])
Use the ref to access the current tag from your input field.
Use form so that it is easier to add the tags and listen to Enter submit.
On submit, update your tags, and reset your form using the ref.
import React from "react";
export default function App() {
const [tags, setTags] = React.useState([]);
const inputRef = React.useRef(null);
const formRef = React.useRef(null);
const addTag = (e) => {
e.preventDefault();
setTags([...tags, inputRef.current.value]);
formRef.current.reset();
};
return (
<div>
<p>{tags.join(",")}</p>
<form onSubmit={addTag} ref={formRef}>
<input type="text" ref={inputRef} placeholder="Press enter" />
</form>
</div>
);
}
Note :- It is advisable in React to use ref to access DOM elements and manipulating them.
You could check a working example here -> https://codesandbox.io/s/wispy-microservice-3j455?file=/src/App.js:0-469
So here's the user function I'm trying to create:
1.) User double clicks on text
2.) Text turns into input field where user can edit text
3.) User hits enter, and upon submission, text is updated to be edited text.
Basically, it's just an edit function where the user can change certain blocks of text.
So here's my problem - I can turn the text into an input field upon a double click, but how do I get the edited text submitted and rendered?
My parent component, App.js, stores the function to update the App state (updateHandler). The updated information needs to be passed from the Tasks.jsx component, which is where the text input is being handled. I should also point out that some props are being sent to Tasks via TaskList. Code as follows:
App.js
import React, {useState} from 'react';
import Header from './Header'
import Card from './Card'
import cardData from './cardData'
import Dates from './Dates'
import Tasks from './Tasks'
import Footer from './Footer'
import TaskList from './TaskList'
const jobItems= [
{
id:8,
chore: 'wash dishes'
},
{
id:9,
chore: 'do laundry'
},
{
id:10,
chore: 'clean bathroom'
}
]
function App() {
const [listOfTasks, setTasks] = useState(jobItems)
const updateHandler = (task) => {
setTasks(listOfTasks.map(item => {
if(item.id === task.id) {
return {
...item,
chore: task.chore
}
} else {
return task
}
}))
}
const cardComponents = cardData.map(card => {
return <Card key = {card.id} name = {card.name}/>
})
return (
<div>
<Header/>
<Dates/>
<div className = 'card-container'>
{cardComponents}
</div>
<TaskList jobItems = {listOfTasks} setTasks = {setTasks} updateHandler = {updateHandler}/>
<div>
<Footer/>
</div>
</div>
)
}
export default App;
Tasks.jsx
import React, {useState} from 'react'
function Tasks (props) {
const [isEditing, setIsEditing] = useState(false)
return(
<div className = 'tasks-container'>
{
isEditing ?
<form>
<input type = 'text' defaultValue = {props.item.chore}/>
</form>
: <h1 onDoubleClick ={()=> setIsEditing(true)}>{props.item.chore}</h1>
}
</div>
)
}
export default Tasks
TaskList.jsx
import React from 'react'
import Tasks from './Tasks'
function TaskList (props) {
const settingTasks = props.setTasks //might need 'this'
return (
<div>
{
props.jobItems.map(item => {
return <Tasks key = {item.id} item = {item} setTasks = {settingTasks} jobItems ={props.jobItems} updateHandler = {props.updateHandler}/>
})
}
</div>
)
}
export default TaskList
You forgot onChange handler on input element to set item's chore value.
Tasks.jsx must be like below
import React, {useState} from 'react'
function Tasks (props) {
const [isEditing, setIsEditing] = useState(false)
const handleInputChange = (e)=>{
// console.log( e.target.value );
// your awesome stuffs goes here
}
return(
<div className = 'tasks-container'>
{
isEditing ?
<form>
<input type = 'text' onChange={handleInputChange} defaultValue = {props.item.chore}/>
</form>
: <h1 onDoubleClick ={()=> setIsEditing(true)}>{props.item.chore}</h1>
}
</div>
)
}
export default Tasks
So, first of all, I would encourage you not to switch between input fields and divs but rather to use a contenteditable div. Then you just use the onInput attribute to call a setState function, like this:
function Tasks ({item}) {
return(
<div className = 'tasks-container'>
<div contenteditable="true" onInput={e => editTask(item.id, e.currentTarget.textContent)} >
{item.chore}
</div>
</div>
)
}
Then, in the parent component, you can define editTask to be a function that find an item by its id and replaces it with the new content (in a copy of the original tasks array, not the original array itself.
Additionally, you should avoid renaming the variable between components. (listOfTasks -> jobItems). This adds needless overhead, and you'll inevitably get confused at some point which variable is connected to which. Instead say, <MyComponent jobItems={jobItems} > or if you want to allow for greater abstraction <MyComponent items={jobItems} > and then you can reuse the component for listable items other than jobs.
See sandbox for working example:
https://codesandbox.io/s/practical-lewin-sxoys?file=/src/App.js
Your Task component needs a keyPress handler to set isEditing to false when enter is pressed:
const handleKeyPress = (e) => {
if (e.key === "Enter") {
setIsEditing(false);
}
};
Your updateHandler should also be passed to the input's onChange attribute, and instead of defaultValue, use value. It also needs to be reconfigured to take in the onChange event, and you can map tasks with an index to find them in state:
const updateHandler = (e, index) => {
const value = e.target.value;
setTasks(state => [
...state.slice(0, index),
{ ...state[index], chore: value },
...state.slice(index + 1)
]);
};
Finally, TaskList seems like an unnecessary middleman since all the functionality is between App and Task; you can just render the tasks directly into a div with a className of your choosing.
react-edit-text is a package I created which does exactly what you described.
It provides a lightweight editable text component in React.
A live demo is also available.