i am new to React and i want to build a simple todo app.
I have made two components one to make a todo item and one for display it.
import { useRef, useState } from "react";
import TodoList from "./TodoList";
function TodoForm() {
const inputItem = useRef();
const [itemExist, setItem] = useState(false);
function submitHandler(event) {
event.preventDefault();
setItem(true);
}
return (
<div>
<form onSubmit={submitHandler}>
<label>
Todo-item
<input name="item" type="text" ref={inputItem}></input>
</label>
<input type="submit"></input>
</form>
{itemExist && <TodoList item={inputItem.current.value}></TodoList>}
</div>
);
}
export default TodoForm;
Component for display the doto items
function TodoList(props) {
return (
<ul>
<li>{props.item }</li>
</ul>
);
}
export default TodoList;
When i submit the first item i cant add other item.
I have tried to to change the itemExist to false under the
{itemExist && <TodoList item={inputItem.current.value}></TodoList>}
but error occurs. Uncaught Error: Too many re-renders
function TodoForm() {
const inputItem = useRef();
const [todos, setTodos] = useState([]);
function submitHandler(event) {
event.preventDefault();
let newTodos = [...todos, { inputItem.current.value }];
setTodos(newTodos);
}
return (
<div>
<form onSubmit={submitHandler}>
<label>
Todo-item
<input name="item" type="text" ref={inputItem}></input>
</label>
<input type="submit"></input>
</form>
{todos.map((todo, i) => {
return(
<TodoList key={`todo_${i}`} item={todo}></TodoList>
)
})}
</div>
);
}
Related
I am beginner and practicing on Library Management System in react. So I have components named BookDetails.js, BookList.js. BookDetails contains the form for entering Title and Description. So How can I pass the data entered from BookDetails to BookList and to dispaly from App.
import React, { useState } from 'react'
import BookList from './BookList'
const BookDetails = (props) => {
const [bookdetails, setbookDetails] = useState('')
const [desc, setDesc] = useState('')
const titleChangehandler = (e) => {
setbookDetails(e.target.value)
}
const descriptionChangehandler = (e) => {
setDesc(e.target.value)
}
const submitHandler = (e) => {
e.preventDefault()
return (
<div className='bookdetails'>
<form className='form_bookdetails' onSubmit={submitHandler}>
<div>
<label>Enter Title:</label>
<input type='text' value={bookdetails} onChange={titleChangehandler}></input>
</div>
<div>
<label>Enter Description:</label>
<input type='text' value={desc} onChange={descriptionChangehandler}></input>
</div>
<div>
<button type='submit'>Add Details</button>
</div>
</form>
</div>
)
}
}
export default BookDetails
BookList.js
import React from 'react'
import './BookList.css'
import BookDetails from './BookDetails'
const BookList = () => {
return (
<div className="booklist">
<header>BookList</header>
<BookDetails />
</div>
)
}
export default BookList
You need to use props. BookList state will have an update function that it will pass to the BookDetail via props. Example (CodeSandbox) with Todo with title & description.
BookDetail will invoke this method on every save which then would update the original list.
TodoList.js
export default function TodoList() {
const [todo, setTodo] = React.useState(null);
const [todoList, setTodoList] = React.useState([]);
React.useEffect(() => {
getTodos();
}, []);
function getTodos() {
console.log("===> fetch all todos!!");
fetchTodos().then((todos) => {
setTodoList(todos);
});
}
function editTodo(todo) {
console.log("===> set todo => ", todo);
setTodo(todo);
}
function handleUpdate(updatedTodo) {
// update Todo
const updatedTodos = todoList.map((el) =>
el.id === updatedTodo.id ? updatedTodo : el
);
setTodoList(updatedTodos);
setTodo(null);
}
return (
<div>
<ul>
{todoList.map((item) => (
<li key={item.id}>
{item.title}, {item.description}
<button onClick={() => editTodo(item)}>edit</button>
</li>
))}
</ul>
{todo && <TodoDetail todo={todo} updateTodo={handleUpdate} />}
</div>
);
}
TodoDetail.js
import React from "react";
export default function TodoDetail(props) {
const [todo, setTodo] = React.useState(props.todo);
console.log("todo =>", todo);
function handleChange(key, value) {
console.log("===> todo changed!");
setTodo({
...todo,
[key]: value
});
}
function handleSubmit() {
// api PUT on todo
console.log("===> todo edit submit!!");
props.updateTodo(todo);
}
return (
<div>
<form onSubmit={handleSubmit}>
<label htmlFor="title">
<input
value={todo.title}
onChange={(e) => handleChange("title", e.target.value)}
/>
<input
value={todo.description}
onChange={(e) => handleChange("description", e.target.value)}
/>
</label>
<button type="submit">submit</button>
</form>
</div>
);
}
You can store the list of books in your BookList component like
const [bookList, setBookList] = useState([])
This way your BookList component has access to the books. You can then create a function to add books to the list
function addBook(book) {
setBookList([...bookList, book])
}
Then pass the addBook() function to the BookDetails component to use it on submit.
<BookDetails addBook={addBook}
Now BookDetails can access the function as a prop
props.addBook("pass new book here")
I get error "Uncaught TypeError: recipeList.push is not a function"
when i try to submit data to localStorage. If i dont push but just rewrite existing data withouth using push() then i get no errors. I think this should be some newbie mistake.
AddRecipe.js page looks like this
import { useNavigate } from "react-router-dom";
import NewRecipeForm from "../components/recipes/NewRecipeForm";
const AddRecipe = () => {
const Navigate = useNavigate();
const setLocalStorage = (recipe) => {
const recipeList = JSON.parse(localStorage.getItem("recipe")) || [];
recipeList.push(recipe);
localStorage.setItem("recipe", JSON.stringify(recipeList));
Navigate("/");
};
// const RecipeFormHandler = (recipeData) => {
// localStorage.setItem("recipe", JSON.stringify(recipeData));
// Navigate("/");
// };
return (
<section>
<NewRecipeForm onAddRecipe={setLocalStorage} />
</section>
);
};
export default AddRecipe;
NewRecipeForm.js looks like this and i am trying to save recipeData to localStorage
import classes from "./NewRecipeForm.module.css";
import Card from "../ui/Card";
import { useRef } from "react";
const NewRecipeForm = (props) => {
const titleRef = useRef();
const imgRef = useRef();
const ingredientsRef = useRef();
const descriptionRef = useRef();
function submitHandler(event) {
event.preventDefault();
const enteredTitle = titleRef.current.value;
const enteredImg = imgRef.current.value;
const enteredingredients = ingredientsRef.current.value;
const enteredDescription = descriptionRef.current.value;
const recipeData = {
id: (Math.random() * 100).toString(),
title: enteredTitle,
image: enteredImg,
ingredients: enteredingredients,
description: enteredDescription,
};
props.onAddRecipe(recipeData);
}
return (
<Card>
<form className={classes.form} onSubmit={submitHandler}>
<div className={classes.control}>
<label htmlFor="title">Recipe Name</label>
<input type="text" required id="title" ref={titleRef} />
</div>
<div className={classes.control}>
<label htmlFor="image">Recipe Image</label>
<input type="url" required id="image" ref={imgRef} />
</div>
<div className={classes.control}>
<label htmlFor="ingredients">Ingredients</label>
<textarea
type="text"
required
id="ingredients"
rows="5"
ref={ingredientsRef}
/>
</div>
<div className={classes.control}>
<label htmlFor="description">Description</label>
<textarea id="description" required rows="5" ref={descriptionRef} />
</div>
<div className={classes.actions}>
<button type="reset">Reset</button>
<button type="submit">Add Recipe</button>
</div>
</form>
</Card>
);
};
export default NewRecipeForm;
I just used strings instead of Ref's. The replication of it got me to the working SHORT code here: SandBox.
NewRecipeForm.js
import { NewRecipeForm } from "./Child";
export default function App() {
const setLocalStorage = (recipe) => {
const recipeList = JSON.parse(localStorage.getItem("recipe")) || [];
recipeList.push(recipe);
localStorage.setItem("recipe", JSON.stringify(recipeList));
console.log(recipeList);
};
return (
<div className="App">
<NewRecipeForm onAddRecipe={setLocalStorage} />
</div>
);
}
Child.js
export const NewRecipeForm = (props) => {
function submitHandler(event) {
const recipeData = {
id: (Math.random() * 100).toString(),
title: "enteredTitle",
image: "enteredImg",
ingredients: "enteredingredients",
description: "enteredDescription"
};
props.onAddRecipe(recipeData);
}
return <button onClick={() => submitHandler()}>Click to print</button>;
};
I created a Todo-list app in react
and inside each item placed one radio button and one edit, delete button for each and every item when a user enters in the input text.
Basically, the edit, delete buttons are disabled at first.
when a user clicks on the radio button then the particular item buttons get enabled.
But here I am getting all the buttons in each item gets enabled inside the list
import React,{useState} from 'react';
import Modal from 'react-modal';
import {AiFillDelete,AiFillEdit} from 'react-icons/ai';
import './App.css';
Modal.setAppElement('#root')
function App() {
const [todos,setTodos] = useState([{id:0,text:"item1"},{id:1,text:"item2"}])
const [todo,setTodo] = useState("")
const [todoEditing,setTodoEditing] = useState(null)
const [editingText,setEditingText] = useState("")
const [deleteItem,setDeleteItem] = useState()
const [editItem,setEditItem]=useState()
const [modalIsOpen,setModalIsOpen] = useState()
function handleSubmit(e){
e.preventDefault()
const newTodo = {
id:todos.length,
text : todo,
}
setTodos([newTodo].concat(...todos))
setTodo("")
}
function deleteTodo(id){
const updateTodos = [...todos].filter((todo)=>todo.id !== id)
setTodos(updateTodos)
}
function editTodo(id){
const updateTodos = [...todos].map((todo) => {
if(todo.id===id){
todo.text = editingText
}
return todo
})
setTodos(updateTodos)
setTodoEditing(null)
setEditingText("")
}
const handleRadioBtnItem = (event) =>{
setDeleteItem(event.target.value);
setEditItem(event.target.value);
}
return (
<div className="App">
<div className='todo-head'>
<h1 className='ForHeading'>Todo List</h1>
<form onSubmit={handleSubmit}>
<input className='User-Input' type='text' onChange={(e)=>setTodo(e.target.value)} value={todo}/>
<button className='Add-Btn' type='submit' disabled={!todo}>Add Todo</button>
</form>
</div>
{todos.map((todo)=>
<ul className='ul-Style' key={todo.id} id={todo.id}>
<input className='Rdo' type='radio' onClick={handleRadioBtnItem}/>
{todoEditing === todo.id ?
(
<div>
<Modal
isOpen={modalIsOpen}
shouldCloseOnOverlayClick={false}
style={
{
overlay:{
backgroundColor:'gray'
},
content:{
textAlign:'center'
}
}
}
>
<h2>Edit Items</h2>
<input type='text' onChange={(e)=> setEditingText(e.target.value)} value={editingText}/>
<div>
<button onClick={()=>editTodo(todo.id)} disabled=''>Save</button>
<button onClick={()=>setModalIsOpen(false)}>Close</button>
</div>
</Modal>
{todo.text}
</div>
)
:
(
<p>{todo.text}</p>
)
}
<button
className='Edit-Btn'
onClick={()=>{setTodoEditing(todo.id);setModalIsOpen(true)}}
disabled={!editItem}><AiFillEdit/>
</button>
<button
className='Del-Btn'
onClick={()=>deleteTodo(todo.id)}
disabled={!deleteItem}><AiFillDelete/>
</button>
</ul>)}
</div>
);
}
export default App;
I want the user to input some text, click submit and the text will be displayed below.
I was able to get the input text as a whole, and print it in console. But I don't know how to display the text.
Here's my code:
https://codesandbox.io/s/ecstatic-curie-ej6og?file=/src/App.js
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [enteredText, setEnteredText] = useState("");
const textChangeHandler = (i) => {
setEnteredText(i.target.value);
//console.log(i.target.value);
};
const submitHandler = (event) => {
event.preventDefault();
const x = enteredText;
console.log(x);
setEnteredText("");
};
return (
<div className="App">
<h1>Get user input</h1>
<form onSubmit={submitHandler}>
<input
placeholder="type something"
type="text"
value={enteredText}
onChange={textChangeHandler}
/>
<button type="submit" >
Submit
</button>
</form>
<p>You just typed: {x}</p> // This is wrong. x is out of scope. But i'm not sure how to write this line.
</div>
);
}
You can use an additional state variable to store the "submitted text". You would update that new state variable with the text from the enteredText state variable before emptying it. You could also make sure the "submitted text" has a value before displaying it.
I am including code that does what I described, but you can also try implementing it on your own before looking at it:
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [enteredText, setEnteredText] = useState("");
const [submittedText, setSubmittedText] = useState(null);
const textChangeHandler = (i) => {
setEnteredText(i.target.value);
//console.log(i.target.value);
};
const submitHandler = (event) => {
event.preventDefault();
setSubmittedText(enteredText);
setEnteredText("");
};
return (
<div className="App">
<h1>Get user input</h1>
<form onSubmit={submitHandler}>
<input
placeholder="type something"
type="text"
value={enteredText}
onChange={textChangeHandler}
/>
<button type="submit" >
Submit
</button>
</form>
{submittedText && (<p>You just typed: {submittedText}</p>)}
</div>
);
}
I found this tutorial on how to create a react quizz app on youtube link to tutorial
I am trying to set the title based on the current Select Option Value when submitting the form.
Currently I managed to change the title only when a different option is selected.
import React, { useState, useEffect, useRef } from "react";
import "./App.css";
import axios from "axios";
import FlashcardList from "./components/FlashcardList";
function App() {
const [flashcards, setFlashcards] = useState([]);
const [categories, setCategories] = useState([]);
const [title, setTitle] = useState("General Knowledge");
const categoryEl = useRef();
const amountEl = useRef();
useEffect(() => {
axios.get("https://opentdb.com/api_category.php").then((res) => {
setCategories(res.data.trivia_categories);
});
}, []);
function decodeString(str) {
const textArea = document.createElement("textarea");
textArea.innerHTML = str;
return textArea.value;
}
function handleSubmit(e) {
e.preventDefault();
axios
.get("https://opentdb.com/api.php", {
params: {
amount: amountEl.current.value,
category: categoryEl.current.value,
},
})
.then((res) => {
setFlashcards(
res.data.results.map((questionItem, index) => {
const answer = decodeString(questionItem.correct_answer);
const options = [...questionItem.incorrect_answers, answer];
return {
id: `${index} - ${Date.now()}`,
question: decodeString(questionItem.question),
answer: answer,
options: options.sort(() => Math.random() - 0.5),
};
})
);
});
}
function getTitle(e) {
setTitle(e.target.options[e.target.selectedIndex].text);
}
return (
<>
<form className="header" onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="category">Category</label>
<select id="category" ref={categoryEl} onChange={getTitle}>
{categories.map((category) => {
return (
<option value={category.id} key={category.id}>
{category.name}
</option>
);
})}
</select>
</div>
<div className="form-group">
<label htmlFor="amount">Number Of Questions</label>
<input
type="number"
id="amount"
min="1"
step="1"
defaultValue={10}
ref={amountEl}
/>
</div>
<div className="form-group">
<button className="btn">Generate</button>
</div>
</form>
<div className="container">
<h1 className="title">{title}</h1>
<FlashcardList flashcards={flashcards} />
</div>
</>
);
}
export default App;
Code
Live demo
You can set the category as soon the categories are fetched. Can just use the zeroth element to set the title.
useEffect(() => { axios.get("https://opentdb.com/api_category.php").then((res) => {
setCategories(res.data.trivia_categories);
setTitle(res.data.trivia_categories[0]);
});
}, []);