react newbie here once again.
I'm trying to update the page with a note when pressing 'send' button.
Honestly I tried for so long that I think my code is pretty much a mess rn.
Main func:
function MainLogged() {
const [sendingScreen, setSendingScreen] = useState(true);
let notes = [];
const [note, setNote] = useState('')
function onNoteChange(event){
const {value} = event.target;
setNote(value);
}
function afterButtonClick(event) {
notes.push(note);
setSendingScreen((prev) => !prev)
}
return (
<div id='main'>
{sendingScreen ? <MainBody noteCallback={onNoteChange} callback={afterButtonClick}/> : <ResetScreen callback={afterButtonClick}/> }
{console.log(notes)}
{
notes.map((singleNote) => {
return (<p id='single-note' className='note'>{singleNote}</p>)
})
}
</div>
)
}
then goes to component:
function MainBody(props) {
const {noteCallback, callback} = props;
return (
<div>
<p id='main-body'>{lorem}</p>
<div className='card text-center' style={{width: '18rem', alignSelf: 'center'}}>
<Input onChange={noteCallback} type='text' placeholder='My note is...' className='card-body information' />
</div>
<SendCardButton callback = {callback}/>
</div>
)
}
and finally (if necessary):
function SendCardButton(props){
const {callback} = props;
return (
<button type="button" className="btn btn-success toBeCentered" onClick={callback}>Send</button>
)
}
Using this code leaves notes array empty always.
Please if you can explain so I can learn and improve, I REALLY need it right now.
Thanks a lot!
React does not rerender your component after you click the button because notes are not stored in state. You need declare notes this way:
const [notes, setNotes] = useState([]);
And then set them on button click, not just push the new one:
setNotes((prevNotes) => ([...prevNotes, note]));
That should be enough:) Feel free to comment if you have further questions
Related
This question already has an answer here:
ReactJS, event.currentTarget doesn't have the same behavior as Vanilla Javascript
(1 answer)
Closed last month.
I want to capture the name attribute of a button on click in React.
I tried the following code block:
export function TestButton(props){
function logName() {
console.log(this.name)
}
return(
<button name={props.name} onClick={event => logName(event.currentTarget.getAttribute("name"))} type='button'>{props.text}</button>
)
}
My expectation was that this code would allow me to create a button that displays the name in the console log:
<TestButton name='helloWorld' text='Click Me'/>
Instead I get an alert that this is undefined. This is in spite of my ability to see the name when I inspect the element.
I have also tried target instead of currentTarget with no luck. I also tried event.currentTarget.name without the results I desire.
What did i miss?
In react, I believe this is reserved for classes, whereas you are defining a functional component. In a functional component, the comparable state value would be stored with useState(). That being said, I'm not sure I see the need for that here, since this button is getting its props from somewhere and the value of name and text are not changing in this component. I would code it this way:
export const TestButton = ({props}) => {
return(
<button name={props.name} onClick={() => console.log(props.name)}>
{props.text}
</button>
)
}
Now to go a bit further, maybe you want to use state wherever this button is being rendered. That could look like this:
import {TestButton} from "./someFile";
const [name, setName] = useState("some-button");
const [text, setText] = useState("click me!");
// now there could be some code here that decides what the name or text would be
// and updates the values of each with setName("name") and setText("text")
const Page = () => (
<>
<TestButton props={{name: name, text: text}} />
</>
)
This is all building off your current code, but now I will combine everything in a way that makes sense to me:
import {useState} from "react";
const [name, setName] = useState("some-button");
const [text, setText] = useState("click me!");
// some code to determine/change the value of the state vars if necessary
const TestButton = ({name, text}) => {
return(
<button name={name} onClick={() => console.log(name)}>
{text}
</button>
)
}
export const Page = () => (
<>
<TestButton name={name} text={text} />
</>
)
Pleas try as follows:
export function TestButton(props){
function logName() {
console.log(props.name)
}
return(
<button name={props.name} onClick={() => logName()} type='button'>{props.text}</button>
)
}
Try this
export function TestButton(props){
const logName = (e, name) => {
console.log("name attribute ->", name)
}
return(
<button name={props.name} onClick={ (e) => logName(e, props.name)} type='button'>{props.text}</button>
)
}
I have a React notes app that has a delete button, and a state for user confirmation of deletion.
Once user confirms, the 'isConfirmed' state is updated to true and deletes the item from MongoAtlas and removes from notes array in App.jsx.
The problem is, the note that takes the index (through notes.map() in app.jsx I'm assuming) of the deleted notes position in the array has the 'isConfirmed' state set to true without calling setState. Thus, bugging out my delete button to not work for that specific note until page refresh.
I've included relevant code from my Delete Component:
function DeletePopup(props) {
const mountedRef = useRef(); //used to stop useEffect call on first render
const [isConfirmed, setIsConfirmed] = useState(false);
const [show, setShow] = useState(false);
function confirmDelete() {
// console.log("user clicked confirm");
setIsConfirmed(true);
// console.log(isConfirmed);
handleClose();
}
useEffect(() => {
// console.log("delete useEffect() run");
if (mountedRef.current) {
props.deleteNote(isConfirmed);
}
mountedRef.current = true;
}, [isConfirmed]);
Note Component:
function Note(props) {
function deleteNote(isConfirmed) {
props.deleteNote(props.id, { title: props.title, content: props.content }, isConfirmed);
console.log("note.deleteNote ran with confirmation boolean: " + isConfirmed);
}
return <Draggable
disabled={dragDisabled}
onStop={finishDrag}
defaultPosition={{ x: props.xPos, y: props.yPos }}
>
<div className='note'>
<h1>{props.title}</h1>
<p>{props.content}</p>
<button onClick={handleClick}>
{dragDisabled ? <LockIcon /> : <LockOpenIcon />}
</button>
<EditPopup title={props.title} content={props.content} editNote={editNote} />
<DeletePopup deleteNote={deleteNote} />
</div>
</Draggable>
}
App Component:
function App() {
const [notes, setNotes] = useState([]);
function deleteNote(id, deleteNote, isConfirmed) {
if (!isConfirmed) return;
axios.post("/api/note/delete", deleteNote)
.then((res) => setNotes(() => {
return notes.filter((note, index) => {
return id !== index;
});
}))
.catch((err) => console.log(err));
}
return (
<div id="bootstrap-override">
<Header />
<CreateArea
AddNote={AddNote}
/>
{notes.map((note, index) => {
return <Note
key={index}
id={index}
title={note.title}
content={note.content}
xPos={note.xPos}
yPos={note.yPos}
deleteNote={deleteNote}
editNote={editNote}
/>
})}
<Footer />
</div>);
}
I've tried inserting log statements everywhere and can't figure out why this is happening.
I appreciate any help, Thanks!
EDIT: I changed my Notes component to use ID based on MongoAtlas Object ID and that fixed the issue. Thanks for the help!
This is because you are using the index as key.
Because of that when you delete an element you call the Array.filter then you the elements can change the index of the array which when React tries to rerender the notes and as the index changes it cannot identify the note you've deleted.
Try using a unique id (e.g. an id from the database or UUID) as a key instead.
I hope it solves your problem!
My application has a modal window with filters. I would like to add another kind of filter to it. I just don’t know how to implement it in React (perhaps you can help me with the code, recommend links).
The meaning is as follows: I want the filters to have a line in which the user could write some value on his own, press Enter, see the entered result (it is possible to enter several values by which you can filter).
Perhaps my explanation is chaotic, in addition I will provide a screenshot with the desired result:
For UI you can use or create a custom input field like this https://evergreen.segment.com/components/tag-input.
For implementation:
Now you have fields searched by user
Ex-
const list = [2,3,4,56,7,8,12,34,0,1]
const searchedItems = [3,7,8]
const finalList= [];
function searchItems() {
list.forEach(item => {
searchedItems.forEach(ele => {
if(ele===item) finalList.push(item);
})
});
return finalList;
}
console.log(searchItems())
Finally use this array in your search item results.
export function ListItem({ itemValue }) {
return (
<div className="item">
<span className="itemValue">{itemValue}</span>
</div>
);
}
export default function App() {
const [itemList, setListItem] = useState([]);
const [item, setItem] = useState("");
const addValue = (event) => {
setItem(event.target.value);
};
const listenEnter = (event) => {
if (event.key === "Enter" && event.target.value !== "") {
setListItem([...itemList, item]);
setItem("");
}
};
return (
<div className="App">
<input
className="inputElement"
placeholder="Enter Value"
value={item}
onChange={addValue}
onKeyUp={listenEnter}
/>
<div className="itemList">
{itemList.map((item) => (
<ListItem itemValue={item} />
))}
</div>
</div>
);
}
whole code https://codesandbox.io/s/elastic-meadow-h5kbxs
I've been trying to debug this for hours and looked at every single other Stack Overflow question that has the same style of issue, but they all just say to use keys and that's still not working for me. I've made a simpler example of my code that replicates the error.
import React from 'react'
import { useState } from 'react'
const FormTest = () => {
const [name, setName] = useState('')
const TestExperience = (props) => {
return (<div>
<h1>Test Experience {props.num}</h1>
<input
type="text"
name="name"
placeholder="Name"
/>
</div>)
}
const processFormData = (event) => {
event.preventDefault();
console.log(event.target);
}
const [nums, setNums] = useState([1, 2, 3]);
const arr = nums.map((num, index) => <TestExperience num={num} key={index}/>);
return (
<div>
<form onSubmit={(event) => {
processFormData(event);
}}>
{arr}
<button onClick={() => {
setNums([...nums, nums.length + 1]);
}}>Add one!</button>
</form>
</div>
)
}
export default FormTest
I've tried moving TestExperience to it's own, separate function. I'm trying to basically have inputs that one can create more of, and this issue of losing focus came from the fact that every time an input was added, all of the existing formData disappeared due to a re-render. The goal would be to just use the onSubmit function to parse the data, but since it disappears after adding the input I figured I needed to store it. I've been going down rabbit hole after rabbit hole trying to fix what seems like such a simple problem and just keep running into issues with every implementation I try.
The overall goal is that I have a submit button and an add input button, and I tried to ditch the whole value={stateVariable} and onChange={setStateVariable} thing and just make the input button a "submit" button so that I can run the processFormData and do different things based on which submit button it was, but I have no clue how to check which button the submit came from when there's two different buttons, so an answer to that could be super helpful as well because then I can avoid this whole state mess.
You need to move the TestExperience out of FormTest.
import React from "react";
import { useState } from "react";
const TestExperience = (props) => {
const [name, setName] = useState("");
return (
<div>
<h1>Test Experience {props.num}</h1>
<input
type="text"
name="name"
placeholder="Name"
onChange={(event) => {
event.preventDefault();
setName(event.target.value);
}}
value={name}
/>
</div>
);
};
const FormTest = () => {
const processFormData = (event) => {
event.preventDefault();
console.log(event.target);
};
const [nums, setNums] = useState([1, 2, 3]);
const arr = nums.map((num, index) => (
<TestExperience num={num} key={index} />
));
return (
<div>
<form
onSubmit={(event) => {
processFormData(event);
}}
>
{arr}
//another way I tried to do it below //
{nums.map((num, index) => (
<TestExperience num={num} key={index} />
))}
<button
onClick={() => {
setNums([...nums, nums.length + 1]);
}}
>
Add one!
</button>
</form>
</div>
);
};
export default FormTest;
Code sandbox => https://codesandbox.io/s/trusting-elbakyan-mxrez?file=/src/App.js
TL;DR I am making a reusable Button function component. My useState() hook for the button label is updating every Button instance. How can I prevent this?
I am very new to React and building a Book Finder app in order to learn. So far my app has a BookList and a ReadingList. Each BookDetail in either list has a Button to add/remove that book from the ReadingList. The add/remove function works (phew), but using useState to update the Button's label updates every instance of the Button component, and not just the one that was clicked.
Buttons on books in the BookList start with label 'Add to Reading List', but if I click any of them, all of them update to 'Remove from Reading List'.
I've tried moving the logic around into the Button component or either List component but I just end up breaking the function.
App.js
function App() {
const books = useState([])
const [booksToRead, setBooksToRead] = useState([])
const [addRemove, setAddRemove] = useState(true)
const [label, setLabel] = useState('Add to Reading List')
function handleAddBook(book) {
const newID = book.title_id
if( (typeof booksToRead.find(x => x.title_id === newID)) == 'undefined' ) {
setBooksToRead([...booksToRead, book])
}
}
function handleRemoveBook(book) {
console.log(book)
const array = booksToRead
const index = array.indexOf(book)
const newArray = [...array.slice(0, index), ...array.slice(index +1)]
setBooksToRead(newArray)
}
function addOrRemove(book) {
if( addRemove ) {
handleAddBook(book)
setLabel('Remove from Reading List')
setAddRemove(false)
} else {
handleRemoveBook(book)
setLabel('Add to Reading List')
setAddRemove(true)
}
}
return (
<main>
<BookList books={books} handleAddBook={handleAddBook} addOrRemove={addOrRemove} label={label} />
<ReadingList booksToRead={booksToRead} handleRemoveBook={handleRemoveBook} />
</main>
);
}
export default App;
BookList.js
function BookList ({ book, label, handleAddBook, addOrRemove }) {
return (
<div className="booklist">
{BookData.map((book, index) => {
const onAddBook = () => addOrRemove(book)
return (
<div key={index} className="card">
<BookDetail key={book.title_id} book={book} />
<Button key={index + 'btn'} label={label} onClick={onAddBook} />
</div>
)
})}
</div>
)
}
export default BookList
And finally, Button.js
export default function Button({ styleClass, label, onClick }) {
return (
<button className='btn' onClick={(event) => onClick(event)}>
{label}
</button>
)
}
Unstyled example in codesandbox: https://codesandbox.io/s/cool-rgb-fksrp
Can you make these changes and let me know if there any progress:
<Button label={label} onClick={() => addOrRemove(book)} />
<button className='btn' onClick={onClick}>
It looks like that in button you are passing event instead of book as function parameter
As it is right now, you are declaring a single label and using that same one on all your book entries. This is why they all display the same label. You would need to keep track of the label of each book, for instance by keeping the label as a field in the book object.
For example:
const books = useState([{ label: 'Add to reading list', addRemove: true }])
And then:
function addOrRemove(book) {
if( book.addRemove ) {
handleAddBook(book)
book.label = 'Remove from Reading List'
book.addOrRemove = false
} else {
handleRemoveBook(book)
book.label = 'Add to Reading List'
book.addOrRemove = true
}
}
This way, each book has it's own label.