React onClick update useState variable and render updated - reactjs

I know this question has been asked before, but the solutions were different from the way I structure/code my React app as shown below. How use one update/render based on the updated listData?
import React, { useState } from "react";
const PageName = () => {
const [listData, setlistData] = useState([]);
function add(){
let newrow = {};
listData.push(newrow);
setlistData(listData);
}
return (
<div>
{listData.length}
<button onClick={() => add()}>Add</button>
{
listData.map(function (row, i) {
return (
<p key={i}>row</p>
);
})
}
</div>
);
};
export default PageName;

If it is a state variable it is react's job to rerender. Your job is to ensure you are passing in a new reference to setState() without mutating the state.
Use ... spread operator.
import React, { useState } from "react";
const PageName = () => {
const [listData, setlistData] = useState([]);
function add(){
let newrow = {};
let newListData = [...listData];
newListData.push(newrow);
setlistData(newListData);
}
return (
<div>
{listData.length}
<button onClick={() => add()}>Add</button>
{
listData.map(function (row, i) {
return (
<p key={i}>row</p>
);
})
}
</div>
);
};
export default PageName;

The reference stays the same if you only push to the same object .
If you push to the list , under the hood the list gets the data but React cannot rerender because the object listData does not save value but reference , which doesn't change . Creating a new object by copying all the data and putting it in a new object changes the reference of newreferenceListData ,which causes re render.
function add(){
let newrow = {};
let newreferenceListData = [...listData,newrow];
setlistData(newreferenceListData);
}

Related

Redux How to get updated state value without using useSelector

I am using functional component and trying to achieve MVVM. I want to get updated value of redux state without using useSelector hook
Here is my code
model.js
export class Model {
getData = () => {
return store.getState().mReducer.jsonData
}
setData = (data) => {
store.dispatch(setData(data)) // storing in redux for further use
}
}
ViewModel.js
export class ViewModel {
onChangeTextHandler = (text) => {
this.model.setData(tmp)
}
}
View.js
export const View = () => {
const vm = useMemo(() => new ENReceivingViewModel(), [])
const model = useMemo(() => new ENREceivingModel(), []);
//IF I use this line then rerender happens otherwise nothing happens
//const { selectedOption, jsonData } = useSelector(state => state.ReceivingReducer)
return (
<TextInput value = {model.getData()[0]}
onChangeText={vm.onChangeTextHandler} />
)}
I don't think that would be possible to handle it in this way, the model object keeps the only value of the store that was in the initialization.
I think passing store to method of class will do what you want:
like this:
export class Model {
getData = (store) => {
return store.getState().mReducer.jsonData
}
setData = (data) => {
store.dispatch(setData(data)) // storing in redux for further use
}
}
and in component:
import store from "./store"
<TextInput value = {model.getData(store)[0]}
onChangeText={vm.onChangeTextHandler} />
or another way is to add dependency in useMemo
like this :
const model = useMemo(() => new ENREceivingModel(), [someState]);
in this way every time that someState changes, a new ENREceivingModel will be replaced the previous one

How can I return my todos after I remove some through splice in this particular code

I was watching a tutorial on how to make todos, though my main focus was local storage use.
But when he made the delete button then I was a bit confused, the code below shows how he did it but I am not getting it.
Can anyone explain that I tried using the splice method to remove items from the array but I am not able to remove the items from the page?
Can you also suggest what should I do after using splice to return the array on the page?
Below is the code,
import "./styles.css";
import { useState, useEffect } from 'react'
import Todoform from './TodoForm'
export default function App() {
const [list, setlist] = useState("");
const [items, setitems] = useState([])
const itemevent = (e) => {
setlist(e.target.value);
}
const listofitem = () => {
setitems((e) => {
return [...e , list];
})
}
const deleteItems = (e) => {
// TODO: items.splice(e-1, 1);
// Is there any other way I can do the below thing .i.e
// to remove todos from page.
// this is from tutorial
setitems((e1)=>{
return e1.filter((er , index)=>{
return index!=e-1;
})
})
}
return (
<>
<div className='display_info'>
<h1>TODO LIST</h1>
<br />
<input onChange={itemevent} value={list} type="text" name="" id="" />
<br />
<button onClick={listofitem} >Add </button>
<ul>
{
items.map((e, index) => {
index++;
return (
<>
<Todoform onSelect={deleteItems} id={index} key={index} index={index} text={e} />
</>
)
})
}
</ul>
</div>
</>
)
}
And this is the TodoForm in this code above,
import React from 'react'
export default function Todoform(props) {
const { text, index } = props;
return (
<>
<div key={index} >
{index}. {text}
<button onClick={() => {
props.onSelect(index)
}} className="delete">remove</button>
</div>
</>
)
}
Here is the codeSandbox link
https://codesandbox.io/s/old-wood-cbnq86?file=/src/TodoForm.jsx:0-317
I think one issue with your code example is that you don't delete the todo entry from localStorage but only from the components state.
You might wanna keep localStorage in sync with the components state by using Reacts useEffect hook (React Docs) and use Array.splice in order to remove certain array elements by their index (Array.splice docs).
// ..
export default function App() {
const [list, setlist] = useState("");
const [items, setitems] = useState([])
/* As this `useEffect` has an empty dependency array (the 2nd parameter), it gets called only once (after first render).
It initially retrieves the data from localStorage and pushes it to the `todos` state. */
useEffect(() => {
const todos = JSON.parse(localStorage.getItem("notes"));
setitems(todos);
}, [])
/* This `useEffect` depends on the `items` state. That means whenever `items` change, this hook gets re-run.
In here, we set sync localStorage to the current `notes` state. */
useEffect(() => {
localStorage.setItem("notes", JSON.stringify(items));
}, [items])
const itemevent = (e) => {
setlist(e.target.value);
}
const listofitem = () => {
setitems((e) => {
return [...e , list];
})
}
const deleteItems = (index) => {
// This removes one (2nd parameter) element(s) from array `items` on index `index`
const newItems = items.splice(index, 1)
setitems(newItems)
}
return (
<>
{/* ... */}
</>
)
}
There are multiple ways to remove an item from a list in JS, your version of splicing the last index is correct too and it is able to remove the last item. What it can't do is update your state.
His code is doing two things at the same time: Removing the last item of the Todo array and then, setting the resulted array in the state which updates the todo list.
So, change your code as
const deleteItems = (e) => {
let newItems = [...items];
newItems.splice(e-1, 1);
setitems(newItems);
}

useEffect not working saving data in my local storage when I refresh my page of the todo list

I am new to react and creating my first react app. not sure why the todo list is not saved even though I have used localStorage set and get methods. I am also getting error about the key in my map method. I can't seen to find any issues on my own with the code.Below is the code of the todo list App
import TodoList from "./TodoList";
import {v4 as uuid} from 'uuid'
function App() {
const [todos,setTodos] = useState([{}]);
const inputRef = useRef();
const LOCAL_STORAGE_KEY = "todoapp"
useEffect(() =>{
const storedTodos = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY))
if(storedTodos){
setTodos(storedTodos)}
}, [])
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY,JSON.stringify(todos))
}, [todos])
function toggleTodo(id){
const newTodos= [...todos]
const todo = newTodos.find(todo => todo.id === id)
todo.complete = !todo.complete
setTodos(newTodos)
}
function handleAdd(e) {
const name = inputRef.current.value;
if(name === "")return
setTodos(prevTodos => {
return [...prevTodos,{id:uuid(),name:name,complete:false}]
})
inputRef.current.value = null;
}
function handleClearTodos(){
const newTodos = todos.filter(todo=>!todo.complete)
setTodos(newTodos)
}
return (
<>
<h1>Chores!!</h1>
<TodoList todo={todos} toggleTodo ={toggleTodo} />
<input ref={inputRef} type="text" />
<button onClick ={handleAdd}>Add todo</button>
<button onClick={handleClearTodos}>Clear todo </button>
<div> {todos.filter(todo => !todo.complete).length} left todo</div>
</>
)
}
export default App;
import Todo from './Todo'
export default function TodoList({todo,toggleTodo}) {
return (
todo.map((todo)=> {
return <Todo key={todo.id} todo={todo} toggleTodo={toggleTodo} />
})
)
}
This:
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY,JSON.stringify(todos))
}, [todos])
Is probably taking the initial state of todos on the first render (empty array) and overwriting what data was in their with that initial state.
You might think the previous effect counters this since todos is populated from local storage -- but it doesn't, because on that initial render pass, the second effect will only see the old value of todos. This seems counter-intuitive at first. But it's because whenever you call a set state operation, it doesn't actual change the value of todos immediately, it waits until the render passes, and then it changes for the next render. I.e. it is, in a way, "queued".
For the local storage setItem, you probably want to do it in the event handler of what manipulates the todos and not in an effect. See the React docs.
import TodoList from "./TodoList";
import {v4 as uuid} from 'uuid'
function App() {
const [todos,setTodos] = useState([{}]);
const inputRef = useRef();
const LOCAL_STORAGE_KEY = "todoapp"
const storeTodos = (todos) => {
localStorage.setItem(LOCAL_STORAGE_KEY,JSON.stringify(todos))
setTodos(todos)
}
useEffect(() =>{
const storedTodos = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY))
if(storedTodos){
setTodos(storedTodos)}
}, [])
function toggleTodo(id){
const newTodos= [...todos]
const todo = newTodos.find(todo => todo.id === id)
todo.complete = !todo.complete
storeTodos(newTodos)
}
function handleAdd(e) {
const name = inputRef.current.value;
if(name === "")return
storeTodos(prevTodos => {
return [...prevTodos,{id:uuid(),name:name,complete:false}]
})
inputRef.current.value = null;
}
function handleClearTodos(){
const newTodos = todos.filter(todo=>!todo.complete)
storeTodos(newTodos)
}
return (
<>
<h1>Chores!!</h1>
<TodoList todo={todos} toggleTodo ={toggleTodo} />
<input ref={inputRef} type="text" />
<button onClick ={handleAdd}>Add todo</button>
<button onClick={handleClearTodos}>Clear todo </button>
<div> {todos.filter(todo => !todo.complete).length} left todo</div>
</>
)
}
export default App;
As the for the key error, we'd need to see the code in TodoList, but you need to ensure when you map over them, that the id property of each todo is passed to a key prop on the top most element/component within the map callback.

useEffect and useState to fetch API data

I want to use useEffect(on mount) to fetch from API and store it in useState. Fetch API is used to get the data. The problem is when initial page loading and also when I reload the page, it outputs an error called test.map is not a function. Why this happening and how to avoid this ?
import { useEffect, useState } from 'react';
function App() {
const[test, setTest] = useState({})
useEffect(() => {
testfunc()
}, [])
async function testfunc(){
let api = await fetch('https://jsonplaceholder.typicode.com/users')
let apijson = await api.json()
setTest(apijson)
}
return (
<div className="App">
{
test.map((item) => {
return(
<div>
{item.name}
</div>
)
})
}
</div>
);
}
export default App;
You can't map on an object {}, so you should need to define an array [] for the base state :
const[test, setTest] = useState([])
You have to change {} to array first to be able to map over it. You can easily place ? after test like this. or make in the default value of the state a default value for item name. because this error results as you map over an empty object.
import { useEffect, useState } from 'react';
function App() {
const[test, setTest] = useState([{name:"default"}])
useEffect(() => {
testfunc()
}, [])
async function testfunc(){
let api = await fetch('https://jsonplaceholder.typicode.com/users')
let apijson = await api.json()
setTest(apijson)
}
return (
<div className="App">
{
test?.map((item) => {
return(
<div>
{item.name}
</div>
)
})
}
</div>
);
}
export default App;
As already mentioned, you can't use the .map for objects.
Instead of this, you can make something like that
Object.keys(test).map(key => {
const currentSmth = test[key]
return(
<div>
{currentSmth.name}
</div>
)
})
})
I think it helps you to solve your problem.
Be careful using the correct data structures and methods.

Reactjs: useState cannot update

I am just learning react,
Here I have set the Initial value and onClick I want to change the value, but nothing happens, react does't rerender the screen, no value changes.
const app = () => {
const [person, setPerson] = useState([
{name:'sugumar',age:23},
{name:'vijay',age:25}
]
)
const changeNameHandler = () => {
console.log('change name called');
setPerson((person)=>{
console.log('set property called');
person[0].name = 'Arun';
return person;
})
}
return (
<div className='App'>
<button onClick={changeNameHandler}>Change Name</button>
<Person name={person[0].name} age={person[0].age}></Person>
<Person name={person[1].name} age={person[1].age}></Person>
</div>
)
}
export default app;
person[0].name will reference the array created from initializing the state. Changing its identifier will not help either. You need to clone the array to prevent mutating it. You can read more on functional programming & immutable objects
function Clone(obj) {
if (obj === null || typeof obj !== "object" || "isActiveClone" in obj)
return obj;
var temp;
if (obj instanceof Date) temp = new obj.constructor();
//or new Date(obj);
else temp = obj.constructor();
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
obj["isActiveClone"] = null;
temp[key] = Clone(obj[key]);
delete obj["isActiveClone"];
}
}
return temp;
}
const changeNameHandler = () => {
let newPerson = Clone(person);
newPerson[0].name = "Arun";
setPerson(newPerson);
};
In this context however, you can also use the spread syntax (ES6 feature) so you will no longer need the aforementioned Clone function. You may opt to use the function I shared when your object gets more complex & you need to deep clone.
const changeNameHandler = () => {
let newPerson = [...person];
newPerson[0].name = "Arun";
setPerson(newPerson);
};
CodeSandBox: https://codesandbox.io/s/lively-fire-qmvil?file=/src/App.js:198-331
Try this
import React, { useState } from "react";
const Person = ({ name, age }) => (
<div>
{name} - {age}
</div>
);
const App = () => {
const [person, setPerson] = useState([
{ name: "sugumar", age: 23 },
{ name: "vijay", age: 25 }
]);
const changeNameHandler = () => {
person[0].name = "Arun, Updated On: " + new Date().getTime();
setPerson([...person]);
};
return (
<div className="App">
<button onClick={changeNameHandler}>Change Name</button>
<Person name={person[0].name} age={person[0].age} />
<Person name={person[1].name} age={person[1].age} />
</div>
);
};
export default App;
I think you're missing what is stated here https://reactjs.org/docs/hooks-faq.html#should-i-use-one-or-many-state-variables
Create a second copy of the array before modifying it ...
const changeNameHandler = () => {
console.log('change name called');
let persons = person;
persons[0].name = 'Arun';
setPerson(persons);
}
For what it's worth ... I don't think binding a form to a single element in an array is a good choice for updating state ... but it's what you asked for.
.. and you don't need to return the value from the event handler.

Resources