Cannot Update State with Form onSubmit React - javascript

I have a component with a list of components. I have an initial cardlist which gets populated as the list in my component. Each time I submit the form I want to add a new component, but I can't get it to update even though my cardlist array gets a new card. What do I do?
import { Card } from "./Card";
export function Body() {
let imgname = "";
let imgurl = "";
let cardlist = [
{
name: "ca122t1",
url: "https://img.webmd.com/dtmcms/live/webmd/consumer_assets/site_images/article_thumbnails/other/cat_relaxing_on_patio_other/1800x1200_cat_relaxing_on_patio_other.jpg"
},
{
name: "cat2",
url: "https://img.webmd.com/dtmcms/live/webmd/consumer_assets/site_images/article_thumbnails/other/cat_relaxing_on_patio_other/1800x1200_cat_relaxing_on_patio_other.jpg"
}
]
const [list, setList] = React.useState(cardlist);
function handleName(event) {
imgname = event.target.value;
}
function handleURL(event) {
imgurl = event.target.value;
}
function handleSubmit(e) {
let imgToAdd = {name: imgname, url: imgurl}
cardlist.push(imgToAdd)
setList(cardlist)
e.preventDefault()
}
return (
<div>
<form onSubmit={handleSubmit}>
<label>Name</label>
<input type="text" onChange={handleName}/>
<label>Image URL</label>
<input type="text" onChange={handleURL}/>
<input type="submit" value="Submit"/>
</form>
<ul>
{list.map((item) => (
<Card name={item.name} url={item.url}></Card>
))}
</ul>
</div>
);
}

Never modify state in React directly. Instead, copy the values of the old array into a new one. Also, you should use the old state (list) and not your cardlist variable which is just used for initializing the state.
In ES6 you can simply do
function handleSubmit(e) {
// copy the elements of list into a new array and add the new card
setList([...list, {name: imgname, url: imgurl}]);
e.preventDefault();
}
EDIT:
I just saw that you are also not using state for the imgname and imgurl variable, so this answer should work but fill in empty strings for the new values. Everything that changes in a component should useState:
const [imgname, setImgname] = useState("");
function handleName(event){
setImgname(event.target.value)
}
Also, for controlled inputs, set the value from state to avoid inconsistencies:
<input type="text" onChange={handleName} value={imgname} />

Related

Remove object property inside ReactJs element - using spread syntax

I'm triyng to remove an object property using spread syntax rendering a React component. I wonder if there is a way to achieve without adding to much extra code. I'm using {reset,...inputName}
I have a custom hook (useField) for every input in my form. useField also has a reset function for my reset button. But I want to remove the reset property only for the inputs element.
custom hook useField
export const useField = (type) => {
const [value, setValue] = useState('')
const onChange = (event) => {
setValue(event.target.value)
}
const reset = () => {
setValue('')
}
return {
type,
value,
onChange,
reset
}
}
react form
const MyForm = (props) => {
const content = useField('text')
const author = useField('text')
const info = useField('text')
const handleClick = () => {
content.reset()
author.reset()
info.reset()
}
return (
<div>
<h2>create a new anecdote</h2>
<form onSubmit={handleSubmit}>
<div>
content
<input {reset,...content} />
</div>
<div>
author
<input {reset,...author} />
</div>
<div>
url for more info
<input {reset,...info} />
</div>
<button>create</button>
<input type="reset" value='reset' onClick={handleClick} />
</form>
</div>
)
}
For future reference, what may work for OP are changes similar to below:
const { reset: resetContent, ...content} = useField('text')
const { reset: resetAuthor, ...author} = useField('text')
const { rest: resetInfo, ...info} = useField('text')
const handleClick = () => {
resetContent();
resetAuthor();
resetInfo();
};
.
.
.
<div>
content
<input {...content} />
</div>
.
.
.
Explanation
the object returned from useField is destructured
the reset prop is separated and renamed as resetContent (or resetAuthor, resetInfo, as required)
the rest of the props go into the content variable (or author, info variables, as required)
when rendering in the JSX, the content is used
Thus, effectively the reset prop from useField was 'removed' (technically, it was just separated, though) in the new content object.

React Hooks: State is resetting to empty array even if I use the spread operator, prevState, etc

Simplified Code Sample right here
WORDS:
In short: My items state is resetting to [] with each NEW checkbox clicked and I dont understand why. But instead I want to use the spread operator and useState hooks to push an new item into the array so it's an array of objects.
Current behavior in detail: I'm creating an object and setting it in state using all (and I mean ALL) manner of React useState hooks like this: setItems((prevState) => [...prevState, { [evt.target.value]: evt.target.checked }]); As I check one item it's added and items becomes an array of objects (it being added over and over again is not the problem; I'll add a check for that later). BUT Here's the problem: when I click a NEW checkbox the items array is set back to [] and isnt concatenated with the prev items—even though I'm using prevState, spread operator, an arrow func as a wrapper, and all that jazz.
Desired behavior: Every time I check a checkbox, I want to update items [] to push a new object into it, which represents all items that have ever been checked. Before you say anything about duplicating: I'll add the check to see if an item is already in the array, and just update it if so. And before I add all items to cart, I'll strip all objects with checked = false states.
Can you help me understand what react lifecycle fundamentals I'm missing here; why is this happening? And how can I fix it?
CODE:
Where this is happening:
Simplified version of InputComponent
const InputComponent = ({ type, itemId, handleSearchQuery, onSubmit }) => {
const [items, setItems] = useState([]);
const captureInput = (evt) => {
if (evt.target.type === 'checkbox') {
setItems((prevState) => [...prevState, { [evt.target.value]: evt.target.checked }]);
}
};
const renderCheckbox = () => {
return (
<form>
<input type={type} name={itemId} value={itemId} onChange={setItem} />
<input name={itemId} type='submit' value='Add to Cart' />
</form>
);
};
return (
<div className='input-bar'>
{renderCheckbox()}
</div>
);
};
export default InputComponent;
Where this component is used:
import React from 'react';
import InputComponent from './InputComponent';
import './ResultsRenderer.css';
function ResultsRenderer({ data }) {
const renderListings = () => {
let listings = data ? data.Search : null;
return listings
? listings.map((item) => {
return (
<div className='cart-row'>
<InputComponent type='checkbox' className='cart-checkbox' itemId={item.imdbID} />
<div key={item.imdbID} className={item.imdbID}>
<img src={`${item.Poster}`} alt={item.Title} />
<div>
Title<em>{item.Title}</em>
</div>
<div>{item.Year}</div>
<em>{item.imdbID}</em>
</div>
</div>
);
})
: null;
};
return <>{renderListings()}</>;
}
export default ResultsRenderer;
items state is doing its job perfectly fine, you misunderstood the situation.
you're using items state inside InputComponent and for each listings item there is one InputComponent and each one have their own items, I think you meant to use items state inside ResultsRenderer Component to chase all selected items.
here is the changes you need to do:
const InputComponent = ({ type, itemId, setItems }) => {
const captureInput = (evt) => {
if (evt.target.type === "checkbox") {
setItems((prevState) => [
...prevState,
{ [evt.target.value]: evt.target.checked }
]);
}
};
return (
<div className="input-bar">
<form>
<input
type={type}
name={itemId}
value={itemId}
onChange={captureInput}
/>
<input name={itemId} type="submit" value="Add to Cart" />
</form>
</div>
);
};
export default InputComponent;
function ResultsRenderer() {
const [items, setItems] = useState([]);
useEffect(() => {
console.log(items);
}, [items]);
const renderListings = () => {
let listings = [
{ itemId: 1, title: "Hello" },
{ itemId: 2, title: "World" }
];
return listings
? listings.map((item) => {
return (
<div className="cart-row">
<InputComponent
type="checkbox"
className="cart-checkbox"
itemId={item.itemId}
setItems={setItems}
/>
<div key={item.itemId} className={item.itemId}>
<div>
Title<em>{item.Title}</em>
</div>
</div>
</div>
);
})
: null;
};
return <>{renderListings()}</>;
}
and here is the working demo: https://codesandbox.io/s/boring-cookies-t0g4e?file=/src/InputComponent.jsx

Error dynamically updating an array list in ReactJs

I am learning reactJS and so I am trying my hands on an example. This example has a form textfield that can add an item to an existing array on click of a button. I am having errors here as when I enter a text and click on the button, the array list is not updated except I try to make changes to the text entered in the textfield. This is what I am doing:
import React, { Component } from 'react';
class App extends Component {
constructor(props){
super(props);
this.state = {
currentName : '',
arrays : ['john', 'james', 'timothy']
}
}
render() {
const showNames = this.state.arrays.map((thisName) => {
const values = <li>{thisName}</li>;
return values;
});
const getText = (e) => {
let value = e.target.value;
this.setState({
currentName : value
})
}
const addToUsers = () => {
this.state.arrays.push(this.state.currentName)
}
return (
<div>
<p>Add new name to List</p><br/>
<form>
<input type="text" onChange={getText}/>
<button type="button" onClick={addToUsers}>Add User</button>
</form>
<ul>
{showNames}
</ul>
</div>
);
}
}
export default App;
There are a host of things wrong with this, but your issue is likely that you need to use setState to modify state.
import React, { Component } from 'react';
class App extends Component {
constructor(){
super();
this.state = {
names: ['john', 'james', 'timothy']
}
}
addToUsers = () => {
this.setState(
prevState => ({
names: [...prevState.names, this.input.value]
})
)
}
render() {
const names = this.state.names.map(
(name, index) => <li key={index}>{name}</li>
)
return (
<div>
<p>Add new name to List</p><br/>
<form>
<input type="text" ref={e => this.input = e} />
<button type="button" onClick={this.addToUsers}>Add User</button>
</form>
<ul>
{names}
</ul>
</div>
)
}
}
export default App;
This quick edit changes a few things:
Uses setState for the addToUsers method
Eliminate onChange tracking and pull the name directly from the input when the button is clicked
Move the addToUsers method out to the component class rather than defining it on render
Rename this.state.arrays to this.state.names
Simplify conversion of this.state.names into list items
Set key on array elements (name list items)
Use prevState in setState to avoid race conditions
You need to make sure you update state using the setState method.
When you update arrays you are reaching into the state object and manipulating the data directly instead of using the method.
Instead try something like:
const addToUsers = () => {
const newArray = this.state.arrays.concat([this.state.currentName]);
this.setState({
arrays: newArray
});
}
You probably must add
onChange={getText}.bind(this)
to your functions.
Also change this
const addToUsers = () => {
this.state.arrays.push(this.state.currentName)
}
to this
const addToUsers = () => {
this.setState({here put your variable})
}

React - Update Value Of Input After Submit

I'm trying to make a feature where a user can edit a submitted value. So to be completely clear:
You would enter some text
Click submit and that value will be pushed into an array
You will be able to see your value on the dom
If you made an error, you can click on that input and change the value, also updating the state of that value in the already pushed array.
On a button click, you will update the state and have a newly edited value.
I'm stuck on the part of changing the state of the value of the pushed items in the array.
For example:
If I were to click on the field of 'Bob', edit it and click submit, the value of whatever I changed it to would also change the state of what was originally in my array to the new value.
This is what I have so far:
import React, { Component } from 'react'
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
notes: ['hello', 'bob'],
val: ''
}
}
submit = () => {
const { notes, val } = this.state
notes.push(val)
this.setState({notes})
}
handleEdit = e => {
console.log(e)
}
render() {
return (
<div>
<input
type="text"
onChange={e => this.setState({val: e.target.value})}
/>
<button onClick={this.submit}>Submit</button>
{this.state.notes.map(item => {
return (
<form onSubmit={e => e.preventDefault()}>
<input
type="text"
defaultValue={item}
onChange={e => this.setState({val: e.target.value})}
/>
<button onClick={() => this.handleEdit(item)}>Submit
Change</button>
</form>
)
})}
</div>
)
}
}
Try this kind of thing :
handleEdit = (item) => {
const notes = this.state.notes.slice();
const index = notes.indexOf(item);
notes[index] = this.state.val;
this.setState({
notes
})
}

React.js - input losing focus when rerendering

I am just writing to text input and in onChange event I call setState, so React re-renders my UI. The problem is that the text input always loses focus, so I need to focus it again for each letter :D.
var EditorContainer = React.createClass({
componentDidMount: function () {
$(this.getDOMNode()).slimScroll({height: this.props.height, distance: '4px', size: '8px'});
},
componentDidUpdate: function () {
console.log("zde");
$(this.getDOMNode()).slimScroll({destroy: true}).slimScroll({height: 'auto', distance: '4px', size: '8px'});
},
changeSelectedComponentName: function (e) {
//this.props.editor.selectedComponent.name = $(e.target).val();
this.props.editor.forceUpdate();
},
render: function () {
var style = {
height: this.props.height + 'px'
};
return (
<div className="container" style={style}>
<div className="row">
<div className="col-xs-6">
{this.props.selected ? <h3>{this.props.selected.name}</h3> : ''}
{this.props.selected ? <input type="text" value={this.props.selected.name} onChange={this.changeSelectedComponentName} /> : ''}
</div>
<div className="col-xs-6">
<ComponentTree editor={this.props.editor} components={this.props.components}/>
</div>
</div>
</div>
);
}
});
Without seeing the rest of your code, this is a guess.
When you create a EditorContainer, specify a unique key for the component:
<EditorContainer key="editor1"/>
When a re-rendering occurs, if the same key is seen, this will tell React don't clobber and regenerate the view, instead reuse. Then the focused item should retain focus.
I keep coming back here again and again and always find the solution to my elsewhere at the end.
So, I'll document it here because I know I will forget this again!
The reason input was losing focus in my case was due to the fact that I was re-rendering the input on state change.
Buggy Code:
import React from 'react';
import styled from 'styled-components';
class SuperAwesomeComp extends React.Component {
state = {
email: ''
};
updateEmail = e => {
e.preventDefault();
this.setState({ email: e.target.value });
};
render() {
const Container = styled.div``;
const Input = styled.input``;
return (
<Container>
<Input
type="text"
placeholder="Gimme your email!"
onChange={this.updateEmail}
value={this.state.email}
/>
</Container>
)
}
}
So, the problem is that I always start coding everything at one place to quickly test and later break it all into separate modules.
But, here this strategy backfires because updating the state on input change triggers render function and the focus is lost.
Fix is simple, do the modularization from the beginning, in other words, "Move the Input component out of render function"
Fixed Code
import React from 'react';
import styled from 'styled-components';
const Container = styled.div``;
const Input = styled.input``;
class SuperAwesomeComp extends React.Component {
state = {
email: ''
};
updateEmail = e => {
e.preventDefault();
this.setState({ email: e.target.value });
};
render() {
return (
<Container>
<Input
type="text"
placeholder="Gimme your email!"
onChange={this.updateEmail}
value={this.state.email}
/>
</Container>
)
}
}
Ref. to the solution: https://github.com/styled-components/styled-components/issues/540#issuecomment-283664947
If it's a problem within a react router <Route/> use the render prop instead of component.
<Route path="/user" render={() => <UserPage/>} />
The loss of focus happens because the component prop uses React.createElement each time instead of just re-rendering the changes.
Details here: https://reacttraining.com/react-router/web/api/Route/component
I had the same symptoms with hooks. Yet my problem was defining a component inside the parent.
Wrong:
const Parent =() => {
const Child = () => <p>Child!</p>
return <Child />
}
Right:
const Child = () => <p>Child!</p>
const Parent = () => <Child />
My answer is similar to what #z5h said.
In my case, I used Math.random() to generate a unique key for the component.
I thought the key is only used for triggering a rerender for that particular component rather than re-rendering all the components in that array (I return an array of components in my code). I didn't know it is used for restoring the state after rerendering.
Removing that did the job for me.
Applying the autoFocus attribute to the input element can perform as a workaround in situations where there's only one input that needs to be focused. In that case a key attribute would be unnecessary because it's just one element and furthermore you wouldn't have to worry about breaking the input element into its own component to avoid losing focus on re-render of main component.
What I did was just change the value prop to defaultValue and second change was onChange event to onBlur.
I got the same behavior.
The problem in my code was that i created a nested Array of jsx elements like this:
const example = [
[
<input value={'Test 1'}/>,
<div>Test 2</div>,
<div>Test 3</div>,
]
]
...
render = () => {
return <div>{ example }</div>
}
Every element in this nested Array re-renders each time I updated the parent element. And so the inputs lose there "ref" prop every time
I fixed the Problem with transform the inner array to a react component
(a function with a render function)
const example = [
<myComponentArray />
]
...
render = () => {
return <div>{ example }</div>
}
EDIT:
The same issue appears when i build a nested React.Fragment
const SomeComponent = (props) => (
<React.Fragment>
<label ... />
<input ... />
</React.Fragment>
);
const ParentComponent = (props) => (
<React.Fragment>
<SomeComponent ... />
<div />
</React.Fragment>
);
I solved the same issue deleting the key attribute in the input and his parent elements
// Before
<input
className='invoice_table-input invoice_table-input-sm'
type='number'
key={ Math.random }
defaultValue={pageIndex + 1}
onChange={e => {
const page = e.target.value ? Number(e.target.value) - 1 : 0
gotoPage(page)
}}
/>
// After
<input
className='invoice_table-input invoice_table-input-sm'
type='number'
defaultValue={pageIndex + 1}
onChange={e => {
const page = e.target.value ? Number(e.target.value) - 1 : 0
gotoPage(page)
}}
/>
The answers supplied didn't help me, here was what I did but I had a unique situation.
To clean up the code I tend to use this format until I'm ready to pull the component into another file.
render(){
const MyInput = () => {
return <input onChange={(e)=>this.setState({text: e.target.value}) />
}
return(
<div>
<MyInput />
</div>
)
But this caused it to lose focus, when I put the code directly in the div it worked.
return(
<div>
<input onChange={(e)=>this.setState({text: e.target.value}) />
</div>
)
I don't know why this is, this is the only issue I've had with writing it this way and I do it in most files I have, but if anyone does a similar thing this is why it loses focus.
If the input field is inside another element (i.e., a container element like <div key={"bart"}...><input key={"lisa"}...> ... </input></div>-- the ellipses here indicating omitted code), there must be a unique and constant key on the container element (as well as on the input field). Elsewise, React renders up a brand new container element when child's state is updated rather than merely re-rendering the old container. Logically, only the child element should be updated, but...
I had this problem while trying to write a component that took a bunch of address information. The working code looks like this
// import react, components
import React, { Component } from 'react'
// import various functions
import uuid from "uuid";
// import styles
import "../styles/signUp.css";
export default class Address extends Component {
constructor(props) {
super(props);
this.state = {
address1: "",
address2: "",
address1Key: uuid.v4(),
address2Key: uuid.v4(),
address1HolderKey: uuid.v4(),
address2HolderKey: uuid.v4(),
// omitting state information for additional address fields for brevity
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
event.preventDefault();
this.setState({ [`${event.target.id}`]: event.target.value })
}
render() {
return (
<fieldset>
<div className="labelAndField" key={this.state.address1HolderKey} >
<label className="labelStyle" for="address1">{"Address"}</label>
<input className="inputStyle"
id="address1"
name="address1"
type="text"
label="address1"
placeholder=""
value={this.state.address1}
onChange={this.handleChange}
key={this.state.address1Key} ></input >
</div>
<div className="labelAndField" key={this.state.address2HolderKey} >
<label className="labelStyle" for="address2">{"Address (Cont.)"}</label>
<input className="inputStyle"
id="address2"
name="address2"
type="text"
label="address2"
placeholder=""
key={this.state.address2Key} ></input >
</div>
{/* omitting rest of address fields for brevity */}
</fieldset>
)
}
}
Sharp-eyed readers will note that <fieldset> is a containing element, yet it doesn't require a key. The same holds for <> and <React.Fragment> or even <div> Why? Maybe only the immediate container needs a key. I dunno. As math textbooks say, the explanation is left to the reader as an exercise.
I had this issue and the problem turned out to be that I was using a functional component and linking up with a parent component's state. If I switched to using a class component, the problem went away. Hopefully there is a way around this when using functional components as it's a lot more convenient for simple item renderers et al.
I just ran into this issue and came here for help. Check your CSS! The input field cannot have user-select: none; or it won't work on an iPad.
The core reason is: When React re-render, your previous DOM ref will be invalid. It mean react has change the DOM tree, and you this.refs.input.focus won't work, because the input here doesn't exist anymore.
For me, this was being caused by the search input box being rendered in the same component (called UserList) as the list of search results. So whenever the search results changed, the whole UserList component rerendered, including the input box.
My solution was to create a whole new component called UserListSearch which is separate from UserList. I did not need to set keys on the input fields in UserListSearch for this to work. The render function of my UsersContainer now looks like this:
class UserContainer extends React.Component {
render() {
return (
<div>
<Route
exact
path={this.props.match.url}
render={() => (
<div>
<UserListSearch
handleSearchChange={this.handleSearchChange}
searchTerm={this.state.searchTerm}
/>
<UserList
isLoading={this.state.isLoading}
users={this.props.users}
user={this.state.user}
handleNewUserClick={this.handleNewUserClick}
/>
</div>
)}
/>
</div>
)
}
}
Hopefully this helps someone too.
I switched value prop to defaultValue. That works for me.
...
// before
<input value={myVar} />
// after
<input defaultValue={myVar} />
My problem was that I named my key dynamically with a value of the item, in my case "name" so the key was key={${item.name}-${index}}. So when I wanted to change the input with item.name as the value, they key would also change and therefore react would not recognize that element
included the next code in tag input:
ref={(input) => {
if (input) {
input.focus();
}
}}
Before:
<input
defaultValue={email}
className="form-control"
type="email"
id="email"
name="email"
placeholder={"mail#mail.com"}
maxLength="15"
onChange={(e) => validEmail(e.target.value)}
/>
After:
<input
ref={(input) => {
if (input) {
input.focus();
}
}}
defaultValue={email}
className="form-control"
type="email"
id="email"
name="email"
placeholder={"mail#mail.com"}
maxLength="15"
onChange={(e) => validEmail(e.target.value)}
/>
I had a similar issue, this is fixed it.
const component = () => {
return <input onChange={({target})=>{
setValue(target.vlaue)
}
} />
}
const ThisComponentKeptRefreshingContainer = () => {
return(
<component />
)
}
const ThisContainerDidNot= () => {
return(
<> {component()} </>
)
}
As the code illustrate calling the component child like an element gave that re-rendering effect, however, calling it like a function did not.
hope it helps someone
I had the same problem with an html table in which I have input text lines in a column. inside a loop I read a json object and I create rows in particular I have a column with inputtext.
http://reactkungfu.com/2015/09/react-js-loses-input-focus-on-typing/
I managed to solve it in the following way
import { InputTextComponent } from './InputTextComponent';
//import my inputTextComponent
...
var trElementList = (function (list, tableComponent) {
var trList = [],
trElement = undefined,
trElementCreator = trElementCreator,
employeeElement = undefined;
// iterating through employee list and
// creating row for each employee
for (var x = 0; x < list.length; x++) {
employeeElement = list[x];
var trNomeImpatto = React.createElement('tr', null, <td rowSpan="4"><strong>{employeeElement['NomeTipologiaImpatto'].toUpperCase()}</strong></td>);
trList.push(trNomeImpatto);
trList.push(trElementCreator(employeeElement, 0, x));
trList.push(trElementCreator(employeeElement, 1, x));
trList.push(trElementCreator(employeeElement, 2, x));
} // end of for
return trList; // returns row list
function trElementCreator(obj, field, index) {
var tdList = [],
tdElement = undefined;
//my input text
var inputTextarea = <InputTextComponent
idImpatto={obj['TipologiaImpattoId']}//index
value={obj[columns[field].nota]}//initial value of the input I read from my json data source
noteType={columns[field].nota}
impattiComposite={tableComponent.state.impattiComposite}
//updateImpactCompositeNote={tableComponent.updateImpactCompositeNote}
/>
tdElement = React.createElement('td', { style: null }, inputTextarea);
tdList.push(tdElement);
var trComponent = createClass({
render: function () {
return React.createElement('tr', null, tdList);
}
});
return React.createElement(trComponent);
} // end of trElementCreator
});
...
//my tableComponent
var tableComponent = createClass({
// initial component states will be here
// initialize values
getInitialState: function () {
return {
impattiComposite: [],
serviceId: window.sessionStorage.getItem('serviceId'),
serviceName: window.sessionStorage.getItem('serviceName'),
form_data: [],
successCreation: null,
};
},
//read a json data soure of the web api url
componentDidMount: function () {
this.serverRequest =
$.ajax({
url: Url,
type: 'GET',
contentType: 'application/json',
data: JSON.stringify({ id: this.state.serviceId }),
cache: false,
success: function (response) {
this.setState({ impattiComposite: response.data });
}.bind(this),
error: function (xhr, resp, text) {
// show error to console
console.error('Error', xhr, resp, text)
alert(xhr, resp, text);
}
});
},
render: function () {
...
React.createElement('table', {style:null}, React.createElement('tbody', null,trElementList(this.state.impattiComposite, this),))
...
}
//my input text
var inputTextarea = <InputTextComponent
idImpatto={obj['TipologiaImpattoId']}//index
value={obj[columns[field].nota]}//initial value of the input I read //from my json data source
noteType={columns[field].nota}
impattiComposite={tableComponent.state.impattiComposite}//impattiComposite = my json data source
/>//end my input text
tdElement = React.createElement('td', { style: null }, inputTextarea);
tdList.push(tdElement);//add a component
//./InputTextComponent.js
import React from 'react';
export class InputTextComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
idImpatto: props.idImpatto,
value: props.value,
noteType: props.noteType,
_impattiComposite: props.impattiComposite,
};
this.updateNote = this.updateNote.bind(this);
}
//Update a inpute text with new value insert of the user
updateNote(event) {
this.setState({ value: event.target.value });//update a state of the local componet inputText
var impattiComposite = this.state._impattiComposite;
var index = this.state.idImpatto - 1;
var impatto = impattiComposite[index];
impatto[this.state.noteType] = event.target.value;
this.setState({ _impattiComposite: impattiComposite });//update of the state of the father component (tableComponet)
}
render() {
return (
<input
className="Form-input"
type='text'
value={this.state.value}
onChange={this.updateNote}>
</input>
);
}
}
Simple solution in my case:
<input ref={ref => ref && ref.focus()}
onFocus={(e)=>e.currentTarget.setSelectionRange(e.currentTarget.value.length, e.currentTarget.value.length)}
/>
ref triggers focus, and that triggers onFocus to calculate the end and set the cursor accordingly.
The issue in my case was that the key prop values I was setting on the InputContainer component and the input fields themselves were generated using Math.random(). The non-constant nature of the values made it hard for track to be kept of the input field being edited.
For me I had a text area inside a portal. This text area was loosing focus. My buggy portal implementation was like this:
export const Modal = ({children, onClose}: modelProps) => {
const modalDOM = document.getElementById("modal");
const divRef = useRef(document.createElement('div'));
useEffect(()=>{
const ref = divRef.current;
modalDOM?.appendChild(ref);
return ()=>{
modalDOM?.removeChild(ref);
}
});
const close = (e: React.MouseEvent) => {
e.stopPropagation();
onClose();
};
const handleClick = (e: React.MouseEvent) => {
e.stopPropagation()
}
return (
createPortal(
<div className="modal" onClick={close}>
<div className="modal__close-modal" onClick={close}>x</div>
{children}
</div>,
divRef.current)
)
}
const Parent = ({content: string}: ParentProps) => {
const [content, setContent] = useState<string>(content);
const onChangeFile = (e: React.MouseEvent) => {
setContent(e.currentTarget.value);
}
return (
<Modal>
<textarea
value={content}
onChange={onChangeFile}>
</textarea>
</Modal>
)
}
Turned out following implementation worked correctly, here I am directly attaching modal component to the DOM element.
export const Modal = ({children, onClose}: modelProps) => {
const modalDOM = document.getElementById("modal");
const close = (e: React.MouseEvent) => {
e.stopPropagation();
onClose();
};
return (
createPortal(
<div className="modal" onClick={close}>
<div className="modal__close-modal" onClick={close}>x</div>
{children}
</div>,
modalDOM || document.body)
)
}
Turns out I was binding this to the component which was causing it to rerender.
I figured I'd post it here in case anyone else had this issue.
I had to change
<Field
label="Post Content"
name="text"
component={this.renderField.bind(this)}
/>
To
<Field
label="Post Content"
name="text"
component={this.renderField}
/>
Simple fix since in my case, I didn't actually need this in renderField, but hopefully me posting this will help someone else.
Changing text in the input of some control can cause parent control rerendering in some cases (according to binding to props).
In this case focus will be lost. Editing should not has effect to parent container in DOM.

Categories

Resources