React re-renders unnecessary components - reactjs

I'm new to React, so please explain why this is happens(I guess because I'm re-creating the array of objects, but I'm not sure and don't know how to fix this) and how to fix this this.
I think it's better to show the code, rather than talk about it, so:
App.js
import { useState } from "react";
import Input from "./Input";
let lastId = 2;
function App() {
const [inputsValues, setInputsValues] = useState([
{ id: 1, value: "" },
{ id: 2, value: "" },
]);
const handleChange = (e, id) => {
setInputsValues((prevState) => {
const newState = [...prevState];
const index = newState.findIndex((input) => input.id === id);
newState[index].value = e.target.value;
return newState;
});
};
const addNewInput = () => {
setInputsValues((prevState) => {
const newState = [...prevState];
newState.push({ id: ++lastId, value: "" });
return newState;
});
};
return (
<div className="App">
<div>
<button onClick={addNewInput}>Add new input...</button>
</div>
{inputsValues.map((input) => (
<div className="input-wrap">
<Input
key={input.id}
id={input.id}
value={input.value}
handleChange={handleChange}
/>
</div>
))}
</div>
);
}
export default App;
Input.js
import { useRef } from "react";
const Input = ({ id, value, handleChange }) => {
const renderAmount = useRef(0);
return (
<>
<div> Rendered: {renderAmount.current++}</div>
<input
id={id}
type="text"
value={value}
onChange={(e) => handleChange(e, id)}
/>
</>
);
};
export default Input;
Thanks

Seems like you just have to wrap your Input component in memo so that it only rerenders if its props change. So i imported the memo function in the top and called it on the right side of the assignment, so that it 'wraps' your component and makes it memoized.
import { useRef, memo } from "react";
const Input = memo(({ id, value, handleChange }) => {
const renderAmount = useRef(0);
return (
<>
<div> Rendered: {renderAmount.current++}</div>
<input
id={id}
type="text"
value={value}
onChange={(e) => handleChange(e, id)}
/>
</>
);
});
export default Input;

Related

Delete currently clicked item in react

Thanks for the help in advance. Currently, I am learning react. As a part of it I am coding a project with a basic listing and deleting the currently clicked listed item. I was able to list the entered item but was not able to delete it. I was able to fetch the id of the currently clicked item but doesn't have a picture of what to do next.Can anyone please help me to solve this.
My code:
App.js
import React, { useState, Fragment } from "react";
import UserAddForm from "./UserAddForm/UserAddForm";
import UserList from "./UserList/UserList";
const App = () => {
const [dataList, setDataList] = useState([]);
const FormDatas = (datas) => {
setDataList([datas, ...dataList]);
};
const deleteListItem = (clickedListId) => {
console.log(clickedListId);
};
return (
<Fragment>
<UserAddForm enteredFormVals={FormDatas} />
<UserList listDatas={dataList} deleteItem={deleteListItem} />
</Fragment>
);
};
export default App;
UserList.js
import React from "react";
import userListing from "./UserList.module.css";
const UserList = (props) => {
const deleteHandler = (e) => {
console.log(e.target.id);
props.deleteItem(e.target.id);
};
return (
<div className={`${userListing.users} ${userListing.whiteBg}`}>
<ul>
{props.listDatas.map((data) => (
<li key={data.id} id={data.id} onClick={deleteHandler}>
{data.name}
{data.age}
</li>
))}
</ul>
</div>
);
};
export default UserList;
UserAddForm.js
import React, { useState } from "react";
import UserForm from "./UserAddForm.module.css";
const UserAddForm = (props) => {
const [enteredName, setEnteredName] = useState("");
const [enteredAge, setEnteredAge] = useState("");
const nameHandler = (e) => {
setEnteredName(e.target.value);
};
const ageHandler = (e) => {
setEnteredAge(e.target.value);
};
const userFormHandler = (e) => {
e.preventDefault();
const EnteredFormDatas = {
name: enteredName,
age: enteredAge,
id: Math.random().toString(),
};
props.enteredFormVals(EnteredFormDatas);
};
return (
<div className={`${UserForm.input} ${UserForm.whiteBg}`}>
<form onSubmit={userFormHandler}>
<label htmlFor="username">Username</label>
<input
id="username"
type="text"
onChange={nameHandler}
value={enteredName}
/>
<label htmlFor="age">Age (Years)</label>
<input
id="age"
type="number"
onChange={ageHandler}
value={enteredAge}
/>
<button type="submit" className={UserForm.button}>
Add User
</button>
</form>
</div>
);
};
export default UserAddForm;
You need to filter out the item from the array by keeping ones with a different id and set it back as a new dataList.
const deleteListItem = (clickedListId) => {
setDataList(items => items.filter(({ id }) => id !== clickedListId))
};

Show use state with expired date in reactJs

I am not able to implement a expire function to my to do app. I have tried using an switch statement and then add a class to the element if its switched but it did not work. Ive also tried just changing the value of an useState and then adding the class from the state but the state didn't change correctly.
Here is my code:
app.js
import React, {useState, useEffect} from 'react';
import './App.css';
import Form from './components/Form.js';
import ToDoList from './components/ToDoList';
function App() {
//States
const [inputText, setInputText] = useState("");
const [inputTime, setInputTime] = useState("");
const [inputDate, setInputDate] = useState("");
const [todos, setTodos] = useState(JSON.parse(localStorage.getItem('todos')) || []);
const [status, setStatus] = useState("all");
const [filteredTodos, setFilteredTodos] = useState ([]);
//useEffect
useEffect(() => {
saveLocalTodos();
filterHandler();
timeStatusHandler();
}, [todos, status]);
//Funktioner
//Funktion för att visa olika uppgifter beroende på status
const filterHandler = () => {
switch(status){
case 'completed':
setFilteredTodos(todos.filter(todo =>todo.completed === true))
break;
case 'uncompleted':
setFilteredTodos(todos.filter(todo =>todo.completed === false))
break;
default:
setFilteredTodos(todos);
break;
}
};
//save list localy
const saveLocalTodos = () => {
localStorage.setItem('todos', JSON.stringify(todos));
};
return (
<div className="App">
<Form
inputText={inputText}
inputTime={inputTime}
inputDate={inputDate}
setInputText={setInputText}
setInputTime={setInputTime}
setInputDate={setInputDate}
timeStatus={timeStatus}
setTimeStatus={setTimeStatus}
todos={todos}
setTodos={setTodos}
setStatus={setStatus}
/>
<ToDoList
setTodos={setTodos}
todos={todos}
filteredTodos={filteredTodos}
/>
</div>
);
}
export default App;
Form.js
import React from 'react';
import nextId from "react-id-generator";
const Form = ({setInputText, inputText, setInputTime, inputTime, setInputDate, inputDate, todos, setTodos, setStatus, timeStatus, setTimeStatus}) =>{
const inputTextHandler = (e) => {
setInputText(e.target.value);
};
const inputTimeHandler = (e) => {
setInputTime(e.target.value);
console.log(e.target.value);
var today = new Date();
var curTime = today.getHours()+':'+today.getMinutes;
if(curTime >= e.target.value){
setTimeStatus("passed");
}
};
const inputDateHandler = (e) => {
setInputDate(e.target.value);
console.log(inputDate);
};
const submitTodoHandler = (e) => {
e.preventDefault();
var todoId = nextId();
setTodos([
...todos, {text: inputText,time: inputTime, date: inputDate, passed: false ,completed: false, id: todoId}
]);
setInputText("");
setInputTime("");
setInputDate("");
};
const statusHandler = (e) => {
setStatus(e.target.value);
}
return(
<form>
<input value={inputText} onChange={inputTextHandler} type="text" />
<input value={inputTime} onChange={inputTimeHandler} type="time" />
<button onClick={submitTodoHandler} type="submit">
<i>Lägg till</i>
</button>
<div>
<select onChange={statusHandler}>
<option value="all">All</option>
<option value="completed">Completed</option>
<option value="uncompleted">Uncompleted</option>
</select>
</div>
</form>
);
};
export default Form;
todolist.js
import React from "react";
import ToDo from './ToDo';
const ToDoList = ({todos, setTodos, filteredTodos}) => {
return(
<div>
<ul>
{filteredTodos.map(todo => (
<ToDo
todo={todo}
setTodos={setTodos}
todos={todos}
key={todo.id}
text={todo.text}
time={todo.time}
date={todo.date}
/>
))}
</ul>
</div>
);
};
export default ToDoList;
todo.js
import React from "react";
const ToDo = ({text, time, date, todo , todos, setTodos}) => {
const deleteHandler = () => {
setTodos(todos.filter((el) => el.id !== todo.id));
};
const completeHandler = () => {
setTodos(todos.map(item => {
if(item.id === todo.id){
return{
...item, completed: !item.completed
};
}
return item;
}))
}
return(
<div className={`${todo.passed ? "passed": ''}`}>
<li className={`${todo.completed ? "completed": ''}`}>
{text + ' ' + date + ' ' + time}
</li>
<button onClick={completeHandler}>check</button>
<button onClick={deleteHandler}>delete</button>
</div>
);
};
export default ToDo;

How to write a useComponent custom hook in React?

I want to create a custom hook useComponent which returns a JSX.Element that will be rendered elsewhere.
I have tried this:
import { useState} from 'react';
const useComponent = () => {
const [value, setValue] = useState('');
const c = () => {
return <>
<p>Component</p>
<input value={value} onChane={(e) => setValue(e.target.value)} />
</>
}
return {
c,
value,
}
}
export default function App() {
const {c: C} = useComponent();
return (
<div className="App">
<C />
</div>
);
}
but it does not work. Once I try typing on input, nothing happens.
How can I achieve this ?
I know it might be a bad practice to do such a thing, but the reason I want this is to be able to open a global dialog and pass the c component as children to the <Dialog /> component so I can both render c inside the dialog's body and also have access to the [value, setValue] state. So my use case would be something like:
[EDIT]
I also add the whole logic with dialog:
import { createContext, useContext, useState } from "react";
const Test = ({ value, setValue }) => {
return (
<>
<p>Component</p>
<input value={value} onChange={(e) => setValue(e.target.value)} />
</>
);
};
const useComponent = () => {
const [value, setValue] = useState("");
return {
element: <Test value={value} setValue={setValue} />,
value
};
};
const DialogCTX = createContext({});
export function DialogProvider(props) {
const [component, setComponent] = useState(null);
const ctx = {
component,
setComponent
};
return (
<DialogCTX.Provider value={ ctx }>
{props.children}
</DialogCTX.Provider>
);
}
export const useDialog = () => {
const {
component,
setComponent,
} = useContext(DialogCTX);
return {
component,
setComponent,
}
};
const Dialog = () => {
const { component } = useDialog();
return <div>
<p>Dialog</p>
{component}
</div>
}
const Setter = () => {
const {element, value} = useComponent();
const {setComponent} = useDialog();
return <div>
<p>Setter component</p>
<p>{value}</p>
<button onClick={() => setComponent(element)}>Set</button>
</div>
}
export default function App() {
return <div className="App">
<DialogProvider>
<Setter />
<Dialog />
</DialogProvider>
</div>;
}
As you said you want to return a JSX.Element but you actually returning a new component (a new function) every time your hook runs. So you could achieve your goal if you actually declare your component outside your hook and return the rendered one. Here is a working example:
import { useState } from "react";
const Test = ({ value, setValue }) => {
return (
<>
<p>Component</p>
<input value={value} onChange={(e) => setValue(e.target.value)} />
</>
);
};
const useComponent = () => {
const [value, setValue] = useState("");
return {
element: <Test value={value} setValue={setValue} />,
value
};
};
export default function App() {
const { element } = useComponent();
return <div className="App">{element}</div>;
}

Can't delete an item using useContext and useReducer

Please help guys. So I've started to learn about useReducer and useContext and I'm stuck at deleting an object. I've tried to log the result in the console and it prints the expected array that I want. However, when I return the array from the reducer, and whenever I delete an object, it clears the array and the button is left. So I've got this 2 js files below.
useContext & useReducer
import { createContext, useReducer } from "react";
const initialState = {
items: [],
};
const reducer = (state, action) => {
switch (action.type) {
case "ADD_ITEM":
return { ...state, items: [action.payload, ...state.items] };
case "DEL_ITEM":
return {
...state,
items: [state.items.filter((item) => item.id !== action.payload)],
};
default:
return state;
}
};
export const ItemContext = createContext(initialState);
export const ItemProvider = (props) => {
const [state, dispatch] = useReducer(reducer, initialState);
const addItem = (item) => {
dispatch({ type: "ADD_ITEM", payload: item });
};
const delItem = (id) => {
dispatch({ type: "DEL_ITEM", payload: id });
};
return (
<ItemContext.Provider value={{ items: state.items, addItem, delItem }}>
{props.children}
</ItemContext.Provider>
);
};
import React, { useContext, useState } from "react";
import { ItemContext } from "../Context/ItemContext";
const Test2 = () => {
const { items } = useContext(ItemContext);
const { addItem } = useContext(ItemContext);
const { delItem } = useContext(ItemContext);
const [name, setName] = useState("");
const [price, setPrice] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
const newItem = {
id: items.length + 1,
name: name,
price: price,
};
addItem(newItem);
setName("");
setPrice("");
console.log(newItem);
};
const handleDelete = (id) => {
delItem(id);
};
return (
<div>
<div>
<form>
<label>Product Name:</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<label>Product Price:</label>
<input
type="text"
value={price}
onChange={(e) => setPrice(e.target.value)}
/>
<button onClick={handleSubmit}>Submit</button>
</form>
</div>
<div>
{items.map((item) => (
<div key={item.id}>
<li>
<p>{item.name}</p>
<p>{item.price}</p>
</li>
<button onClick={() => handleDelete(item.id)}>X</button>
</div>
))}
</div>
</div>
);
};
export default Test2;
The Array.filter() already returns a new array, so when you're putting it into [], you are creating an array which contains an array as your items as the first element.
case "DEL_ITEM":
return {
...state,
items: state.items.filter((item) => item.id !== action.payload),
};
is therefore correct.

handleSubmit functional is not working in functional component of React

I am trying to convert class component of todo app into functional component. Everything goes well, but when I submit the form, the blank screen appears. I think there is some issue in handleSubmit function. Please help.
import React, {useState} from "react";
export const TodoFunc = (props: Props) => {
const [items, setItems] = useState([])
const [text, setText] = useState('')
const handleChange = (e) => {
setText(e.target.value)
}
const handleSubmit = (e) => {
e.preventDefault()
if (text.length === 0) {
return;
}
const newItems = {text: {text}, id: Date.now()}
setItems(() => (items.concat(newItems)))
setText('')
}
return (
<div>
<h3>TODO</h3>
<TodoList items = {items} />
<form onSubmit={handleSubmit}>
<label htmlFor="new-todo">
What do you want to do?
</label>
<input type="text"
id='new-todo'
onChange={handleChange}
value={text}
/>
<button>
Add #{items.length + 1}
</button>
</form>
</div>
);
};
const TodoList = (props) => {
return (
<ul>
{props.items.map(item => <li key={item.id}>{item.text}</li>)}
</ul>
)
}
Your problem lies in the below line:
const newItems = {text: {text}, id: Date.now()}
Here you are assigning an object to the text key and not just the value of the variable text.
And this is why when you loop over them in your TodoList component you are not able to display any of them.

Resources