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>
Related
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.
So I want to have a span <spam contentEditable={true} onChange={...} {...props}>{value}</span>, but I don't have onChange effect on it, rather I need to use onInput, which in the case with React doesn't work, so my question is how can I add onChange event like on input to a span element that has contentEditable on React?
Try onInput method like this, it works!
<div
contentEditable='true'
onInput={e => console.log('Text inside div',e.currentTarget.textContent)}
>
Text inside div
</div>
It works with differnet tags
const {
useState,
useEffect
} = React;
function App() {
const [input, setInput] = useState('I am input');
const [h1, setH1] = useState('I am H1 tag');
const [p, setP] = useState('I am Paragraph tag');
const [span, setSpan] = useState('I am Span tag');
useEffect(() => {
console.log('Input value change', input);
}, [input]);
useEffect(() => {
console.log('H1 value change', h1);
}, [h1]);
useEffect(() => {
console.log('P value change', h1);
}, [p]);
useEffect(() => {
console.log('Span value change', h1);
}, [span]);
return (
<div>
<input
contentEditable
suppressContentEditableWarning
value={input}
onChange={(event) => setInput(event.currentTarget.value)}
/>
<h1
contentEditable
suppressContentEditableWarning
onBlur={(event) => setH1(event.currentTarget.textContent)}
>
{h1}
</h1>
<p
contentEditable
suppressContentEditableWarning
onBlur={(event) => setP(event.currentTarget.textContent)}
>
{p}
</p>
<span
contentEditable
suppressContentEditableWarning
onBlur={(event) => setSpan(event.currentTarget.textContent)}
>
{span}
</span>
</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>
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.
So when I'm trying to log the value of the input every onChange event fires, it gives a weird 1 event delay, As 'some text' would be the default value on the input, say when I remove 't' from 'some text' by pressing backspace/delete it logs 'some text' first instead of 'some tex' then when the 2nd onChange (keystroke) fires that's the time I get the expected log (see photo below). Is there something that I'm missing, a gap on my understanding maybe?
So basically what's happening is, when you do some key stoke it's just logging any default value first then you get the expected log after pressing more keystroke in "1-event-callback-delayed" manner. weird eh? how would I get rid of the delay? help.
const TestComponent = () => {
const [val, setVal] = React.useState('some text');
const handleOnchange = React.useCallback((event: TypeEvent) => {
setVal(event.target.value);
console.log(val);
}, [val]);
return (
<>
<input type='text' value={val} onChange={handleOnchange} />
</>
);
}
Console the value e.target.value which is been set to the state,
inside the handler without useCallback would be fine.
const App = () => {
const [val, setVal] = React.useState('some text');
const handleOnchange = e => {
console.log(e.target.value)
setVal(e.target.value)
}
return (
<div className="App">
<input type='text' value={val} onChange={handleOnchange} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
Update: demo with useCallback
const App = () => {
const [val, setVal] = React.useState('some text');
const handleOnchange = React.useCallback(e => {
console.log(e.target.value);
setVal(e.target.value);
}, [val]);
return (
<div className="App">
<input type='text' value={val} onChange={handleOnchange} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
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>
);
}