function not working tho props have been passed - reactjs

Both of my completeTodo and removeTodo function is not working tho i have passed the props to the Todo function and in the Todo component rendered in the App.js. Anyone knows what is wrong with my two function or anything, please help me! Thank you so much!
import React, { useState } from "react";
function Todo({ todo, index, completeTodo, removeTodo }) {
console.log("hiiii");
return (
<div>
<div className={todo.isCompleted ? "line-through" : ""}>
<p>{todo.text}</p>
</div>
<button onCllick={() => completeTodo(index)}>completed</button>
<button onCllick={() => removeTodo(index)}>X</button>
</div>
);
}
function TodoForm({ addTodo }) {
const [value, setValue] = useState("");
handleSubmit = (e) => {
e.preventDefault();
if (!value) return;
addTodo(value);
setValue("");
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="add new todo"
value={value}
onChange={(e) => {
setValue(e.target.value);
}}
/>
</form>
</div>
);
}
function App() {
const [todos, setTodos] = useState([
{
text: "eat lunch",
isCompleted: false
},
{
text: "do homework",
isCompleted: false
},
{
text: "go to school",
isCompleted: false
}
]);
const addTodo = (text) => {
const newTodos = [...todos, { text }];
setTodos(newTodos);
};
const completeTodo = (index) => {
console.log("completed");
const newTodos = [...todos];
newTodos[index].isCompleted = true;
setTodos(newTodos);
};
const removeTodo = (index) => {
console.log("deleted");
const newTodos = [...todos];
newTodos.splice(index, 1);
setTodos(newTodos);
};
return (
<div>
<div>
{todos.map((todo, index) => {
return (
<Todo
key={index}
index={index}
todo={todo}
completeTodo={completeTodo}
removeTodo={removeTodo}
/>
);
})}
</div>
<div>
<TodoForm addTodo={addTodo} />
</div>
</div>
);
}
export default App;
Sanbox link:https://codesandbox.io/s/serverless-bash-ef4hk?file=/src/App.js:0-1979

You are mutating the state.
const completeTodo = (index) => {
console.log("completed");
const newTodos = [...todos];
newTodos[index].isCompleted = true; // <-- mutates todo object
setTodos(newTodos);
};
const removeTodo = (index) => {
console.log("deleted");
const newTodos = [...todos];
newTodos.splice(index, 1); // <-- mutates todo object
setTodos(newTodos);
};
Along with shallow copying the array you need to also shallow copy any nested state that you are updating. The todo objects should also be new object references.
const completeTodo = (index) => {
console.log("completed");
setTodos((todos) =>
// array.map to return new array
todos.map((todo, i) =>
i === index
? {
...todo, // shallow copy old todo object
isCompleted: true
}
: todo
)
);
};
const removeTodo = (index) => {
console.log("deleted");
// array.filter to return new array
setTodos((todos) => todos.filter((_, i) => i !== index));
};
Fix the buttons in the Todo, they aren't using the correct event handler.
<button onCllick={() => completeTodo(index)}>completed</button>
<button onCllick={() => removeTodo(index)}>X</button>
should be
<button onClick={() => completeTodo(index)}>completed</button>
<button onClick={() => removeTodo(index)}>X</button>

Related

How i push an object on my array with hook on react js?

I wanna push the object "task" on my array todoList.
For the moment only the value task is recorded on my todoList. It's weird because on my const Addtask, the value task is my object.
When i click on my button i also want him to change is value "etat".If i want to sort it, do i need to use .map.sort ?
Did i forget something ?
function Task() {
const [task, setTask] = useState({ task: "", etat: "en cours" });
const [todoList, setTodoList] = useState([]);
const switchEnCours = () => {
setTask({etat:"terminé"});
};
const deleteTask = () => {
setTask({etat:"supprimée"});
};
const handleInput = (e) => {
setTask(e.target.value);
};
const AddTask = (e) => {
setTodoList([...todoList, task]);
console.log(todoList);
};
useEffect(() => console.log(todoList), [todoList]);
return (
<div>
<input onChange={handleInput}></input>
<button onClick={AddTask}>Valider</button>
<div className="DivColonne">
<div className="Colonne">
<h1>Tâche à faire</h1>
{todoList.map((insertTask) => {
return (
<div>
<p>{insertTask.task}</p>
<button onClick={switchEnCours}>{insertTask.etat}</button>
</div>
);
})}
</div>
<div className="Colonne">
<h1>Tâche en cours</h1>
{encours === "terminé" ? (
<div>
{todoList.map((insert) => {
return (
<div>
<p>{insert.task}</p>
<button onClick={deleteTask}>{insert.etat}</button>
</div>
);
})}
</div>
) : (
<div></div>
)}
</div>
<div>
<h1>Tâches terminées</h1>
{encours === "supprimée" ? (
<div>
<p>{todoList}</p>
</div>
) : (
<div></div>
)}
</div>
</div>
</div>
);
}
when you want to update an object state you need to pass in the old properties of that object that are not going to change, so you dont lose those properties. This is done using a spread operator
for example your
const switchEnCours = () => {
setTask({ etat: "terminé" });
};
should be
const switchEnCours = () => {
setTask({...task, etat: "terminé"});
};
you also have to do the same with the handleInput function
const handleInput = (e) => {
setTask({...task, task: e.target.value});
};

Local storage is not keeping the data after page reload

I'm making in react a list of episodes that user would like to watch later (similar to todo app) , but after reloading the page data is not keeping in local storage.
I'm new to react, so, please help me to understand the issue.
This is my code
import React, { useState, useEffect } from "react";
import { RiCloseCircleLine } from "react-icons/ri";
import { TiEdit } from "react-icons/ti";
import { MyWatchListForm } from "./MyWatchListForm";
export const MyWatchListItem = ({
watchLists,
completeWatchList,
removeWatchList,
updateWatchList,
}) => {
const [edit, setEdit] = useState({
id: null,
value: "",
});
const submitUpdate = (value) => {
updateWatchList(edit.id, value);
setEdit({
id: null,
value: "",
});
};
useEffect(() => {
const data = localStorage.getItem("my-watchList");
const savedData = JSON.parse(data);
setEdit(savedData);
}, []);
useEffect(() => {
localStorage.setItem("my-watchList", JSON.stringify(watchLists));
});
if (edit.id) {
return <MyWatchListForm edit={edit} onSubmit={submitUpdate} />;
}
return watchLists.map((watchList, index) => (
<div className={watchList.isComplete ? "checked" : ""} key={index}>
<div key={watchList.id} onClick={() => completeWatchList(watchList.id)}>
{watchList.text}
</div>
<div>
<RiCloseCircleLine onClick={() => removeWatchList(watchList.id)} />
<TiEdit
onClick={() => setEdit({ id: watchList.id, value: watchList.text })}
/>
</div>
</div>
));
};
Form that is used to get the data from:
export const MyWatchListForm = (props) => {
const [input, setInput] = useState(props.edit ? props.edit.value : "");
const inputRef = useRef();
useEffect(() => {
inputRef.current.focus();
});
const handleSubmit = (e) => {
e.preventDefault();
props.onSubmit({
id: Math.floor(Math.random() * 10000),
text: input,
});
setInput("");
};
const handleChange = (e) => {
setInput(e.target.value);
};
return (
<form
className="w-full max-w-sm flex items-center border-b border-teal-500 py-2"
onSubmit={handleSubmit}
>
{props.edit ? (
<>
<input
className="appearance-none bg-transparent border-none w-full text-gray-700 mr-3 py-1 px-2 leading-tight focus:outline-none"
type="text"
value={input}
placeholder="Update the episode"
onChange={handleChange}
ref={inputRef}
></input>
<button>Update</button>
</>
) : (
<>
<input
className="appearance-none bg-transparent border-none w-full text-gray-700 mr-3 py-1 px-2 leading-tight focus:outline-none"
type="text"
value={input}
placeholder="Add the episode"
onChange={handleChange}
ref={inputRef}
></input>
<button>Add</button>
</>
)}
</form>
);
};
And the WatchList.js file
import React, { useState } from "react";
import { MyWatchListForm } from "./MyWatchListForm";
import { MyWatchListItem } from "./MyWatchListItem";
export const MyWatchList = () => {
const [watchLists, setWatchLists] = useState([]);
const addWatchList = (watchList) => {
if (!watchList.text || /^\s*$/.test(watchList.text)) {
return;
}
const newWatchList = [watchList, ...watchLists];
setWatchLists(newWatchList);
console.log(...watchLists);
};
const updateWatchList = (watchListId, newValue) => {
if (!newValue.text || /^\s*$/.test(newValue.text)) {
return;
}
setWatchLists((prev) =>
prev.map((item) => (item.id === watchListId ? newValue : item))
);
};
const removeWatchList = (id) => {
const removeArr = [...watchLists].filter(
(watchList) => watchList.id !== id
);
setWatchLists(removeArr);
};
const completeWatchList = (id) => {
const updatedWatchList = watchLists.map((watchList) => {
if (watchList.id === id) {
watchList.isComplete = !watchList.isComplete;
}
return watchList;
});
setWatchLists(updatedWatchList);
};
return (
<div>
<h1>Watch later</h1>
<MyWatchListForm onSubmit={addWatchList} />
<MyWatchListItem
watchLists={watchLists}
completeWatchList={completeWatchList}
removeWatchList={removeWatchList}
updateWatchList={updateWatchList}
/>
</div>
);
};
I fixed the issue by changing the initial state in MyWatchList as below:
export const MyWatchList = () => {
const [watchLists, setWatchLists] = useState(() => {
const data = localStorage.getItem("my-watchList");
return data ? JSON.parse(data) : [];
});
useEffect(() => {
localStorage.setItem("my-watchList", JSON.stringify(watchLists));
}, [watchLists]);
I think the issue with your code is this snippet:
useEffect(() => {
localStorage.setItem("my-watchList", JSON.stringify(watchLists));
});
Every time the state get's updated, this useEffect runs and it replaces the content of localStorage to the values which you are getting in watchlists since you are not providing a dependancy array to the useEffect.
Using the useEffect hook without a dependency array is never a good idea. It basically runs on every render. Actually, goal of the useEffect is to solve this problem. Remove your useEffect and try like this;
const submitUpdate = (value) => {
updateWatchList(edit.id, value);
setEdit({
id: null,
value: "",
});
localStorage.setItem("my-watchList", JSON.stringify(watchLists));
};

map function to work with event.target.name, have multiple values tagged to index of map

The below functionality is only capable of running only one component (i.e. "ComponentTwo"), I want to modify it to have more component, but issue is as i am using map function to loop through component to map "value", same value will be passed to all the component.
In the code there is two function for handling the change currently i am using the "handleInputChange" which take value as input but i want it to work with name so that i can have name to distinguish between components, below is one commented function which i am trying to implement, but is not working.
p.s. if you need any clarifications let me know in comment section.
link to code:https://codesandbox.io/s/happy-hugle-mfstd?file=/src/App.js
import React, { Component, useState } from "react";
export default function App() {
const [inputValues, setInputValues] = useState(["Test"]);
const addNewInputvalue = () => {
setInputValues((prev) => {
return [...prev, ""];
});
};
const removeInput = (index) => {
setInputValues((prev) => {
const copy = [...prev];
copy.splice(index, 1);
return copy;
});
};
// const handleChange = (event) => {
// event.persist()
// setData(prev => ({ ...prev, [event.target.name]: event.target.value }))
// }
const handleInputChange = (index, value) => {
setInputValues((prev) => {
const copy = [...prev];
copy[index] = value;
return copy;
});
};
const consoleAllValues = () => {
console.log(inputValues);
};
return (
<div className="App">
<button onClick={addNewInputvalue}>New Input</button>
{inputValues.map((val, i) => {
return (<div>
<ComponentTwo
key={i}
index={i}
value={val}
onChange={handleInputChange}
removeInput={() => removeInput(i)}
/>
<ComponentThree />
<ComponenFour />
</div>
>
);
})}
<button onClick={consoleAllValues}>console log all values</button>
</div>
);
}
const ComponentTwo = (props) => {
return (
<div>
<p>Input: {props.index}</p>
<input
name={"right_value"}
onChange={(e) => props.onChange(props.index, e.target.value)}
type="text"
value={props.value}
/>
<button onClick={props.removeInput}>Remove Input</button>
</div>
);
};
Instead of using an array to store your values, you should create a key value object. See the changes I've made regarding your state, the way you iterate through the object inside your return statement and all the functions that manipulate your state.
import React, { Component, useState } from "react";
export default function App() {
const [inputValues, setInputValues] = useState({
'componentTwo': 'val1',
'componentThree': 'val2',
'componentFour': 'val3',
});
const addNewInputvalue = (name, value) => {
setInputValues((prev) => {
return {
...prev,
[name]: value,
}
});
};
const removeInput = (name) => {
setInputValues((prev) => {
const copy = {...prev};
delete copy[name];
return copy;
});
};
const handleInputChange = (name, value) => {
setInputValues((prev) => {
return {
...prev,
[name]: value,
};
});
};
const consoleAllValues = () => {
console.log(inputValues);
};
return (
<div className="App">
<button onClick={addNewInputvalue}>New Input</button>
{Object.keys(inputValues).map((name, i) => {
return (<div>
<ComponentTwo
key={name}
index={i}
value={inputValues[name]}
onChange={(value) => handleInputChange(name, value)}
removeInput={() => removeInput(name)}
/>
<ComponentThree />
<ComponenFour />
</div>
>
);
})}
<button onClick={consoleAllValues}>console log all values</button>
</div>
);
}
const ComponentTwo = (props) => {
return (
<div>
<p>Input: {props.index}</p>
<input
name={"right_value"}
onChange={(e) => props.onChange(e.target.value)}
type="text"
value={props.value}
/>
<button onClick={props.removeInput}>Remove Input</button>
</div>
);
};

How can I edit a todo in my react app using hooks?

I'm trying to figure out how to edit a todo item in my react app using hooks, but I can't seem to figure out how to write the code.
Most of the solutions I've seen online are using class components and it's not written with the same logic as my app.
Here is my current code
function TodoList() {
const [todos, setTodos] = useState([]);
const addTodo = todo => {
if (!todo.text || /^\s*$/.test(todo.text)) {
return;
}
const newTodos = [todo, ...todos];
setTodos(newTodos);
console.log(newTodos);
};
const removeTodo = id => {
const removedArr = [...todos].filter(todoId => todoId.id !== id);
setTodos(removedArr);
};
const completeTodo = id => {
let updatedTodos = todos.map(todo => {
if (todo.id === id) {
todo.isComplete = !todo.isComplete;
}
return todo;
});
setTodos(updatedTodos);
};
const editTodo = e => {
setTodos(e.target.value);
};
return (
<>
<TodoForm onSubmit={addTodo} />
{todos.map(todo => (
<div>
<div
key={todo.id}
className={todo.isComplete ? 'complete' : ''}
key={todo.id}
onClick={() => completeTodo(todo.id)}
>
{todo.text}
</div>
<FaWindowClose onClick={() => removeTodo(todo.id)} />
</div>
))}
</>
);
}
Here is the code from the other component
function TodoForm(props) {
const [input, setInput] = useState('');
const handleChange = e => {
setInput(e.target.value);
};
const handleSubmit = e => {
e.preventDefault();
props.onSubmit({
id: Math.floor(Math.random() * 10000),
text: input,
complete: false
});
setInput('');
};
return (
<form onSubmit={handleSubmit}>
<input
placeholder='todo...'
value={input}
onChange={handleChange}
name='text'
/>
<button onClick={handleSubmit}>add todo</button>
</form>
);
}
So right now everything works where I can add todos and delete todos + cross out todos. Only thing missing is being able to edit them.
I saw some suggestions about updating the text value with an input form, but I'm not too sure how I'd implement that in my editTodo function.
Similar to your removeTodo handler, you want to pass the todo.id to completeTodo.
<div className={todo.isComplete ? "complete" : ""} key={todo.id} onClick={() => completeTodo(todo.id)}>
Then you would update a bool value in the todo object.
const completeTodo = (id) => {
let updatedTodos = todos.map(todo => {
if(todo.id === id){
todo.isComplete = true
}
return todo
})
setTodos(updatedTodos)
};
Edit: add styling strikethrough
You'll then conditionally add a css style based on isComplete boolean
CSS
.complete {
text-decoration: line-through;
}
To be able to click on the Remove button, place it outside the todo div in your map function.
{todos.map((todo, isComplete) => (
<>
<div
key={todo.id}
onClick={completeTodo}
className={isComplete ? 'complete' : ''}
>
{todo.text}
</div>
<FaWindowClose onClick={() => removeTodo(todo.id)} />
</>
))}
As discussion with you in another question here it is:
TodoList.js
import React, { useState } from "react";
import TodoForm from "./TodoForm";
import Todo from "./Todo";
function TodoList({ onClick }) {
const [todos, setTodos] = useState([]);
//Track is edit clicked or not
const [editId, setEdit] = useState(false);
//Save input value in input box
const [inputValue, setInputValue] = useState("");
const handleEditChange = (id, text) => {
setEdit(id);
setInputValue(text);
};
const addTodo = (todo) => {
if (!todo.text || /^\s*$/.test(todo.text)) {
return;
}
const newTodos = [todo, ...todos];
setTodos(newTodos);
console.log(newTodos);
};
const removeTodo = (id) => {
const removedArr = [...todos].filter((todoId) => todoId.id !== id);
setTodos(removedArr);
};
const completeTodo = (id) => {
let updatedTodos = todos.map((todo) => {
if (todo.id === id) {
todo.isComplete = !todo.isComplete;
}
return todo;
});
setTodos(updatedTodos);
};
const editTodo = (id, text) => {
let editTodos = todos.map((todo) => {
if (todo.id === id) {
todo.text = text;
}
return todo;
});
setTodos(editTodos);
setEdit(false);
};
return (
<>
<TodoForm onSubmit={addTodo} />
{/* I want to move this code below into a new component called Todo.js */}
<Todo
todos={todos}
completeTodo={completeTodo}
removeTodo={removeTodo}
editTodo={editTodo}
handleEditChange={handleEditChange}
editId={editId}
inputValue={inputValue}
setInputValue={setInputValue}
/>
</>
);
}
export default TodoList;
Todo.js
// I want to move this code into this component
import React, { useState } from "react";
import { FaWindowClose, FaRegEdit } from "react-icons/fa";
const Todo = ({
todos,
completeTodo,
removeTodo,
editTodo,
editId,
handleEditChange,
inputValue,
setInputValue
}) => {
return todos.map((todo) => (
<div className="todo-row">
{editId === todo.id ? (
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
) : (
<div
key={todo.id}
className={todo.isComplete ? "complete" : ""}
onClick={() => completeTodo(todo.id)}
>
{todo.text}
</div>
)}
{editId === todo.id ? (
<button onClick={() => editTodo(todo.id, inputValue)}>Edit todo</button>
) : (
<>
<FaWindowClose onClick={() => removeTodo(todo.id)} />
<FaRegEdit onClick={() => handleEditChange(todo.id, todo.text)} />
</>
)}
</div>
));
};
export default Todo;
Make sure you read and understand code first. Logic is pretty simple what you do in completeTodo. You just need to update text part. Tricky part is to open in input. So logic is like track if user click on id set that id. And check if id is there open input with that id value other wise normal one.
Here is demo of this POC: https://codesandbox.io/s/nostalgic-silence-idm21?file=/src/Todo.js:0-1059

Conflicts between useEffect in react

I have to create component which fetch data with pagination and filters.
Filters are passed by props and if they changed, component should reset data and fetch it from page 0.
I have this:
const PaginationComponent = ({minPrice, maxPrice}) => {
const[page, setPage] = useState(null);
const[items, setItems] = useState([]);
const fetchMore = useCallback(() => {
setPage(prevState => prevState + 1);
}, []);
useEffect(() => {
if (page === null) {
setPage(0);
setItems([]);
} else {
get(page, minPrice, maxPrice)
.then(response => setItems(response));
}
}, [page, minPrice, maxPrice]);
useEffect(() => {
setPage(null);
},[minPrice, maxPrice]);
};
.. and there is a problem, because first useEffect depends on props, because I use them to filtering data and in second one I want to reset component. And as a result after changing props both useEffects run.
I don't have more ideas how to do it correctly.
In general the key here is to move page state up to the parent component and change the page to 0 whenever you change your filters. You can do it either with useState, or with useReducer.
The reason why it works with useState (i.e. there's only one rerender) is because React batches state changes in event handlers, if it didn't, you'd still end up with two API calls. CodeSandbox
const PaginationComponent = ({ page, minPrice, maxPrice, setPage }) => {
const [items, setItems] = useState([]);
useEffect(() => {
get(page, minPrice, maxPrice).then(response => setItems(response));
}, [page, minPrice, maxPrice]);
return (
<div>
{items.map(item => (
<div key={item.id}>
{item.id}, {item.name}, ${item.price}
</div>
))}
<div>Page: {page}</div>
<button onClick={() => setPage(v => v - 1)}>back</button>
<button onClick={() => setPage(v => v + 1)}>next</button>
</div>
);
};
const App = () => {
const [page, setPage] = useState(0);
const [minPrice, setMinPrice] = useState(25);
const [maxPrice, setMaxPrice] = useState(50);
return (
<div>
<div>
<label>Min price:</label>
<input
value={minPrice}
onChange={event => {
const { value } = event.target;
setMinPrice(parseInt(value, 10));
setPage(0);
}}
/>
</div>
<div>
<label>Max price:</label>
<input
value={maxPrice}
onChange={event => {
const { value } = event.target;
setMaxPrice(parseInt(value, 10));
setPage(0);
}}
/>
</div>
<PaginationComponent minPrice={minPrice} maxPrice={maxPrice} page={page} setPage={setPage} />
</div>
);
};
export default App;
The other solution is to use useReducer, which is more transparent, but also, as usual with reducers, a bit heavy on the boilerplate. This example behaves a bit differently, because there is a "set filters" button that makes the change to the state that is passed to the pagination component, a bit more "real life" scenario IMO. CodeSandbox
const PaginationComponent = ({ tableConfig, setPage }) => {
const [items, setItems] = useState([]);
useEffect(() => {
const { page, minPrice, maxPrice } = tableConfig;
get(page, minPrice, maxPrice).then(response => setItems(response));
}, [tableConfig]);
return (
<div>
{items.map(item => (
<div key={item.id}>
{item.id}, {item.name}, ${item.price}
</div>
))}
<div>Page: {tableConfig.page}</div>
<button onClick={() => setPage(v => v - 1)}>back</button>
<button onClick={() => setPage(v => v + 1)}>next</button>
</div>
);
};
const tableStateReducer = (state, action) => {
if (action.type === "setPage") {
return { ...state, page: action.page };
}
if (action.type === "setFilters") {
return { page: 0, minPrice: action.minPrice, maxPrice: action.maxPrice };
}
return state;
};
const App = () => {
const [tableState, dispatch] = useReducer(tableStateReducer, {
page: 0,
minPrice: 25,
maxPrice: 50
});
const [minPrice, setMinPrice] = useState(25);
const [maxPrice, setMaxPrice] = useState(50);
const setPage = useCallback(
page => {
if (typeof page === "function") {
dispatch({ type: "setPage", page: page(tableState.page) });
} else {
dispatch({ type: "setPage", page });
}
},
[tableState]
);
return (
<div>
<div>
<label>Min price:</label>
<input
value={minPrice}
onChange={event => {
const { value } = event.target;
setMinPrice(parseInt(value, 10));
}}
/>
</div>
<div>
<label>Max price:</label>
<input
value={maxPrice}
onChange={event => {
const { value } = event.target;
setMaxPrice(parseInt(value, 10));
}}
/>
</div>
<button
onClick={() => {
dispatch({ type: "setFilters", minPrice, maxPrice });
}}
>
Set filters
</button>
<PaginationComponent tableConfig={tableState} setPage={setPage} />
</div>
);
};
export default App;
You can use following
const fetchData = () => {
get(page, minPrice, maxPrice)
.then(response => setItems(response));
}
// Whenever page updated fetch new data
useEffect(() => {
fetchData();
}, [page]);
// Whenever filter updated reseting page
useEffect(() => {
const prevPage = page;
setPage(0);
if(prevPage === 0 ) {
fetchData();
}
},[minPrice, maxPrice]);

Resources