I'm new to the react and I would like to know I made a function that makes filtering depending on the boolean values
let [filterState, setFilterSTate] = useState("all");
let filteredUser = todos.filter((е) => {
if (filterState === "active") {
return !е.done;
}
if (filterState === "completed") {
return е.done;
}
return true;
});
in the console, everything is displayed, but I can't figure out how to make it work in the browser itself and only the necessary tricks are displayed
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./index.css";
const TodoList = () => {
const [newTodo, setNewTodo] = useState("");
const [todos, setTodos] = useState([
{ done: true, text: "Hey", id: 1 },
{ done: false, text: "There", id: 2 },
{ done: false, text: "Dima", id: 3 },
]);
const [id, setId] = useState(4);
const toggleDone = (id) => {
setTodos(
todos.map((todo) => ({
...todo,
done: id === todo.id ? !todo.done : todo.done,
}))
);
};
const updateTodo = (id, e) => {
setTodos(
todos.map((todo) => ({
...todo,
text: id === todo.id ? e.target.value : todo.text,
}))
);
};
const onDelete = (id) => {
setTodos(todos.filter((todo) => todo.id !== id));
};
const updateNewTodo = (e) => {
setNewTodo(e.target.value);
};
const onAdd = () => {
setTodos([
...todos,
{
text: newTodo,
done: false,
id,
},
]);
setId(id + 1);
setNewTodo("");
};
let [filterState, setFilterSTate] = useState("all");
let filteredUser = todos.filter((е) => {
if (filterState === "active") {
return !е.done;
}
if (filterState === "completed") {
return е.done;
}
return true;
});
// let filteredComplited = todos.filter(function (e) {
// return e.done === true;
// });
// console.log("filteredComplited", filteredComplited);
// let filteredActive = todos.filter(function (e) {
// return e.done === false;
// });
// console.log("filteredActive", filteredActive);
// let filteredAll = todos;
// console.log("filteredAll", filteredAll);
return (
<div className="todos">
Todo List
{todos.map((todo) => (
<div key={todo.id} className="todo">
<input
type="checkbox"
value={todo.done}
onChange={() => toggleDone(todo.id)}
/>
<input
type="text"
value={todo.text}
onChange={(evt) => updateTodo(todo.id, evt)}
/>
<button onClick={() => onDelete(todo.id)}>Delete</button>
</div>
))}
<div className="todo">
<input type="text" value={newTodo} onChange={updateNewTodo} />
<button onClick={() => onAdd()}>Add</button>
</div>
<button
onclick={() =>
todos.map((t) => `${t.done ? "x" : ""} ${t.text}`).join("\n")
}
>
Save
</button>
<button onClick={() => setFilterSTate("completed")}>Complited</button>
<button onClick={() => setFilterSTate("active")}>Active</button>
<button onClick={() => setFilterSTate("all")}>All</button>
<pre>filterState: {JSON.stringify(filterState, null, 2)}</pre>
<br />
<pre>{JSON.stringify(filteredUser, null, 2)}</pre>
</div>
);
};
ReactDOM.render(<TodoList />, document.getElementById("todos"));
Related
I'm making a todo list in react js. Each time a new todo item is created, some buttons are appended next to it along with a edit input text box. I'm trying to avoid using refs but purely usestate for my case, however I can't figure out how to do it. At its current state, all edit text inputs are using the same state and that brings focus loss along with other issues. I'd highly appreciate any suggetsions.
import "./theme.css"
import * as appStyles from "./styles/App.module.css"
import * as todoStyles from "./styles/Todo.module.css"
import { useState } from "react"
const initialState = [
{
id: "1",
name: "My first ToDo",
status: "new",
},
]
export function App() {
const [numofItems, setNumofItems] = useState(2)
const [newToDo, setnewToDo] = useState('');
const [todos, setTodos] = useState(initialState);
const [editTodo, setEditTodo] = useState({name: ""});
const onAddTodo = () => {
setnewToDo("");
setTodos((old) => [
...old,
{ id: numofItems.toString(), name: newToDo, status: "new" },
])
setNumofItems(numofItems + 1);
}
deleteList = () =>{
setTodos([]);
}
const handleEdit = (id, description) =>{
let el = todos.map((item) => {if(item.id === id) {item.name = description} return item});
setTodos(el);
setEditTodo('');
}
const handleMove = (id, position) =>{
const search = obj => obj.id === id;
const todoIndex = todos.findIndex(search);
if(position === "up"){
if (todos[todoIndex - 1] === undefined) {
} else {
const newTodo1 = [...todos];
const temp1 = newTodo1[todoIndex - 1];
const temp2 = newTodo1[todoIndex]
newTodo1.splice(todoIndex - 1, 1, temp2);
newTodo1.splice(todoIndex, 1, temp1);
setTodos([...newTodo1]);
}
}
else if(position === "down"){
if (todos[todoIndex + 1] === undefined) {
} else {
const newTodo1 = [...todos];
const temp1 = newTodo1[todoIndex + 1];
const temp2 = newTodo1[todoIndex]
newTodo1.splice(todoIndex + 1, 1, temp2);
newTodo1.splice(todoIndex, 1, temp1);
setTodos([...newTodo1]);
}
}
}
const Todo = ({ record }) => {
return <li className={todoStyles.item}>{record.name}
<button className={appStyles.editButtons} onClick={() => deleteListItem(record.id)} >Delete</button>
<button className={appStyles.editButtons} onClick={() => handleEdit(record.id, editTodo.name)}>Edit</button>
<button className={appStyles.editButtons} onClick={() => handleMove(record.id, "down")}>Move down</button>
<button className={appStyles.editButtons} onClick={() => handleMove(record.id, "up")}>Move up</button>
<input className={appStyles.input}
type = "text"
name={`editTodo_${record.id}`}
value = {editTodo.name}
onChange={event => {event.persist();
setEditTodo({name: event.target.value});}}
/></li>
}
const deleteListItem = (todoid) => {
setTodos(todos.filter(({id}) => id !== todoid))
}
return (
<>
<h3 className={appStyles.title}>React ToDo App</h3>
<ul className={appStyles.list}>
{todos.map((t, idx) => (
<Todo key={`todo_${idx}`} record={t} />
))}
</ul>
<div className={appStyles.actions}>
<form>
<label>
Enter new item:
<input className={appStyles.input} type="text" name="newToDo" value={newToDo} onChange={event => setnewToDo(event.target.value)}/>
</label>
</form>
<button
className={appStyles.button}
onClick={onAddTodo}
>
Add
</button>
<br></br>
<button className={appStyles.button} onClick={this.deleteList}>
Delete List
</button>
</div>
</>
)
}
Never define components in the body of another component. It will result in unmount/mount of that element every time it's rendered.
Here is how you can split up the Todo component from you App:
const Todo = ({ record, onDelete, onEdit, onMove }) => {
const [inputValue, setInputValue] = useState(record.name);
return (
<li className={todoStyles.item}>
{record.name}
<button className={appStyles.editButtons} onClick={() => onDelete()}>
Delete
</button>
<button
className={appStyles.editButtons}
onClick={() => onEdit(inputValue)}
>
Edit
</button>
<button className={appStyles.editButtons} onClick={() => onMove("down")}>
Move down
</button>
<button className={appStyles.editButtons} onClick={() => onMove("up")}>
Move up
</button>
<input
className={appStyles.input}
type="text"
value={inputValue}
onChange={(event) => {
setInputValue(event.target.value);
}}
/>
</li>
);
};
function App() {
return (
<>
<ul className={appStyles.list}>
{todos.map((t, idx) => (
<Todo
key={`todo_${idx}`}
record={t}
onDelete={() => deleteListItem(t.id)}
onEdit={(description) => handleEdit(t.id, description)}
onMove={(position) => handleMove(t.id, position)}
/>
))}
</ul>
</>
);
}
Note: I've shown only the interesting bits, not your entire code.
If you're going to do it this way I would suggest using useReducer instead of useState.
const initialState = [
{
id: "1",
name: "My first ToDo",
status: "new",
},
]
export const types = {
INIT: 'init',
NEW: 'new'
}
export default function (state, action) {
switch (action.type) {
case types.INIT:
return initialState;
case types.NEW:
return { ...state, { ...action.item } };
default:
return state;
}
}
Now in your component you can use it like this:
import {useReducer} from 'react';
import reducer, { initialState, types } from './wherever';
const [state, dispatch] = useReducer(reducer, initialState);
const handleSubmit = (event) => {
event.preventDefault();
dispatch({ type: types.NEW, item: event.target.value });
}
I'm trying to get the updated/newly created records and send it to the backend in "queryparam"
import React, { useState, useEffect } from "react";
//import { Container, Row, Col } from "reactstrap";
// import Box from "#mui/material/Box";
// import "bootstrap/dist/css/bootstrap.css";
// import "./index.css";
const Index = () => {
const [formValues, setFormValues] = useState([
{ orderno: 0, inputValue1: "", inputValue2: "", checked: false }
]);
const [isDisabled, setDisabled] = useState(false);
// const [inputVal1, setInputval1] = useState();
const [isChanged, setIsChanged] = useState([]);
const [error, setError] = useState(false);
const [orderNumber, setOrderNumber] = useState(1);
const addFormFields = () => {
// if (error) {
// setDisabled(false)
// }
// else {
// setDisabled(true)
// }
setFormValues((prevState) => [
...prevState,
{
orderno: orderNumber,
inputValue1: "",
inputValue2: "",
checked: false
}
]);
setOrderNumber((prev) => prev + 1);
};
const removeFormFields = (i) => {
let newFormValues = [...formValues];
newFormValues.splice(i, 1);
setFormValues(newFormValues);
setOrderNumber((prev) => prev - 1);
};
const onChangeFieldValue = (index, key, value) => {
setFormValues((prevState) => {
let copyState = [...prevState];
if (value?.length > 0) {
setError(false);
} else {
setError(true);
}
copyState[index][key] = value;
return copyState;
});
};
const saveFields = (e) => {
const queryparam = {
inputData: formValues
};
setIsChanged(queryparam);
setIsChanged((prevState, nextState) => {
let copyState = [];
if (prevState === nextState) {
copyState = [...prevState];
} else {
copyState = [...nextState];
}
return copyState;
});
console.log(isChanged, "lllllllll");
};
// useEffect(() => {
// saveFields()
// }, [isChanged])
return (
<>
{formValues.map((element, index) => (
<div className="form-inline" key={index}>
{/* <Container>
<Row>
<Col xs="12" sm="6"> */}
<label>{index + 1}</label>
<input
type="text"
value={element.inputVal1}
onChange={(e) =>
onChangeFieldValue(index, "inputValue1", e.target.value)
}
/>
<input
type="text"
value={element.inputVal2}
required
onChange={(e) =>
onChangeFieldValue(index, "inputValue2", e.target.value)
}
/>
{/* </Col>
<Col xs="12" sm="6">
<Box> */}
<button
className={`button ${error ? "add" : "btn-secondary"}`}
type="button"
disabled={error}
onClick={(e) => addFormFields(e)}
>
Add{console.log(isDisabled, "ooooooo", error)}
</button>
<button
type="button"
className="button remove"
onClick={() => removeFormFields(index)}
>
Remove
</button>
{/* </Box>
</Col>
</Row>
</Container> */}
</div>
))}
{/* <Row>
<Col sm="6" md={{ size: 4, offset: 2 }}>
<Box> */}
<button
type="button"
className="button save"
onClick={(e) => saveFields(e)}
>
Save
</button>
<button
type="button"
className="button remove"
//onClick={(e) => cancelFields(e)}
>
cancel
</button>
{/* </Box>
</Col>
</Row> */}
</>
);
};
export default Index;
https://codesandbox.io/s/black-fire-ixeir?file=/src/App.js:3662-3701
In the above link,
Step1 : when I add values for inputs "123" in input1 and "345" in input2.Then when I click on "Save" the values sent are {"input1":"123","input2":"345"}.
Step2: Again I try to add one row for inputs "456" in input1 and "678" in input2.Then when I click on save the values sent are {"input1":"456","input2":"678"}.
When I edit the existing row, for example the first row values and when I click on "Save" then only the first row value should be sent as the second row values hasn't changed.Also, If I add new rows then the newly added only should be sent if the existing row values aren't changed. Is there any way to send only the updated/newly created values to the backend using react hook
You could use a separate changes object to track changes by orderno property; saved during add/update/remove, and committed when submitting.
const [changes, setChanges] = useState({});
...
const addFormFields = () => {
const newItem = {
orderno: orderNumber,
inputValue1: "",
inputValue2: "",
checked: false,
type: "add"
};
setFormValues((values) => [...values, newItem]);
setChanges((changes) => ({
...changes,
[newItem.orderno]: newItem
}));
setOrderNumber((prev) => prev + 1);
};
const removeFormFields = (index) => {
const item = {
...formValues[index],
type: "remove"
};
setFormValues((values) => values.filter((el, i) => i !== index));
setChanges((changes) => ({
...changes,
[item.orderno]: item
}));
};
const onChangeFieldValue = (index, key, value) => {
const item = {
...formValues[index],
[key]: value,
type: "edit"
};
setFormValues((prevState) => {
if (value?.length > 0) {
setError(false);
const copyState = [...prevState];
copyState[index] = item;
return copyState;
} else {
setError(true);
return prevState;
}
});
setChanges((changes) => ({
...changes,
[item.orderno]: item
}));
};
const saveFields = (e) => {
const queryparam = {
inputData: Object.values(changes)
};
console.log("Changes to commit", queryparam);
setChanges({});
};
How to map through two arrays and send data as props to other component? I want to send data from price and gamedet as a prop to Wishlist Component.As I am new to react, suggest me if this code is not good or unclean too, thanks in advance.
This is the dumb way I send whole array to Wishlist component :
price.map((game1) => {
let temp = {
steamAppID: game1.steamAppID,
storeID: game1.storeID,
normalPrice: game1.normalPrice,
salePrice: game1.salePrice,
};
console.log("temp sale", temp.salePrice);
return tempArr1.push(temp) && tempArr2.push(temp.steamAppID));
})
This is all of WishListData component:
import React, { useEffect, useState } from "react";
import Wishlist from "./Wishlist";
import "./Wishlist.css";
import "animate.css";
import axios from "axios";
const WishlistData = () => {
const [gamedet, setGameDet] = useState([]);
const [loaded, setLoaded] = useState(false);
const [stores, setStores] = useState([]);
const [price, setPrice] = useState([]);
const [wishlist, setWishlist] = useState([]);
useEffect(() => {
setWishlist(
localStorage.getItem("Wishlist")
? JSON.parse(localStorage.getItem("Wishlist"))
: []
);
}, []);
const RemoveFromWishlist = (id) => {
let newList = wishlist.filter((game) => game.gameID !== id);
setWishlist(newList)
localStorage.setItem("Wishlist", JSON.stringify(newList));
console.log("id", [wishlist.pop(id)]);
console.log("newlist", wishlist);
setGameDet([])
};
const DET_URL = `https://api.rawg.io/api/games`;
useEffect(() => {
let isCancelled = false;
const RAWGdet = () => {
wishlist &&
wishlist.map(async (game, index) => {
const res = await axios({
url: `https://cors-anywhere.herokuapp.com/${DET_URL}/${game.gameID}?key=${process.env.REACT_APP_RAWG_KEY}`,
headers: {
"X-Requested-With": "XMLHttpRequest",
},
method: "GET",
});
if (!isCancelled) {
setGameDet((gamedet) => gamedet.concat(res.data));
}
setLoaded(true);
});
};
RAWGdet();
return () => {
isCancelled = true;
};
}, [DET_URL, wishlist]);
useEffect(() => {
let isCancelled = false;
const CSPrice = () => {
wishlist &&
wishlist.map(async (game, index) => {
const res = await axios({
url: `https://cors-anywhere.herokuapp.com/${DET_URL}/${game.slug}/stores?key=${process.env.REACT_APP_RAWG_KEY}`,
headers: {
"X-Requested-With": "XMLHttpRequest",
},
method: "GET",
});
if (!isCancelled) {
setStores((stores) => stores.concat(res.data));
}
setLoaded(true);
});
};
CSPrice();
return () => {
isCancelled = true;
};
}, [DET_URL, wishlist]);
let stm = [];
stores
.map((steam) => {
return steam.results;
})
.filter((item) => {
return item.map((id) => {
return id.store_id === 1 ? stm.push(id.url) : <>{null}</>;
});
});
let idmain = [];
stm.map((steamid) => {
return steamid.split("/").map((item) => {
return idmain.push(item);
});
});
useEffect(() => {
let isCancelled = false
wishlist.length !==0 &&
wishlist.map((game, index) => {
return (
<div key={index}>
{axios
.get(
`https://www.cheapshark.com/api/1.0/deals?storeID=1&steamAppID=${game.steamID}`
)
.then((res) => {
if (!isCancelled){
setPrice((price) => price.concat(res.data));
setLoaded(true)
}
})
.catch((err) => {
console.log("ERR", err);
})}
</div>
);
});
return () => {
isCancelled = true
}
}, [wishlist]);
let tempArr1 = [];
let tempArr2 = [];
if (loaded ) {
return (
<div className="animate__animated animate__slideInDown">
<div className="wishlist_header">
<h3>Your Wishlist</h3>
</div>
{wishlist.length !== 0 ? (
price.map((game1) => {
let temp = {
steamAppID: game1.steamAppID,
storeID: game1.storeID,
normalPrice: game1.normalPrice,
salePrice: game1.salePrice,
};
console.log("temp sale", temp.salePrice);
return tempArr1.push(temp) && tempArr2.push(temp.steamAppID));
}) &&
gamedet.map((game, index) => {
// console.log("mad2", game.name);
return (
<div id="wishlist_ctn" key={index}>
<Wishlist
// key={index}
title={game.name}
steamRatingCount={game.id}
// steamRatingPercent={game[0].steamRatingPercent}
// savings={game[0].savings}
// normalPrice={}
// salePrice={salePrice}
steamAppID={tempArr2}
data={tempArr1}
image={game.background_image}
rem={() => RemoveFromWishlist(game.id)}
/>
</div>
);
})
) : (
<div className="wishlist_header">
<h3>Add Games!!</h3>
</div>
)}
</div>
);
} else {
return (
<div className="hmm">
<div className="wishlist_header">
<h3>Your Wishlist</h3>
</div>
<div className="wishlist_header">
<h3>Loading Games</h3>
</div>
);
</div>
);
}
};
export default WishlistData;
I don't understand why you need two extra arrays since you are mapping price to populate
tempArr1, which contain a copy of its items, and tempArr2, which contains a copy of its steamAppIDs.
I think you could just pass the price array as data, and a mapped version as steamAppID:
{wishlist.length !== 0 ?
(gamedet.map((game, index) => {
<Wishlist
// key={index}
title={game.name}
steamRatingCount={game.id}
// steamRatingPercent={game[0].steamRatingPercent}
// savings={game[0].savings}
// normalPrice={}
// salePrice={salePrice}
steamAppID={price.map(item => item.steamAppID)}
data={price}
image={game.background_image}
rem={() => RemoveFromWishlist(game.id)}
/>;
</div>
);
})
) : (
<div className="wishlist_header">
<h3>Add Games!!</h3>
</div>
)}
I am building a clone of the Google Keep app with react js. I added all the basic functionality (expand the create area, add a note, delete it) but I can't seem to manage the edit part. Currently I am able to edit the inputs and store the values in the state, but how can I replace the initial input values for the new values that I type on the input?
This is Note component
export default function Note(props) {
const [editNote, setEditNote] = useState(false);
const [currentNote, setCurrentNote] = useState({
id: props.id,
editTitle: props.title,
editContent: props.content,
});
const handleDelete = () => {
props.deleteNote(props.id);
};
const handleEdit = () => {
setEditNote(true);
setCurrentNote((prevValue) => ({ ...prevValue }));
};
const handleInputEdit = (event) => {
const { name, value } = event.target;
setCurrentNote((prevValue) => ({
...prevValue,
[name]: value,
}));
};
const updateNote = () => {
setCurrentNote((prevValue, id) => {
if (currentNote.id === id) {
props.title = currentNote.editTitle;
props.content = currentNote.editContent;
} else {
return { ...prevValue };
}
});
setEditNote(false);
};
return (
<div>
{editNote ? (
<div className='note'>
<input
type='text'
name='edittitle'
defaultValue={currentNote.editTitle}
onChange={handleInputEdit}
className='edit-input'
/>
<textarea
name='editcontent'
defaultValue={currentNote.editContent}
row='1'
onChange={handleInputEdit}
className='edit-input'
/>
<button onClick={() => setEditNote(false)}>Cancel</button>
<button onClick={updateNote}>Save</button>
</div>
) : (
<div className='note' onDoubleClick={handleEdit}>
<h1>{props.title}</h1>
<p>{props.content}</p>
<button onClick={handleDelete}>DELETE</button>
</div>
)}
</div>
);
}
And this is the Container component where I am renderind the CreateArea and mapping the notes I create. I tried to map the notes again with the new values but it wasn't working.
export default function Container() {
const [notes, setNotes] = useState([]);
const addNote = (newNote) => {
setNotes((prevNotes) => {
return [...prevNotes, newNote];
});
};
const deleteNote = (id) => {
setNotes((prevNotes) => {
return prevNotes.filter((note, index) => {
return index !== id;
});
});
};
// const handleUpdateNote = (id, updatedNote) => {
// const updatedItem = notes.map((note, index) => {
// return index === id ? updatedNote : note;
// });
// setNotes(updatedItem);
// };
return (
<div>
<CreateArea addNote={addNote} />
{notes.map((note, index) => {
return (
<Note
key={index}
id={index}
title={note.title}
content={note.content}
deleteNote={deleteNote}
//handleUpdateNote={handleUpdateNote}
/>
);
})}
</div>
);
}
There are a couple of mistakes in your code.
The state properties are in the camel case
const [currentNote, setCurrentNote] = useState({
...
editTitle: props.title,
editContent: props.content,
});
But the names of the input are in lowercase.
<input
name='edittitle'
...
/>
<textarea
name='editcontent'
...
/>
Thus in handleInputEdit you don't update the state but add new properties: edittitle and editcontent. Change the names to the camel case.
In React you cant assign to the component prop values, they are read-only.
const updateNote = () => {
...
props.title = currentNote.editTitle;
props.content = currentNote.editContent;
You need to use the handleUpdateNote function passed by the parent component instead. You have it commented for some reason.
<Note
...
//handleUpdateNote={handleUpdateNote}
/>
Check the code below. I think it does what you need.
function Note({ id, title, content, handleUpdateNote, deleteNote }) {
const [editNote, setEditNote] = React.useState(false);
const [currentNote, setCurrentNote] = React.useState({
id,
editTitle: title,
editContent: content,
});
const handleDelete = () => {
deleteNote(id);
};
const handleEdit = () => {
setEditNote(true);
setCurrentNote((prevValue) => ({ ...prevValue }));
};
const handleInputEdit = (event) => {
const { name, value } = event.target;
setCurrentNote((prevValue) => ({
...prevValue,
[name]: value,
}));
};
const updateNote = () => {
handleUpdateNote({
id: currentNote.id,
title: currentNote.editTitle,
content: currentNote.editContent
});
setEditNote(false);
};
return (
<div>
{editNote ? (
<div className='note'>
<input
type='text'
name='editTitle'
defaultValue={currentNote.editTitle}
onChange={handleInputEdit}
className='edit-input'
/>
<textarea
name='editContent'
defaultValue={currentNote.editContent}
row='1'
onChange={handleInputEdit}
className='edit-input'
/>
<button onClick={() => setEditNote(false)}>Cancel</button>
<button onClick={updateNote}>Save</button>
</div>
) : (
<div className='note' onDoubleClick={handleEdit}>
<h1>{title}</h1>
<p>{content}</p>
<button onClick={handleDelete}>DELETE</button>
</div>
)}
</div>
);
}
function CreateArea() {
return null;
}
function Container() {
const [notes, setNotes] = React.useState([
{ title: 'Words', content: 'hello, bye' },
{ title: 'Food', content: 'milk, cheese' }
]);
const addNote = (newNote) => {
setNotes((prevNotes) => {
return [...prevNotes, newNote];
});
};
const deleteNote = (id) => {
setNotes((prevNotes) => {
return prevNotes.filter((note, index) => {
return index !== id;
});
});
};
const handleUpdateNote = ({ id, title, content }) => {
const _notes = [];
for (let i = 0; i < notes.length; i++) {
if (i === id) {
_notes.push({ id, title, content });
} else {
_notes.push(notes[i]);
}
}
setNotes(_notes);
};
return (
<div>
<CreateArea addNote={addNote} />
{notes.map((note, index) => {
return (
<Note
key={index}
id={index}
title={note.title}
content={note.content}
deleteNote={deleteNote}
handleUpdateNote={handleUpdateNote}
/>
);
})}
</div>
);
}
function App() {
return (
<div>
<Container />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://unpkg.com/react#17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>
Also, you can store the notes in an object or hash map instead of an array. For example
const [notes, setNotes] = React.useState({
'unique_id': { title: 'Words', content: 'hello, bye' }
});
Then in handleUpdateNote you have
setNotes((prev) => ({ ...prev, unique_id: { title, content } }))
I'm reading the answer of this question: Problem with conditional statements in React Hook how can I Rewrite this code with multiple use state?
in this answer gather all the state objects to a single state.is there any way to rewrite this code with multiple and separate state?
const initialState = {
movies: [],
loading: true,
searchKeyword: "",
filterOverSeven: false,
filterByWatch: "ALL" // Can be "ALL" | "WATCHED" | "NOT_WATCHED"
};
const App = () => {
const [state, setState] = useState(initialState);
useEffect(() => {
fetch("https://my-json-server.typicode.com/bemaxima/fake-api/movies")
.then((response) => response.json())
.then((response) => {
setState((s) => ({
...s,
movies: response.map((item) => ({
id: item.id,
name: item.name,
rate: item.rate,
watched: false
})),
loading: false
}));
});
}, []);
function handleWatchedBtn(id) {
setState((s) => ({
...s,
movies: s.movies.map((movie) => {
if (movie.id === id) {
return { ...movie, watched: !movie.watched };
}
return movie;
})
}));
}
function handleKeywordChange(e) {
setState((s) => ({ ...s, searchKeyword: e.target.value }));
}
function handleOverSevenChange(e) {
setState((s) => ({ ...s, filterOverSeven: !s.filterOverSeven }));
}
function handleWatchedChange(filter) {
setState((s) => ({ ...s, filterByWatch: filter }));
}
function filterItems() {
return state.movies
.filter((item) =>
item.name.toLowerCase().includes(state.searchKeyword.toLowerCase())
)
.filter((item) => (state.filterOverSeven ? item.rate > 7 : true))
.filter((item) =>
state.filterByWatch === "ALL"
? true
: item.watched === (state.filterByWatch === "WATCHED")
);
}
if (state.loading) {
return "Please wait...";
}
return (
<div>
<div>
<div>
Keyword
<input
type="text"
value={state.searchKeyword}
onChange={handleKeywordChange}
/>
</div>
<div>
<button onClick={() => handleWatchedChange("ALL")}>all</button>
<button onClick={() => handleWatchedChange("WATCHED")}>watch</button>
<button onClick={() => handleWatchedChange("NOT_WATCHED")}>
not watch
</button>
</div>
<div>
Only over 7.0
<input
type="checkbox"
checked={state.filterOverSeven}
onChange={handleOverSevenChange}
/>
</div>
<div>
<ul>
{filterItems().map((movie) => (
<li data-id={movie.id}>
{`${movie.id} : ${movie.name} ${movie.rate}`}{" "}
<button onClick={() => handleWatchedBtn(movie.id)}>
{movie.watched ? "Watched" : " Not watched"}
</button>
</li>
))}
</ul>
</div>
</div>
</div>
);
};
export default App;
this is my code and how I try to change that.
import React, { useEffect, useState } from "react";
import { getAllMovies } from "./components/Transportlayer";
const App = () => {
const [movies, setMovies] = useState([]);
const [Keyword, setKeyword] = useState("");
const [OverSeven, setOverSeven] = useState(false);
const [Loading, setLoading] = useState(true);
const [filterByWatch, setfilterByWatch] = useState("ALL");
useEffect(() => {
getAllMovies().then((response) => {
setMovies(
response.map((item) => ({
id: item.id,
name: item.name,
rate: item.rate,
watched: false,
}))
);
setLoading(false);
});
}, []);
function handleWatchedBtn(id) {
setMovies({
movies: movies.map((movie) => {
if (movie.id === id) {
return { movie, watched: !movie.watched };
}
return movie;
})
});
}
function handleKeywordChange(e) {
setKeyword(e.target.value);
}
function handleOverSevenChange(e) {
setOverSeven(e.target.checked);
}
function handleWatchedChange(filter) {
setfilterByWatch({ filterByWatch: filter });
}
function filterItems() {
return movies
.filter((item) =>
item.name.toLowerCase().includes(Keyword.toLowerCase())
)
.filter((item) => (OverSeven ? item.rate > 7 : true))
.filter((item) =>
filterByWatch === "ALL"
? true
: item.watched === (filterByWatch === "WATCHED")
);
}
if (Loading) {
return "Please wait...";
}
return (
<div>
<div>
<div>
Keyword
<input
type="text"
value={Keyword}
onChange={handleKeywordChange}
/>
</div>
<div>
<button onClick={() => handleWatchedChange("ALL")}>all</button>
<button onClick={() => handleWatchedChange("WATCHED")}>done</button>
<button onClick={() => handleWatchedChange("NOT_WATCHED")}>
undone
</button>
</div>
<div>
Only over 7.0
<input
type="checkbox"
checked={OverSeven}
onChange={handleOverSevenChange}
/>
</div>
<div>
<ul>
{filterItems().map((movie) => (
<li data-id={movie.id}>
{`${movie.name} ${movie.rate}`}
<button onClick={() => handleWatchedBtn(movie.id)}>
{movie.watched ? "done" : " undone"}
</button>
</li>
))}
</ul>
</div>
</div>
</div>
);
};
export default App;
In the second code (separated states), you define movies as an array, but you are changing it to an object:
function handleWatchedBtn(id) {
setMovies({
movies: movies.map((movie) => {
if (movie.id === id) {
return { movie, watched: !movie.watched };
}
return movie;
})
});
}
I think this would work:
function handleWatchedBtn(id) {
setMovies(
movies.map((movie) => {
if (movie.id === id) {
return { movie, watched: !movie.watched };
}
return movie;
})
);
}
Same with the filterByWatch state, its defined to be a string but in here you are setting to it to a object:
function handleWatchedChange(filter) {
setfilterByWatch({ filterByWatch: filter });
}
correct:
function handleWatchedChange(value) {
setfilterByWatch(value);
}
This functions is wrong
function handleWatchedBtn(id) {
setMovies({
movies: movies.map((movie) => {
if (movie.id === id) {
return { movie, watched: !movie.watched };
}
return movie;
})
});
}
you are returning an object with key movies inside movies state, but movies should be an array of movies. Try something like this:
function handleWatchedBtn(id) {
setMovies(() => movies.map((movie) => {
if (movie.id === id) {
return { movie, watched: !movie.watched };
}
return movie;
)
});
}
This is the answer given by #Luca Pizzini and work for this problem.
change button text and state by click and show list in react hooks
You can use useReducer hook to better refactor this code.