I'm fairly new to React. I am working on a note app and when I add 2 notes, they have the same key and the next 2 notes also share their own key and so on. I started off with prop drilling from the App to the AddNote file via NotesList.js and it was working fine and the problem has only occurred since I used useContext API so maybe I am not coding the useContext in the correct way. The useContext component looks like this:
import { createContext } from "react";
const HandleAddContext = createContext();
export default HandleAddContext;
This is my App.js
import { useState } from "react";
import { v4 as uuid } from "uuid";
import NotesList from "./components/NotesList";
import HandleAddContext from "./components/UseContext/HandleAddContext";
const unique_id = uuid();
const small_id = unique_id.slice(0, 8);
const initialState = [
{
id: small_id,
text: "1st note",
date: "12/10/22022",
},
{
id: small_id,
text: "2nd note",
date: "15/10/22022",
},
{
id: small_id,
text: "3rd note",
date: "16/10/22022",
},
{
id: small_id,
text: "4th note",
date: "30/10/22022",
},
];
export const App = () => {
const [notes, setNote] = useState(initialState);
const addHandleNote = (text) => {
console.log(text);
const date = new Date();
const newNote = {
id: small_id,
text: text,
date: date.toLocaleDateString(),
};
console.log(newNote);
const newNotes = [...notes, newNote];
setNote(newNotes);
};
return (
<HandleAddContext.Provider value={addHandleNote}>
<div className="container">
<NotesList notes={notes} />
</div>
</HandleAddContext.Provider>
);
};
export default App;
This is the component with map notes
import Note from "./Note";
import AddNote from "./AddNote";
const NotesList = ({ notes }) => {
return (
<div className="notes-list">
{notes.map((note) => (
<Note key={note.id} id={note.id} text={note.text} date={note.date} />
))}
<AddNote />
</div>
);
};
export default NotesList;
This is the Note:
import { RiDeleteBin6Line } from "react-icons/ri";
const Note = ({ text, date }) => {
return (
<div className="note">
{/* <div> */}
<p>{text}</p>
{/* </div> */}
<div className="note-footer">
<p className="note-footer-text">{date}</p>
<RiDeleteBin6Line />
</div>
</div>
);
};
export default Note;
This is the AddNote.js component
import { useState } from "react";
import { RiSave2Line } from "react-icons/ri";
const AddNote = ({ handleAddNote }) => {
const [addText, setAddText] = useState("");
const [errorMsg, setErrorMsg] = useState("");
//handle text input
const handleChange = (e) => {
console.log(e.target.value);
setAddText(e.target.value);
};
//handle save
const handleSaveClick = () => {
if (addText.trim().length > 0) {
handleAddNote(addText);
}
};
return (
<div>
<textarea
rows="8"
cols="10"
placeholder="Type here to add a note..."
value={addText}
onChange={handleChange}
/>
<div>
<p>200 characters remaining</p>
<RiSave2Line onClick={handleSaveClick} />
</div>
</div>
);
};
export default AddNote;
The issue is your unique_id and small_id are only being generated once due to your function call syntax.
const unique_id = uuid();
Assigns unique_id the result of uuid(), rather than referencing the function. And therefore small_id is simply slicing the already generated uuid. To fix this your must generate a new uuid every time you create a note. Your can create a function that return a new 'small ID' everytime.
function genSmallID() {
return uuid().slice(0, 8);
}
And now when you create your initial notes use the function:
const initialState = [{
id: genSmallID(),
text: "1st note",
date: "12/10/22022",
}, {
id: genSmallID(),
text: "2nd note",
date: "15/10/22022",
}, {
id: genSmallID(),
text: "3rd note",
date: "16/10/22022",
}, {
id: genSmallID(),
text: "4th note",
date: "30/10/22022",
}];
by setting a variable
const small_id = unique_id.slice(0, 8);
you create a variable and assign it to each element of your initialState array's id.
you should delete small_id and unique_id and do this:
const initialState = [{
id: uuid().slice(0, 8),
text: "1st note",
date: "12/10/22022",
}, {
id: uuid().slice(0, 8),
text: "2nd note",
date: "15/10/22022",
}, {
id: uuid().slice(0, 8),
text: "3rd note",
date: "16/10/22022",
}, {
id: uuid().slice(0, 8),
text: "4th note",
date: "30/10/22022",
}];
In order to have different id (here you have always the same), or if the id isn't relevant for you you can always use the element's position in the array as key with the 2nd parameter of the map function like this:
<div className="notes-list">
{notes.map((note, key) => (
<Note key={key} id={note.id} text={note.text} date={note.date} />
))}
<AddNote />
Related
I am learning react with typescript and i have the following problem:
App.tsx
import { useState } from 'react';
import Header from './components/Header';
import Tasks from './components/Tasks';
import { TaskType } from './components/Task'
function App() {
const [tasks, setTasks] = useState<TaskType[]>([
{
id: 0,
text: 'Doctors Appointment',
day: 'Feb 4th at 2:30 pm',
reminder: true
},
{
id: 1,
text: 'Meeting at school',
day: 'Feb 5th at 3:50 pm',
reminder: true
},
{
id: 2,
text: 'food shopping',
day: 'Feb 4th at 2:30 pm',
reminder: false
}
]);
const deleteTask = (id: number) => {
console.log('Delete', id);
}
// Delete task function
const someTitle = 'Title';
return (
<div className='container'>
<Header title={someTitle}></Header>
<Tasks tasks={tasks} onDelete={() => deleteTask}></Tasks>
</div>
);
}
export default App;
The method deleteTask() is not getting called from the inner components below: (Tasks are a list of Task)
Tasks.tsx
import Task from './Task'
import { TaskType } from './Task'
export interface ITasksListProps {
tasks: TaskType[]
onDelete: React.MouseEventHandler
}
const Tasks: React.FC<ITasksListProps> = ({tasks, onDelete}) => {
return (
<>
{tasks.map ((task) => (
<Task key={task.id} task={task} onDelete={() => onDelete}/>
))}
</>
)
}
export default Tasks
Task.tsx
import { FaTimes } from 'react-icons/fa'
export type TaskType = {
id: number,
text: string,
day: string,
reminder: boolean
}
export interface ITaskProps {
task: TaskType
onDelete: React.MouseEventHandler
}
const Task = ({task, onDelete}: ITaskProps) => {
return (
<div className='task'>
<h3>{task.text} <FaTimes style={{ color: 'red', cursor: 'pointer' }} onClick={onDelete} /></h3>
<p>{task.day}</p>
</div>
)
}
export default Task
Where is the error here? I can't figure out how to retain those states and simultaneously get the method to be called properly
Follow the updated files, even "onDelete" removes your tasks.
Let me know if it worked or not.
Codesandbox here
// App.tsx
import { useState } from "react";
import Header from "./components/Header";
import Tasks from "./components/Tasks";
import { TaskType } from "./components/Task";
function App() {
const defaultState = [
{
id: 0,
text: "Doctors Appointment",
day: "Feb 4th at 2:30 pm",
reminder: true
},
{
id: 1,
text: "Meeting at school",
day: "Feb 5th at 3:50 pm",
reminder: true
},
{
id: 2,
text: "food shopping",
day: "Feb 4th at 2:30 pm",
reminder: false
}
];
const [tasks, setTasks] = useState<TaskType[]>(defaultState);
const deleteTask = (id: number) => {
console.log("id is", id);
const filteredTasks = tasks.filter((each) => each.id !== id);
setTasks(filteredTasks);
};
// Delete task function
const someTitle = "Title";
return (
<div className="container">
<Header title={someTitle}></Header>
<Tasks {...{ tasks, onDelete: deleteTask }}></Tasks>
</div>
);
}
export default App;
// Header.tsx
export default function Header(props: { title: string }) {
const { title } = props;
return <div>Header {title}</div>;
}
// Task.tsx
import { FaTimes } from "react-icons/fa";
export type TaskType = {
id: number;
text: string;
day: string;
reminder: boolean;
};
export interface ITaskProps {
task: TaskType;
onDelete: Function;
}
const Task = ({ task, onDelete }: ITaskProps) => {
return (
<div className="task">
<h3>
{task.text}{" "}
<FaTimes
style={{ color: "red", cursor: "pointer" }}
onClick={() => onDelete(task.id)}
/>
</h3>
<p>{task.day}</p>
</div>
);
};
export default Task;
// Tasks.tsx
import React from "react";
import Task from "./Task";
import { TaskType } from "./Task";
export interface ITasksListProps {
tasks: TaskType[];
onDelete: Function;
}
const Tasks: React.FC<ITasksListProps> = ({ tasks, onDelete }) => {
return (
<>
{tasks.map((task) => (
<Task key={task.id} {...{ task, onDelete }} />
))}
</>
);
};
export default Tasks;
Because you are using it wrong
<Tasks tasks={tasks} onDelete={() => deleteTask}></Tasks>
Here you should do for example
<Tasks tasks={tasks} onDelete={() => deleteTask(5)}></Tasks>
So what is happening is that
onClick={onDelete}
basically means that onClick = onDelete but
onDelete={() => deleteTask}
means onDelete = () => { deleteTask } you are just referencing the method but not invoking it.
I am trying to grab the user input on key pressed and pass it to that list above next. I feel like there must be a way to reset the state and make it persist, but I just can't figure it out? How can I understand this?
import { useState, useEffect, useRef, useMemo } from 'react';
import '../sign.css';
const VALUES = [
{ id: 1, label: "name", text: "Hi, What is your Name?", placeholder: "Enter your full name" },
{ id: 2, label: "uname", text: "What shall we call you?", placeholder: "Enter a username" },
{ id: 3, label: "email", text: "Enter you email", placeholder: "Email" },
{ id: 4, label: "password", text: "Choose a password", placeholder: "make sure you dont forget" },
{ id: 5, label: "signup", text: "sign up", placeholder: ""},
];
export default function SignUp() {
const [show, setShow] = useState(VALUES)
const [currentIndex, setCurrentIndex] = useState(0);
const [details, setDetails] = useState('');
useEffect(() => {
}, [show]);
const onKeyPressed = (ev, id) => {
if (ev.charCode === 13) {
ev.preventDefault();
const nextRender = currentIndex + 1;
if (nextRender < show.length) {
setCurrentIndex(nextRender);
setDetails(ev.target.value);
} else {
//todo
}
}
}
const displayItem = useMemo(() => show[currentIndex], [show, currentIndex]);
return (
<div className="container" id="container">
<div className="navigation">
<ol>
<li>{this should display their name}</li>
<li>{this should display their username}</li>
<li>{this should display their email}</li>
</ol>
</div>
<form id="sign-form" className="sign-form">
<ol className="questions">
{
<li onKeyPress={(KeyboardEvent) => onKeyPressed(KeyboardEvent, displayItem.id)} key={displayItem.id} >
<span><label htmlFor={displayItem.label}>{displayItem.text}</label></span>
<input id={displayItem.id} name={displayItem.label} type="text" placeholder={displayItem.placeholder} autoFocus/>
</li>
};
</ol>
</form>
</div>
)
Right, I think I know what you mean now. I've run the code in CodeSandbox and it makes sense. You want to do like a stepper for your registration where you ask a single question at a time.
Storing values in an object would still be a preferable way of doing this. But you need to get the label for the value and append it to the existing object. You can build your object with data when you go through your stepper.
Here is a working solution. Hopefully that's what you were looking for: https://codesandbox.io/s/unruffled-pasteur-76xux4?file=/src/App.js
I modified the onKeyPressed to grab the label from VALUES array based on the index we are currently on. Then that label is used as a key inside of the object where the value is the value from the event handler
const onKeyPressed = (ev, id) => {
if (ev.charCode === 13) {
ev.preventDefault();
const label = show[currentIndex].label; // grab the label based on the current index
const nextRender = currentIndex + 1;
if (nextRender < show.length) {
setCurrentIndex(nextRender);
setDetails({ ...details, [label]: ev.target.value }); // add the value to the details object where key is the label
} else {
//todo
}
}
};
I let you the code that it works like I think that you want
import { useState, useEffect, useRef, useMemo } from 'react';
const VALUES = [
{ id: 1, label: "name", text: "Hi, What is your Name?", placeholder: "Enter your full name" },
{ id: 2, label: "uname", text: "What shall we call you?", placeholder: "Enter a username" },
{ id: 3, label: "email", text: "Enter you email", placeholder: "Email" },
{ id: 4, label: "password", text: "Choose a password", placeholder: "make sure you dont forget" },
{ id: 5, label: "signup", text: "sign up", placeholder: ""},
];
export default function SignUp() {
const [show, setShow] = useState(VALUES)
const [currentIndex, setCurrentIndex] = useState(0);
const [details, setDetails] = useState({});
useEffect(() => {
}, [show]);
const onKeyPressed = ( ev, id ) => {
if (ev.charCode === 13) {
ev.preventDefault();
const nextRender = currentIndex + 1;
if (nextRender < show.length) {
setCurrentIndex(nextRender);
const label = VALUES[currentIndex].label;
details[label] = ev.target.value;
setDetails(details);
} else {
//todo
}
}
}
const displayItem = useMemo(() => show[currentIndex], [show, currentIndex]);
return (
<div className="container" id="container">
<div className="navigation">
<ol>
{Object.keys(details).map((key) => (
<li><a href="#" dataref={key}>{key}: {details[key]}</a></li>
))}
</ol>
</div>
<form id="sign-form" className="sign-form">
<ol className="questions">
<li onKeyPress={( KeyboardEvent ) => onKeyPressed(KeyboardEvent, displayItem.id)} key={displayItem.id}>
<span><label htmlFor={displayItem.label}>{displayItem.text}</label></span>
<input id={displayItem.id} name={displayItem.label} type="text" placeholder={displayItem.placeholder}
autoFocus/>
</li>
</ol>
</form>
</div>
)
}
sorry if this question is so simple, I want to pass props between components using state hooks, this is the first component:
import React, { useState } from "react";
import NewExpense from "../components/Expenses/NewExpense/NewExpense";
import Expenses from "../components/Expenses/Expenses";
import "../components/Expenses/NewExpense/ExpenseForm.css";
const DUMMY_EXPENSES = [
{
id: "e1",
title: "Lego Simpsons",
amount: 100.5,
date: new Date(2021, 1, 15),
},
{
id: "e2",
title: "Lego Mindstorms",
amount: 200.5,
date: new Date(2021, 2, 15),
},
{
id: "e3",
title: "Lego Batman",
amount: 300.5,
date: new Date(2021, 3, 15),
},
{
id: "e4",
title: "Lego Star Wars",
amount: 400.5,
date: new Date(2021, 4, 15),
},
];
const VerExpensesV3 = () => {
const [expenses, setExpenses] = useState(DUMMY_EXPENSES);
const [showForm, setShowForm] = useState(false);
const addExpenseHandler = (expense) => {
setExpenses((prevExpenses) => {
return [expense, ...prevExpenses];
});
};
const statusFormHandler = () => {
return showForm;
}
const showFormHandler = () => {
setShowForm(true);
};
return (
<div>
<h1>My expenses</h1>
{showForm && (
<NewExpense
onAddExpense={addExpenseHandler}
statusForm={statusFormHandler}
/>
)}
<div className="new-expense__actions">
<button onClick={showFormHandler}>Add New Expense</button>
</div>
<Expenses records={expenses} />
</div>
);
};
export default VerExpensesV3;
this is the second one:
import React, { useState } from 'react';
import './NewExpense.css';
import ExpenseFormV2 from './ExpenseFormV2';
const NewExpense = (props) => {
const [statusForm, setStatusForm] = useState(props.statusFormHandler);
console.log("el valor de statusForm en NewExpense es: "+statusForm);
const saveExpenseDataHandler =
(enteredExpenseData) => {
const expenseData = {
...enteredExpenseData,
id: Math.random().toString()
};
props.onAddExpense(expenseData);
};
return (
<div className="new-expense">
<ExpenseFormV2
onSaveExpenseData={saveExpenseDataHandler}
currentStatusForm={statusForm}/>
</div>
)
};
export default NewExpense;
So far I'm stuck because in these lines:
const [statusForm, setStatusForm] = useState(props.statusFormHandler);
console.log("el valor de statusForm en NewExpense es: "+statusForm);
I get "undefined", from my point of view the value passed is NULL from component 1 to component 2 and I don't understand why, so your comments and suggestions will be appreciated.
Thanks a lot
You're using the wrong property on props in <NewExpense>. Try this instead:
const [statusForm, setStatusForm] = useState(props.statusForm);
statusForm is the prop you defined on the component, and statusFormHandler is the name of the function you assign to the statusForm in the first component.
If you actually want your prop name to be statusFormHandler then disregard the above and use this
<NewExpense
onAddExpense={addExpenseHandler}
statusFormHandler={statusFormHandler}
/>
In my case I was using lower letter variable name.
const newExpense = (props) => {
const [isDefault, setDefault] = useState(true);
const saveNewExpenseHandler = (expenseData) => {
props.onSaveExpense(expenseData);
};
return (
<div className="new-expense">
<ExpenseForm onSavePressed={saveNewExpenseHandler} />
</div>
);
};
export default newExpense;
once I changed newExpense to NewExpense it all worked fine
I am trying to auto-selected a value from a list of data in the selected component. Kindly help.
have already tried with the isLoading flag as well.
If the selected value is available in the list then auto selection
if value not available then no issue.
import React, {useEffect, useState} from 'react';
import Select from 'react-select';
import './App.css';
function App() {
const [selectCity, setSelectCity] = useState(null);
const [cityOptions, setCityOptions] = useState([]);
useEffect(() => {
setSelectCity("Mumbai");
setCityOptions([{label: "Kolkata", value:"Kolkata"}, {label: "New Delhi", value:"New Delhi"}, {label: "Chennai", value:"Chennai"}, {label: "Mumbai", value:"Mumbai"}])
}, []);
const onCitySelect = (e) => {
console.log("Selected: ", e);
};
return (
<div className="App">
<Select
defaultValue={selectCity}
options={cityOptions}
onChange={onCitySelect}
/>
</div>
);
}
export default App;
Please pass object "setSelectCity({ label: "Kolkata", value: "Kolkata" });"
import './App.css';
import React, { useEffect, useState } from 'react';
import Select from 'react-select';
const App = () => {
const [selectCity, setSelectCity] = useState(null);
const [cityOptions, setCityOptions] = useState([]);
useEffect(() => {
setSelectCity({ label: "Kolkata", value: "Kolkata" });
setCityOptions([{ label: "Kolkata", value: "Kolkata" }, { label: "New Delhi", value: "New Delhi" }, { label: "Chennai", value: "Chennai" }, { label: "Mumbai", value: "Mumbai" }])
}, []);
const onCitySelect = (e) => {
console.log("Selected: ", e);
setSelectCity(e);
};
return (
<div className="App">
<h1>Hello MERN !!</h1>
<Select
value={selectCity}
options={cityOptions}
onChange={onCitySelect}
/>
</div>
);
}
export default App;
Try using the key prop
<Select
defaultValue={selectCity}
options={cityOptions}
onChange={onCitySelect}
key={selectCity}
/>
The defaultValue props cannot be set dynamically, so you have to set it manually. And please note that the defaultValue should be an object containing the label and the actual value.
import ReactDOM from "react-dom";
import React, { useEffect, useState } from "react";
import Select from "react-select";
function SelectMod() {
const [selectCity, setSelectCity] = useState(null);
const [cityOptions, setCityOptions] = useState([]);
useEffect(() => {
setCityOptions([
{ label: "Kolkata", value: "Kolkata" },
{ label: "New Delhi", value: "New Delhi" },
{ label: "Chennai", value: "Chennai" },
{ label: "Mumbai", value: "Mumbai" }
]);
}, []);
const onCitySelect = (e) => {
console.log("Selected: ", e);
};
return (
<div className="App">
<Select
defaultValue={{ label: "Mumbai", value: "Mumbai" }}
options={cityOptions}
onChange={onCitySelect}
/>
</div>
);
}
export default SelectMod;
const rootElement = document.getElementById("root");
ReactDOM.render(<SelectMod />, rootElement);
Or just set it as the default state value.
const [selectCity, setSelectCity] = useState({ label: "Mumbai", value: "Mumbai" });
--snips--
defaultValue={selectCity}
How can i print more than one key from an object inside an event handler?
here is the link to my code - https://codesandbox.io/s/suspicious-hamilton-erkqv
on line number 35 i call the event handler ChangeName with the object "x". I am able to print one key called 'name' (x.name)
How can i print all 3 props of the x object (name,id,type)??
tried to convert x into an array with object.keys(x) but when i loop through the output it gives an error saying cannot use map function
const ChangeName = x => {
setvalue(x.name);
};
<div>{value}</div> // display the results
your value is not an array its a object that why you can't use map method and this is how it works:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
function App() {
const [posts, setposts] = useState([]);
const [value, setvalue] = useState("");
useEffect(() => {
const posts = [
{
id: 1,
name: "Name 1",
type: "Type 1"
},
{
id: 2,
name: "Name 2",
type: "Type 2"
},
{
id: 3,
name: "Name 3",
type: "Type 3"
},
{
id: 4,
name: "Name 4",
type: "Type 4"
}
];
setposts(posts);
}, []);
const ChangeName = x => {
setvalue(x);
};
return (
<div>
<ul>
{posts.map(post => (
<li key={post.id}>
{post.name},{post.type}
<button onClick={() => ChangeName(post)}>More Info</button>
</li>
))}
</ul>
<div>
{value.name}
{value.id}
{value.type}
</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
A running code:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
function App() {
const [posts, setposts] = useState([]);
const [values, setvalue] = useState([]);
useEffect(() => {
const posts = [
{
id: 1,
name: "Name 1",
type: "Type 1"
},
{
id: 2,
name: "Name 2",
type: "Type 2"
},
{
id: 3,
name: "Name 3",
type: "Type 3"
},
{
id: 4,
name: "Name 4",
type: "Type 4"
}
];
setposts(posts);
}, []);
const ChangeName = x => {
console.log(Object.values(x));
Object.values(x).map(k => setvalue(k));
};
return (
<div>
<ul>
{posts.map(post => (
<li key={post.id}>
{post.name},{post.type}
<button onClick={ChangeName.bind(this, post)}>More Info</button>
</li>
))}
</ul>
<div>{values}</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
You need to set values not value.