UseState not saving user input [duplicate] - javascript

This question already has answers here:
Push method in React Hooks (useState)?
(8 answers)
Closed 2 years ago.
Im working on a todo aplication in react using useState, im trying to save user input and then after they click submit push it to the listArray, later to display it...
I think im doing something wrong in the updateArray function, but I can seem to understand what.
import React, { useState } from "react";
function App() {
const listArray = [""];
const [list, updateList] = useState("");
function handleChange(event) {
const { name, value } = event.target;
updateList(value);
//console.log(list);
}
function updateArray() {
console.log(list);
listArray.push(list);
console.log(listArray);
}
return (
<div className="container">
<div className="heading">
<h1>To-Do List</h1>
</div>
<div className="form">
<input name="entry" onChange={handleChange} type="text" />
<button>
<span onSubmit={updateArray}>Add</span>
</button>
</div>
<div>
<ul>
<li>{listArray[0]}</li>
</ul>
</div>
</div>
);
}
export default App;

There are several issues with your current code and I will briefly describe and provide a solution to fix them.
Your functions are working fine and as expected, but in a React application there are few ways to re-render a page or component and changing the local variable is not one of them. So you need to use a local state instead of local listArray variable. Since there is one state already you should either define another state or make your current state an object and put your component related states into it in one place I will go with the second approach, because it's more elegant and cleaner one.
const [state, setState] = useState({ list: [], input: "" });
After you define your state, you need to change them properly without effecting unnecessary ones. You just need to send the previous state, save it in the new one and only change the related state in each function. So with ES6 syntax, updating input state will look like this:
setState((prev) => ({ ...prev, input: value })); // immediate return "({})" of an object with iterating through the previous values "...prev" and updating input "input: value"
NOTE: You can read more about spread operator (...) here.
So your handle and updateArray function will look like this:
function handleChange(event) {
const { value } = event.target;
setState((prev) => ({ ...prev, input: value }));
}
function updateArray() {
setState((prev) => ({ ...prev, list: [...state.list, state.input] }));
}
onSubmit event will only work on forms or submit buttons so you need to change it to onClick. Also, to make the whole button catch the onclick action you need to set it on the button itself, instead of span element.
<button onClick={updateArray}> <!-- It's better to assign onclick action to an element with a function or arrow function to prevent it from running in the first render "(event) => updateArray(event)" -->
<span>Add</span>
</button>
And finally you need to map through the updated array of todo items in your JSX.
<ul>
{state.list.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
Working Demo:

Save the current value into the state, and keep the list as well into the state so that it isn't cleared each render cycle.
import React, { useState } from "react";
function App() {
const [list, updateList] = useState([]);
const [currentValue, setCurrentValue] = useState()
function handleChange(event) {
setCurrentValue(event.target.value)
}
function handleClick() {
updateList([...list, currentValue])
}
return (
<div className="container">
<div className="heading">
<h1>To-Do List</h1>
</div>
<div className="form">
<input name="entry" onChange={handleChange} type="text" />
<button type="button" onClick={handleClick}>
<span>Add</span>
</button>
</div>
<div>
<ul>
{list.map((res) => (
<li key={res}>{res}</li>
))}
</ul>
</div>
</div>
);
}
export default App;
Also, moving the onClick to the button makes more sense semantically (and even for the UX) but it's up to you.

listArray is cleared with every rerender.
You should store your data in state. So
const [listArray, setListArray] = useState([])
And updateArray should look like:
function updateArray() {
setListArray([...listArray, list]);
}
I guess, in updateArray function should be some logic to clear list

Notice how listArray will always go back to the default value when your app component re-renders (using useState may re-render the component)
I would instead make the string the user inputs a normal const and use useState for the array so the values in the array will be saved across re-renders

Related

Is good to use React.useMemo or React.useCallback inside component props?

I was thinking about how to code TailwindCSS cleaner in React. Since Tailwind is utility-first, it makes us inevitably end up with components (ex: className="w-full bg-red-500"). So, I tried to create a utility like this:
utils/tailwind.ts
const tw = (...classes: string[]) => classes.join(' ')
and call it inside:
components/Example.tsx
import { useState } from 'react'
import tw from '../utils/tailwind'
const Example = () => {
const [text, setText] = useState('')
return (
<div>
<input onChange={(e: any) => setText(e.target.value)} />
<div
className={tw(
'w-full',
'h-full',
'bg-red-500'
)}
>
hello
</div>
</div>
)
}
But, it will cause tw() to be re-called as always as text state is updated.
So, I decided to wrap tw() function using useMemo to prevent re-call since the tw() always returns the same value. But the code is like this:
import { useState, useMemo } from 'react'
import tw from '../utils/tailwind'
const Example = () => {
const [text, setText] = useState('')
return (
<div>
<input onChange={(e: any) => setText(e.target.value)} />
<div
className={useMemo(() => tw(
'w-full',
'h-full',
'bg-red-500'
), [])}
>
hello
</div>
</div>
)
}
Is it correct or good practice if I put useMemo like that? Thank you 🙏 .
Is it correct or good practice if I put useMemo like that?
Short answer - yes.
Long answer - it depends. It depends on how heavy the operation is. In your particular case, joining a couple of strings may not be such heavy calculation to make the useMemo worth to be used - it's good to remember that useMemo memoizes stuff and it takes memory.
Consider example below. In the first case, without useMemo, the tw function will be called with every App re-render, to calculate new className. However, if useMemo is used (with empty dependency array), tw will not be called and new className will not be calculated even if the App re-renders, due to the basic memoization. It will be called only once, on component mount.
Conclusion - it's a good practice to use useMemo, but rather for heavy operations, like mapping or reducing huge arrays.
export default function App() {
const [_, s] = useState(0);
return (
<div className="App">
<div className={tw(false, 'w-full', 'h-full', 'bg-red-500')}>div1</div>
<div
className={useMemo(
() => tw(true, 'w-full', 'h-full', 'bg-red-500'),
[],
)}
>
div2
</div>
<button onClick={() => s(Math.random())}>re-render</button>
</div>
);
}
Playground: https://codesandbox.io/s/distracted-liskov-tfm72c?file=/src/App.tsx
The issue here is that React will re-render the component every time it's state changes. (each time you setText).
If you want to prevent that from happening, then see if you really need this re-render hence what do you really need the input text for?
you do not HAVE to use state here to use the input value.
you could call another function on change which will not update the state, and use the input value there for whatever you need.
for example:
const Example = () => {
const onInputChange = (e) => {
const text = e.target.value
// do something with text
}
return (
<div>
<input onChange={(e: any) => onInputChange(e)} />
<div
className={useMemo(() => tw(
'w-full',
'h-full',
'bg-red-500'
), [])}
>
hello
</div>
</div>
)
}

Exporting a variable set in a function to another component React

I have some variables that are set within a function in one component of my react application that I need to reuse in other components.
I set the variables in component 1 like so (this is a much simplified version but captures the structure)
export default function Example() {
const [market, setMarket] = useState('');
return (
<button onClick={setMarket('1')}>Click 1</button>
<button onClick={setMarket('2')}>Click 2</button>
<button onClick={setMarket('3')}>Click 3</button> )}
How can I export the 'market' variable specifically, so that I can import it into another component (in a separate jsx file) and render as necessary. I know that I can just import the whole component, or set a variable outside of this function in component 1 and export it but I do not know how I would then conditionally set it based on which button is clicked.
Thank you
Hey #Milo there are different ways to use state value in another component.
First is props-
Create a component that passes values like-
const passValue = () => {
const [ value, setValue ] = useState("")
return (
)
}
While in the second component we get the value like-
const SecondComponent = ({value})=>{
return(
<div>
{value}
</div>
)
}
While Second method is to pass value using state and get it by useLocation in another component-
First Component like-
const FirstComponent = () =>{
return(
<div>
<Link to="/secondpage" state={{value:yourValue/state}}>Click Here</Link>
</div>
)
}
Second Component Like-
const Second Component = () => {
const {state} = useLocation()
return(
<div>{state}</div>
)
}
Hope these solution helps to solve your problem. If you still facing issue lemme know, i will help you.
Thanks

React todolist onDoubleclick Input edit value show the current value instead of empty

I'm trying to create a todolist with React which when you double click on one of the to do list that will show input that allow you to edit the value. But I want to show the before value in the input edit when user click on it and the user can erase the before value and change to new and change the value.
I know, my explanation is very bad. I will show the example that I want in here https://todomvc.com/examples/react/#/.
I want to make just like this person todo mvc which when you double click the todolist the edit input value still shown the before value.
import {useState} from 'react';
export default function App() {
const [todo, setTodo] = useState([])
const [input, setInput] = useState('')
const [edit, setEditing]= useState(null)
const [edittext , setEditingText] = useState('')
const InputHandler = (e)=>{
setInput(e.target.value)
}
const SubmitHandler = ()=>{
setTodo([...todo, {text:input, id: Math.random()*1000}])
setInput('')
}
const EditHandler = (e)=>{
setEditingText(e.target.value)
console.log(e.target.value)
}
const SubmitEdit = (id)=>{
setTodo([...todo].map((todos)=>{
if(todos.id === id){
todos.text = edittext
}
return todos
}))
setEditing(null)
setEditingText("")
}
return (
<div className="App">
<input value={input} onChange={InputHandler}/>
<button onClick={SubmitHandler}>Add</button>
{todo.map(todos =>
<div key={todos.id}>
{edit === todos.id ?
(<><input type="text" value={edittext} onChange={EditHandler}/>
<button onClick={()=>SubmitEdit(todos.id)}>Edit</button></>)
: (<p onDoubleClick={()=>setEditing(todos.id)}>{todos.text}</p>)
}
</div>
)}
</div>
);
}
I'm sorry if my explanation is a little confusing.
It's all good, just update the editing text as well on double click.
<p onDoubleClick={()=>{setEditing(todos.id); setEditingText(todos.text)}}>{todos.text}</p>
I get you, first in the input to edit the todo you need to use the same value as what you used in adding todo.
<input type="text" value={todos.text} onChange={EditHandler}/>
But in your todo app you use many state which make an app very hard to manage its state.
Lastly its bad practice to use useState function direct in the eventHandler function like what you have used in onDoubleClick.
Try to visit thinking in React by React js website. You will thanks your self for your time. https://reactjs.org/docs/thinking-in-react.html

Getting data from react state function component

I have 2 function component , one have input and button and its passing data to states. Now i want to get state data from first function conlmponent to second component and display text to my div in second component. Is it possible to share states data from 1st component to 2nd component?
The component are functions not classes
function Articles(){
return(<div>
<h1>{inputtext}</h1>
</div>)
}
function Inputform() {
const [inputtext, setInputtext] = useState('');
const [task_list,setTask_list] = useState([]);
function changer(event){
setInputtext(event.target.value);
}
function add_post(){
setTask_list(inputtext);
}
return (
<div>
<label>Post</label>
<input name="first" onChange={changer} />
<button onClick={add_post}>Dodaj</button>
</div>
)
}
trying to get states in Articles from Input but it doesnt work
what you would like to do, is to put this two components inside a third one and have them share whatever states/data you want.
function ThirdComponent() {
const [inputtext, setInputtext] = useState('');
const [task_list,setTask_list] = useState([]);
function Articles(){
return(<div>
<h1>{inputtext}</h1>
</div>)
}
function Inputform() {
function changer(event){
setInputtext(event.target.value);
}
function add_post(){
setTask_list(inputtext);
}
return (
<div>
<label>Post</label>
<input name="first" onChange={changer} />
<button onClick={add_post}>Dodaj</button>
</div>
)
}
return (
<>
</>
)
}
State belongs to a component. It does not create global variables.
If you want to pass data from a state to another component, then:
The recipient must be a child of the element holding the data
You can pass it as a prop
Since you don't have <Article propName={stateVariable} /> you can't do that.
If the destination isn't a child of the element holding the state then you need to Lift State Up.
Move the useState hook to component that is ancestor of both <Article> and <Inputform>
Pass the set function as a prop to <Inputform>
Pass the state value variable as a prop to <Article>.
For complex systems where the common ancestor is a long way from the components that need to work with the state, you might consider using Context or an external state management system like Redux.

hide and show names using react hooks

I am new to react hooks,
in my app initially, all the names of the users should be hidden.
later when I click each user it should show the name.
so I used the show and setshow.
when I tried to print the values in the browser, I don't see it.return(<div>{show}users Data{setShow}
I wrote click for each user but I am not sure how to hide and show.
there will be millions of users in my app, so whats the best way to hide and show the name when I click each
providing my code snippet and sandbox below
https://stackblitz.com/edit/react-t1mdfj?file=index.js
import React, { Component, useState } from "react";
import { render } from "react-dom";
import Hello from "./Hello";
import "./style.css";
function DataClick(){
const [show, setShow]= useState(false);
function showItem(e){
console.log("e--->", e.target);
setShow(true);
}
return(<div>{show}users Data{setShow}
<div onClick= {showItem}
//onClick={()=>setShow(true)}
>user1</div>
<div>John</div>
<div onClick= {showItem}
//onClick={()=>setShow(true)}
>user2</div>
<div>Mike</div>
<div onClick= {showItem}
//onClick={()=>setShow(true)}
>user3</div>
<div>Mike3</div><div onClick= {showItem}
//onClick={()=>setShow(true)}
>user4</div>
<div>Mik4e</div><div onClick= {showItem}
//onClick={()=>setShow(true)}
>user5</div>
<div>Mike5</div><div onClick= {showItem}
//onClick={()=>setShow(true)}
>user6</div>
<div>Mike6</div><div onClick= {showItem}
//onClick={()=>setShow(true)}
>user7</div>
<div>Mike7</div><div onClick= {showItem}
//onClick={()=>setShow(true)}
>user8</div>
<div>Mike8</div>
</div>);
}
render(<DataClick />, document.getElementById("root"));
#computer cool, Please see the below implementation to show and hide the usernames when user id is clicked, this is an updated code of #JMadelaine's implementation. Hope this will help. (In #JMadelaine's implementation one drawback I found is, once the username is shown it was not hiding back when clicking on the user id again because he was always setting the state to true onClick), please see the below code.
import React, { Component, useState } from "react";
const UserItem = ({ user }) => {
const [isNameShown, setIsNameShown] = useState(false)
const handleChange = () => {
setIsNameShown(prevState => !prevState)
}
return (
<div onClick={handleChange} >
<div>{user.id}</div>
{isNameShown && <div>{user.name}</div>}
</div>
)
}
function DataClick() {
const users = [
{
id: 'user1',
name: 'John',
},
{
id: 'user2',
name: 'Peter',
},
]
return (
<div>
{users.map(user => <UserItem user={user} />)}
</div>
)
}
export default DataClick;
Here the handleChange function sets the value of the state using the previous value by passing a callback function instead of direct value, so this callback function will get the previous state as argument and returns the inverted value, so if the user is opened, it will close, if the user is closed it will open.
EDIT:
Below is the explanation of the code setIsNameShown(prevState => !prevState)
setIsNameShown or any function that is returned by the useState hook can be written in the below ways.
1st way example:
setIsNameShown(false)
in this, you are directly passing the value to be set irrespective of the previous value.
2nd way example:
setIsNameShown((prevStateVariable) => {
return !prevStateVariable
})
or more concisely this same can be written as
setIsNameShown(prevStateVariable => !prevStateVariable)
in this case, the function setIsNameShown accepts the callback function which receives the previous state (to which the function is related to) as an argument.
So in cases where you need to set values to state which depends on the previous state value, use the callback function instead of directly providing the value.
The useState hook is just like a class component's state and setState. In your case show is the state variable that has a true or false value, and you can change this value using the function setShow.
To conditionally show the users' names, you should use the value of show like so:
return(
<div>
<div onClick={() => setShow(true)}>user1</div>
{show && <div>John</div>}
<div onClick={() => setShow(true)}>user2</div>
{show && <div>Mike</div>}
</div>
)
However, I don't think that is what you want. It sounds like you want to show only the name of the user that was clicked, and in the current state, when you click one user, all usernames will show because they all depend on the same state variable show.
You should create a separate component that contains the logic to show and hide a username, and map each user to that component. That way, each user has their own show state.
EDIT:
I've updated your code with the below:
// each user gets mapped to this component
const UserItem = ({user}) => {
// that means each user has their own 'isNameShown' variable
const [isNameShown, setIsNameShown] = useState(false)
return (
// if you click this div, this user's isNameShown value will be set to true
<div onClick={() => setIsNameShown(true)}>
// user.id is the id of the user from props
<div>{user.id}</div>
// only show this user's name if this user's isNameShown is true
{isNameShown && <div>{user.name}</div>}
</div>
)
}
function DataClick(){
// we just create some example users here for testing
const users = [
{
id: 'user1',
name: 'John',
},
{
id: 'user2',
name: 'Peter',
},
]
return(
<div>
// map each user to a UserItem, passing the user as a prop
{users.map(user => <UserItem user={user} />)}
</div>
)
}
I don't know what your users data structure looks like, so you should change it accordingly.
Something to learn here is that, if you find yourself repeating code over and over or copy pasting things again and again, then there is usually a better way of achieving what you want. Your list of users was basically copied and pasted over and over again, so you should create a single UserItem component instead.
There may be on issue with this code, which is that once a username is visible, you cannot hide it again, but I'll let you figure out how to do that if that is your intention.

Categories

Resources