React hooks select resuable component losing options values on set state - reactjs

I am trying to set options from a "state" Object, which has initial data which contains courses array.This
value is lost on setting state.I have added the comments line in the code.
Thanks for the help in advance.
Used for Input
Input.jsx
import React from 'react';
const Input = (props) => (
<div className="form-group">
<label htmlFor={props.name} className="form-label">{props.title}</label>
<input
className="form-input"
id={props.name}
name={props.name}
type={props.type}
value={props.value}
onChange={props.handleChange}
placeholder={props.placeholder}
/>
</div>
)
export default Input;
Select Resuable components
Select.jsx
import React from "react";
const Select=(props)=>(
<div className="form-group">
<label htmlFor={props.name}>{props.title}</label>
<select
id={props.id}
name={props.name}
value={props.value}
onChange={props.handleChange}
defaultValue={props.placeholder}
>
<option defaultValue={props.placeholder} disabled>{props.placeholder}</option>
{
props.options && props.options.map(option=>(
<option
key={option}
value={option}
label={option}
>{option}</option>
))
}
</select>
</div>
)
export default Select;
Setting state here losing courses values if not set through state
FormContainer.jsx
import React, {useState } from 'react';
import Input from "./Input";
import Select from "./Select";
import { useEffect } from 'react';
const FormContainer = function () {
const userInitialData ={
user:{
name: '',
course:'',
},
courses:["course1","course2"]
};
const courses=["course1","course2"];
const [newUser, setNewUser] = useState(userInitialData);
function handleFormSubmit(e) {
e.preventDefault();
console.log(newUser);
}
function handleClearForm (){
}
function onChange(e){
const value=e.target.value;
const name=e.target.name;
setNewUser((prevState)=>({
user:{...prevState.user,
[name]:value},
courses:prevState.courses // If this line is removed on set state losing values
}))
}
return (
<form className="container" onSubmit={handleFormSubmit}>
<Input id="name" name="name" type="input" title="name"
value={newUser.name} placeholder="enter name" handleChange={onChange}/>
<Select id="course" name="course" title="Course List"
placeholder="Select Course" handleChange={onChange}
options={newUser.courses}
value={newUser.course}
/>
<button onSubmit={handleFormSubmit}>Submit</button>
</form>
)
}
export default FormContainer;

Like stated in the comments, this behavior is expected. The useState updater function does not do a merge like it's class-based counterpart does.
From the docs:
unlike this.setState in a class, updating a state variable always replaces it instead of merging it.
This means every value you want to keep, must be explicitly given in the update.
There are a few easy ways to get the result you want. The first would be to simply spread the previous state values before you make any updates. Example below:
const {useState} = React;
const Example = () => {
const [state, setState] = useState({a: 'foo', b: 'bar'});
const update = () => {
setState((prevState) => ({
...prevState,
a: prevState.a + 'a'
}));
}
return (
<div>
<div>{JSON.stringify(state)}</div>
<button onClick={update}>Update "a"</button>
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
It's important to do the spread before the other values are assigned, or your updates will be overwritten. This method is a little simpler than replacing each value individually.
The second option would be to make a custom hook that will merge the state for you. This way you can treat the state update the same way as the class-based setState. Here is an example of what that custom hook might look like:
const {useState} = React;
const useMergeState = (initialValues) => {
const [state, setState] = useState(initialValues);
const updater = (obj) => {
if (typeof obj === "function") {
setState((prevState) => (Object.assign({}, prevState, obj(prevState))));
} else {
setState((prevState) => (Object.assign({}, prevState, obj)));
}
}
return [state, updater];
}
const Example = () => {
const [state, setState] = useMergeState({a: 'foo', b: 'bar'});
const update = () => {
setState((prevState) => ({a: prevState.a + 'a'}));
}
return (
<div>
<div>{JSON.stringify(state)}</div>
<button onClick={update}>Update "a"</button>
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
This hook could be moved to it's own file and reused throughout your app as needed. I added support for both plain object updates (setState({a: 'new value'})) or using the updater function. So this should not cause any loss of functionality.

Related

React onClick button is not triggering the function

I am developing an chrome extension where i need to authentication user but a very simple onClick button which calls a function is not working
this is the simple code where i want to show info on console when button is clicked
import React, { useState } from 'react';
const Login = () => {
const [user, setuser] = useState("");
const handleSubmit = (data) => {
data.preventDefault();
console.log("usernae: ");
console.log("Data: ", data.target);
}
const getInputValue = (event) => {
console.log(event.target.value)
// Select input element and get its value
console.log("I am heresdfg")
// let inputVal = document.getElementsByClassName("usernameInputField")[0].value;
// Display the value
// alert(inputVal);
}
return (
<div
id="login-form">
<p>
<div className='form'>
</div>
<input type="text"
id="username"
name="username"
className='usernameInputField'
value={user}
onChange={(event => setuser(event.target.value))}
placeholder="Username" required />
</p>
<p>
<button onClick={getInputValue} type="button" id="login">button</button>
</p>
</div>
);
};
export default Login;
It seems like you want the input value value inside the event handler if I'm not wrong, you can get it from the state - user as
const getInputValue = (event) => {
console.log(user)
}
as the event would be button's you wouldn't get the value of input from it's event and it is not required too as it's already in the react's state ....
Example:
const {useState} = React;
const App = () => {
const [name, setName] = useState("");
const submitHandler = () => {
console.log(name)
}
return (
<div>
Name: <input type="text" value={name} onChange={(e)=>setName(e.target.value)}/>
<button onClick={submitHandler}>Submit</button>
</div>
);
};
ReactDOM.createRoot(
document.getElementById("root")
).render(
<App/>
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
In the getInputValue function event is pointing to the button.
Change the event.target.value to user if you want to print the text into the console.
Here's the codesandbox.
If you don't want to use the value from useState then you can also check useRef hook which works in a similar way.

how to make sure to get the latest values of input when using useState hook in ReactJs

Below is a React component that uses useState hook but I do not get the latest values.
import { FC, useRef,useState } from "react";
import { Album } from "../../../types/album";
import "./album.module.css";
interface Props {
addAlbum(newAlbum: Album): void;
}
const NewAlbum: FC<Props> = (props) => {
const [name, setName] = useState('');
const [releaseDate, setReleaseDate] = useState('');
console.info("New Album Component Render");
const releaseDateRef = useRef<HTMLInputElement>(null);
const albumNameRef = useRef<HTMLInputElement>(null);
const addAlbumHandler = () => {
setName(albumNameRef.current?.value!)
setReleaseDate(releaseDateRef.current?.value!)
// *LOC
props.addAlbum({
name: name,
releaseDate: releaseDate
id: Math.random().toString(),
});
};
return (
<>
<div>
<input type="text" placeholder="Album Name" ref={albumNameRef} />
</div>
<div>
<input type="text" placeholder="Release year" ref={releaseDateRef} />
</div>
<div>
<button type="button" onClick={addAlbumHandler}>
Add New Album
</button>
</div>
</>
)};
export default NewAlbum;
When I click the AddNewAlbum button, addAlbumHandler() gets called however, I don't get the latest value of the name & releaseDate.
However, when I update the code at *LOC as below
props.addAlbum({
name: albumNameRef.current?.value!,
releaseDate: releaseDateRef.current?.value!,
id: Math.random().toString(),
});
I do get the desired values,
I understand that useState hook behind the scenes does not execute immediately
How to make sure to get the latest values of input when using useState hook in React?
Some tips:
You should never have to use useRef to retrieve values from elements. This is totally unreactly and totally unnecessary. The usual react way to do this is to define state, which is used as values for inputs and provide appropriate value change handlers to the inputs which alter the state.
As you know yourself, state changes do not happen immediately. This is the only reason your first example does not work and also explains why the second one does.
A recommendation for code that should work:
import { FC, useRef,useState } from "react";
import { Album } from "../../../types/album";
import "./album.module.css";
interface Props {
addAlbum(newAlbum: Album): void;
}
const NewAlbum: FC<Props> = (props) => {
const [name, setName] = useState('');
const [releaseDate, setReleaseDate] = useState('');
const nameChangeHandler = (e) => setName(e.target.value);
const releaseDateChangeHandler = (e) => setReleaseDate(e.target.value);
console.info("New Album Component Render");
const addAlbumHandler = (e) => {
// *LOC
props.addAlbum({
name: name,
releaseDate: releaseDate
id: Math.random().toString(),
});
};
return (
<>
<div>
<input
type="text"
placeholder="Album Name"
value={name}
onChange={nameChangeHandler}
/>
</div>
<div>
<input
type="text"
placeholder="Release year"
value={releaseDate}
onChange={releaseDateChangeHandler}
/>
</div>
<div>
<button type="button" onClick={addAlbumHandler}>
Add New Album
</button>
</div>
</>
);
}
export default NewAlbum;

Pass a variable in a functional component to a class component

I've a ToysPage.js with a class component and a SearchFeature.js with a functional component (child of ToysPage).
I made a searchBar in SearchFeature.js with hooks and with console.log I can see that it works. But how can I pass the const filteredToys to the state toysFiltered in ToysPage.js?
ToysPage.js
import React, { Component } from "react";
export default class ToysPage extends Component {
state = {
toys: undefined,
toysFiltered: undefined,
};
//Here I call all the toys through Axios and insert them in toys and toysFiltered
return (
<>
<div className="container">
<div className="row">
<SearchSide
toysFiltered={this.state.toysFiltered}/>
<ToysSide toys={this.state.toysFiltered} />
</div>
</div>
</>
);
}
}
function SearchSide({ toysFiltered }) {
return (
<>
<div className="col-3">
<SearchFeature toysFiltered={toysFiltered} />
</div>
</>
);
}
SearchFeature.js
import React, { useState, useMemo } from "react";
export default function SearchFeature({ toysFiltered }) {
const [query, setQuery] = useState("");
const [filteredToys, setFilteredToys] = useState(toysFiltered);
useMemo(() => {
const result = toysFiltered.filter((toy) => {
return toy.titulo.toLowerCase().includes(query.toLowerCase());
});
setFilteredToys(result);
}, [toysFiltered, query]);
return (
<div className="form-group">
<label>Search a Toy</label>
<input
type="text"
className="form-control"
placeholder="Search"
value={query}
onChange={(e) => {
setQuery(e.target.value);
}}
/>
</div>
);
}
You have some state in a child component, SearchFeature, that you would like in the parent component ToysPage. The usual pattern for this is to lift the state out of SearchFeature, which you have partially done.
You're almost doing this correctly. You pass a list of filtered toys into SearchFeature, but that list is only being used as a default value for a duplicate state variable in SearchFeature, and changes don't flow up to the parent:
// filteredToys has the real state. toysFiltered is just a default value.
const [filteredToys, setFilteredToys] = useState(toysFiltered);
Instead, leave the state variable in the parent, and send changes up from the search component.
Also, useMemo shouldn't have side effects. I've changed it to useEffect to be safe. There is a way to do this with useMemo, but that's out of the scope of the question.
in ToysPage.js
Add a setter function:
<SearchSide
toysFiltered={this.state.toysFiltered}
setToysFiltered={
newFilterList => this.setState({...this.state, toysFiltered: newFilterList})}
/>
Pass it through from SearchSide into SearchFeature:
function SearchSide({ toysFiltered, setToysFiltered }) {
return (
<>
<div className="col-3">
<SearchFeature toysFiltered={toysFiltered} setToysFiltered={setToysFiltered} />
</div>
</>
);
}
SearchFeature.js
export default function SearchFeature({ toysFiltered, setToysFiltered }) {
const [query, setQuery] = useState("");
useEffect(() => {
const result = toysFiltered.filter((toy) => {
return toy.titulo.toLowerCase().includes(query.toLowerCase());
});
setToysFiltered(result);
}, [setToysFiltered, query]);
// ...
}

how to use form in React as a component

I tried to put a form in a separate reusable component but when used that way I can't type anything into the input. I observed, that after entering one letter (it does not appear in the input box) it seems that React rerender the whole component and the name is updated with the inserted letter.
in the version 2 the same code works correctly.
// the part same for the both versions
const [userdata, setUser] = useState({});
const { name } = userdata
const handleChange = key => event => {
setUser({
...userdata,
[ key ]: event.target.value
});
};
const submitEdit = event => {
event.preventDefault();
handleChange();
};
// VERSION 1. doesn't work
const FormEdit = () => (
<form>
<div className="form-group">
<input onChange={handleChange("name")} type="text"/>
</div>
<button onClick={submitEdit}> Submit </button>
</form>
)
return (
<Layout>
<div>
{name} //<-it shows only one letter
<FormEdit />
</div>
</Layout>
);
// VERSION 2 -> works properly
return (
<Layout>
<div>
{name} //<-the updated name is shown immediately
<form>
<div className="form-group">
<input onChange={handleChange("name")} type="text"/>
</div>
<button onClick={submitEdit}> Submit </button>
</form>
</div>
</Layout>
);
};
export default User;
The issue is directly related to declaring the FormEdit component within the other component. Here's why:
In a functional component, everything declared inside gets destroyed and re-created each render. It's no different than a normal function call. This is what makes React's hooks so special. They keep track of values in between renders and make sure they are re-created with the correct values.
You're declaring the FormEdit component inside a function, which means not only is it re-declared every render, but as a side-effect it also un-mounts and remounts each render as well.
This has a few different effects:
The component's input loses focus every render.
It's impossible for it to maintain its own state.
It's not very performant.
Below is a working example to demonstrate.
const {useState, useEffect} = React;
const Example = () => {
// the part same for the both versions
const [userdata, setUser] = useState({});
const { name } = userdata
const handleChange = (key) => (event) => {
setUser({
...userdata,
[ key ]: event.target.value
});
};
const submitEdit = (event) => {
event.preventDefault();
handleChange();
};
const FormEdit = () => {
useEffect(() => {
console.log('mount');
return () => console.log('unmount');
}, []);
return (
<form>
<div>
<input onChange={handleChange("name")} type="text"/>
</div>
<button onClick={submitEdit}> Submit </button>
</form>
)
}
return (
<div>
{name}
<FormEdit />
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
As for why you only see the first character; You are not giving the input a value, only an onChange. If the component does not unmount, this just makes it an "uncontrolled" component. The input still gets it's value updated, you just can't programatically control it. But, since it is unmounting and re-mounting every render, it loses its last value every time the user types.
Making it a controlled input would fix this:
const {useState, useEffect} = React;
const Example = () => {
// the part same for the both versions
const [userdata, setUser] = useState({});
const { name } = userdata
const handleChange = (key) => (event) => {
setUser({
...userdata,
[ key ]: event.target.value
});
};
const submitEdit = (event) => {
event.preventDefault();
handleChange();
};
const FormEdit = () => {
useEffect(() => {
console.log('mount');
return () => console.log('unmount');
}, []);
return (
<form>
<div>
<input value={name} onChange={handleChange("name")} type="text"/>
// ^ Add this
</div>
<button onClick={submitEdit}> Submit </button>
</form>
)
}
return (
<div>
{name}
<FormEdit />
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
This is a little better, but still not ideal. Now it keeps the value each update, but it still loses focus. Not a very good user experience.
This final solution is to never declare a component within another component.
const {useState, useEffect} = React;
const FormEdit = (props) => {
useEffect(() => {
console.log('mount');
return () => console.log('unmount');
}, []);
return (
<form>
<div>
<input value={props.name} onChange={props.handleChange("name")} type="text"/>
</div>
<button onClick={props.submitEdit}> Submit </button>
</form>
)
}
const Example = () => {
// the part same for the both versions
const [userdata, setUser] = useState({});
const { name } = userdata
const handleChange = (key) => (event) => {
setUser({
...userdata,
[ key ]: event.target.value
});
};
const submitEdit = (event) => {
event.preventDefault();
handleChange();
};
return (
<div>
{name}
<FormEdit name={name} handleChange={handleChange} submitEdit={submitEdit} />
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Now it only mounts once, keeps focus, and updates as expected.
You would have to pass your form handlers to the child component as props so that the lifted state can be manipulated from the child.
// Parent Component
...
const [userdata, setUser] = useState({});
const { name } = userdata
const handleChange = key => event => {
...
};
const submitEdit = event => {
...
};
return (
<Layout>
<div>
{name}
<FormEdit handleChange={handleChange} submitEdit={submitEdit}/>
</div>
</Layout>
);
and then in the child:
// Child Component
const FormEdit = (props) => (
<form>
<div className="form-group">
<input onChange={props.handleChange("name")} type="text"/>
</div>
<button onClick={props.submitEdit}> Submit </button>
</form>
)
Your FormEdit component which is inside the App component is causing the entire App component to re-render when the state gets updated onChange and hence you can only enter only one character at a time. It is generally not a great idea to declare a component within a component. Refer this link for more info. All you have to do is pull the FormEdit component out of the App component in its own separate function and pass the change handlers as props to the FormEdit component. Have a look at the working code below.
import React, { useState } from 'react';
const FormEdit = ({ handleChange, submitEdit, name }) => {
return (
<form>
<div className='form-group'>
<input onChange={handleChange('name')} type='text' value={name || ''} />
</div>
<button onClick={submitEdit} type='submit'>
Submit
</button>
</form>
);
};
export default function App() {
const [userdata, setUser] = useState();
const { name } = userdata || {};
const handleChange = key => event => {
setUser(prevState => {
return { ...prevState, [key]: event.target.value };
});
event.persist();
event.preventDefault();
};
const submitEdit = event => {
event.preventDefault();
handleChange();
};
return (
<div>
<div>
{name || ''}
<FormEdit
handleChange={handleChange}
submitEdit={submitEdit}
name={name}
/>
</div>
</div>
);
}

React - make input appear with text selected

I have a functional component, that uses hooks.
const [editMode, setEditMode] = useState(false);
...
return (
...
{editMode && <input value="Some value">}
}
When editMode is changed to true - the input field is appearing and I want it to appear with already selected text inside of it. How can I do this?
You can use the useRef hook to create a ref and put it on your input element, and use the useEffect hook to run a function every time editMode changes. If editMode is true, you can invoke the select method on the ref.current element.
Example
const { useState, useRef, useEffect } = React;
function App() {
const [value, setValue] = useState("Some value");
const [editMode, setEditMode] = useState(false);
const ref = useRef();
useEffect(() => {
if (editMode) {
ref.current.select();
}
}, [editMode]);
return (
<div>
<button onClick={() => setEditMode(!editMode)}>Toggle edit</button>
<div>
{editMode && (
<input
ref={ref}
value={value}
onChange={e => setValue(e.target.value)}
/>
)}
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>

Resources