Controlling one element inside an array - reactjs

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>.

Related

how to use one state in two component / shere state

import React, { useState } from 'react';
export const Context = React.createContext();
export const ContextProvider = ({ children }) => {
const [open, setOpen] = useState(false);
return (
<Context.Provider value={{ open, setOpen }}>{children}</Context.Provider>
);
};
componen1
import React, { useContext } from 'react';
import { Context } from '../context/Context';
export default function SideNav({ surahs }) {
const { open, setOpen } = useContext(Context);
return (
<div className={`${open} bg-red`}></div>
);
}
componen2
import React, { useContext } from 'react';
import { Context } from '../context/Context';
export default function Nav() {
const { open, setOpen } = useContext(Context);
const clickHandler = () => {
setOpen((o) => (o === false ? 'hidden' : 'block'));
};
return (
<button onClick={clickHandler}></button>
);
}
how to trigger component 2, once the button on component 1 is clicked it will trigger component 2 and add or remove block and hidden classes
this confuses me
First, you need a state to monitor which category is currently active.
const [category, setCategory] = useState("All"); // possible values [All, Web, App, Design]
Then, you need to set and unset the item-active class depending on which category is active.
You can do the primitive way
className={`work__item ${category === "All" ? "item-active" : ""}`}
Or using a small utility library clsx
clsx("work__item", { "item-active": category === "All"})
Putting everything together:
import React, { useMemo, useState } from "react";
import "./portfolio.css";
import Menu from "./Menu";
import clsx from "clsx";
const Portfolio = () => {
const [category, setCategory] = useState("All");
// a computed variable "items" which update itself only when "category" is changed
const items = useMemo(
// array filter, get items when category matches or when category is selected to "All"
() => Menu.filter(({ category: c }) => c === category || category === "All"),
[category] // watch "category" state, update "items" when "category" changes
);
return (
<div className="work__filters container">
<span
className={clsx("work__item", { "item-active": category === "All" })}
onClick={() => setCategory("All")}
>
All
</span>
<span
className={clsx("work__item", { "item-active": category === "Web" })}
onClick={() => setCategory("Web")}
>
Web
</span>
<span
className={clsx("work__item", { "item-active": category === "App" })}
onClick={() => setCategory("App")}
>
App
</span>
<span
className={clsx("work__item", { "item-active": category === "Design" })}
onClick={() => setCategory("Design")}
>
Design
</span>
</div>
);
};

React remove item from array by using filter function, why does the uuid keeps changing?

I'm making a To Do List app using React, I made 2 components which is the App component and the ToDoItem component, In the App component I have 2 states one of them is being used to add tasks and the second is to set the items array, In the ToDoItem component I have a state that is being used to mark items (Setting its text decoration to line through).
I'm also using UUID to make a uniqe key to each one of the components,
The problem is that everytime I try to remove an item from the list, it doesn't working and its also changes the uuids
App component:
import React, {useState} from "react";
import ToDoItem from "./ToDoItem";
import { v4 as uuidv4 } from 'uuid';
function App() {
const [item, setItem] = useState("");
const [items, setItems] = useState([]);
function handleChange(event){
const newItem = event.target.value;
setItem(newItem);
}
function addItem(){
if(item.length > 0){
console.log(item + " inserted!");
setItems( (prevItems) =>{
return[...prevItems, item];
});
setItem("");
}
}
function deleteItem(id){
setItems((prevItems) =>{
return prevItems.filter(
(key) => {
return key !== id;
}
)
});
}
return (
<div className="container">
<div className="heading">
<h1>To-Do List</h1>
</div>
<div className="form">
<input type="text" onChange={handleChange} value={item}/>
<button onClick={addItem}>
<span>Add</span>
</button>
</div>
<div>
<ul>
{ items.map((todoItem) => (
<ToDoItem
key={uuidv4()}
id={uuidv4()} //Must be used in order to be able to use it or deleting an item
item={todoItem}
onDelete={deleteItem}
/>
))}
</ul>
</div>
</div>
);
}
export default App;
ToDoItem component:
import React, {useState} from "react";
import { BiTrash } from "react-icons/bi";
function ToDoItem(props){
const [isChecked, setChecked] = useState(false);
function markItem(){
setChecked(prevValue => {
return !prevValue;
});
}
return(
<li ><span onClick={markItem} style={{textDecoration: isChecked ? "line-through" : "none"}}>
{props.item}</span> {isChecked ?
<span className="trash" onClick={() => {
props.onDelete(props.id);
}}>
<BiTrash/></span> : null}</li>
);
}
export default ToDoItem;
On every App render you make a function call (uuid()) which results in new keys (unmount) and new id, you should move the id prop to your todoItem state on creation:
// on creating a todo item
const todoItem = { id: uuid() }

Why am I getting a null value in localStorage even after parsing it?

I'm trying to build a crypto tracker where you can add the items by clicking a button. Each time the button is clicked, the array should be added to the storage with the name "crypto" and then on another component where it is the portfolio one we should be able to get the items.
Here is where I set the item to an array whenever I click the add button:
import React, {useEffect, useState} from 'react'
import axios from 'axios'
import './tracker.css'
import Navigation from './Nav.js'
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
function Tracker() {
const [data, setData] = useState([])
const [portfolio, setPortfolio] = useState([])
useEffect(() => {
setInterval(() => {
const fetchData = async () => {
const result = await axios('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=100&page=1&sparkline=false' , {
'mode': 'no-cors',
'headers': {
'Access-Control-Allow-Origin': '*',
}
})
setData(result.data)
}
fetchData()
}, 1000)
}, [])
return (
<div>
<Navigation />
<div className="tracker__names">
<b>Coins</b>
<b>Symbol</b>
<b>Price</b>
<b>Market Cap</b>
</div>
{data.map((coins, i) => {
const addToPortfolio = () => {
setPortfolio([...portfolio, data[i]])
localStorage.setItem('crpyto', JSON.stringify(portfolio))
}
return (
<>
<div className="tracker__main">
<div className="tracker__img">
<img src={coins.image} className="tracker__image"/>
<button key={i} onClick={addToPortfolio}>{coins.id}</button>
</div>
<div className="tracker__symbol">
<p>{coins.symbol}</p>
</div>
<div className="tracker__price">
<p></p>
${coins.current_price}
</div>
<div className="tracker__market">
<p></p>
${coins.market_cap}
</div>
</div>
</>
)
})}
</div>
)
}
export default Tracker
Here is the component where I want to get the item:
import React, {useState, useEffect} from 'react'
import Navigation from './Nav.js'
function Portfolio() {
const [value, setValue] = useState(JSON.parse(localStorage.getItem('crypto')) || '')
useEffect(() => {
console.log(value)
}, )
return (
<div>
<Navigation />
{value}
</div>
)
}
export default Portfolio
It is because useState is executed before JSON.parse(localStorage.getItem('crypto')) and once you get the value from the localstorage, component doesn't re-render.
Instead do:
useEffect(() => {
const crypto = JSON.parse(localStorage.getItem('crypto'))
if(crypto) setValue(crypto)
}, [])
In React you can't set a state var and on the next line save it in localStorage (or even read it). This because setPortfolio is async!
To solve this you have I think 2 ways:
store value and not state variable:
localStorage.setItem('crpyto', JSON.stringify([...portfolio, data[i]]))
use an useEffect hook:
useEffect(() => {
localStorage.setItem('crpyto', JSON.stringify(portfolio))
}, [portfolio])
First of all, when yo uare setting state like this, in the next block of code, portfolio won't necessarily have the updated state.
setPortfolio([...portfolio, data[i]])
localStorage.setItem('crpyto', JSON.stringify(portfolio))
update the portfolio like this.
const newPortfolio = [...portfolio, data[i]];
setPortfolio(newPortfolio )
localStorage.setItem('crpyto', JSON.stringify(newPortfolio))

Delete an item on click

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>

How to dynamically show a list of data by React?

I am new to React and trying to display list of data with checkbox and inputbox. In details, I want to grab a series of data from database and put each record into a <div> like element with checkbox and inputbox. So I can check the record and change the data and then do the re-save action after clicking a button. Since the number of data will keep changing, how to make it dynamic? Also, how can I mark down which records are being checked and need to be saved? Thanks!
Code:
App.js:
import React from 'react';
import { useState, useEffect } from 'react';
import { Menu, Message, Button, Segment } from 'semantic-ui-react';
import SemanticDatepicker from 'react-semantic-ui-datepickers';
import 'react-semantic-ui-datepickers/dist/react-semantic-ui-datepickers.css';
import Form from './Form';
export default function App(props){
const [currentDate, setNewDate] = useState(null);
const onChange = (event, data) => setNewDate(data.value);
const loadData= (event) => {
return (<Form date = {currentDate} />);
};
return (
<div className="App">
<div>
<Menu borderless>
<Menu.Item >
<div >
<img src={logo} alt="image" />
</div>
</Menu.Item>
</Menu>
<Segment>
<SemanticDatepicker onChange={onChange} />
<Button onClick={loadData}>Load Data</Button>
</Segment>
<Segment>>
</Segment>
//Here will diaplyed returned list of data after click button
</div>
</div>
)
};
Simple JSON response:
{
"APPLE":{
"PRICE":100
},
"ORANGE":{
"PRICE":20
},
"PEAR":{
"PRICE":10
}
}
You could use your data to build your form.
You need to build the state from your data.
Also, map your input fields with respect to your state.
If the state needs different input fields, you could define your input fields in deriveStateFromData.
You can check the example here
For Object.keys, you could check the docs here
import React from 'react';
const price = {
"APPLE":{
"PRICE":100
},
"ORANGE":{
"PRICE":20
},
"PEAR":{
"PRICE":10
}
}
function deriveStateFromData(data) {
let state = {}
Object.keys(data).forEach(key => {
state[key] = data[key]['PRICE']
})
return state;
}
function MyForm({ data }) {
const [form, setFormData] = React.useState(deriveStateFromData(data));
const handleChange = e => {
setFormData({ ...form, [e.target.name]: Number(e.target.value) });
}
console.log(form)
return (
<>
{Object.keys(form).map(key => {
return (
<div>
<label>{key}</label>
<input
name={key}
value={form[key]}
onChange={handleChange}
/>
</div>
)
})}
</>
)
}
const App = () => <MyForm data={price} />
export default App;

Resources