Access to DOM elements by ID in React - javascript

I want to handle disable attribute of input elements by ID in my React app
It's possible with states but number of elements is not fixed, maybe 10 input or 20 or more ...
I've decided to set ID for each input and access to them with ID, for example :
document.getElementById('title-12') ....
So, is it a suitable trick or best practice to handle this issue ?
Performance and clean code is very important for me :-)
Thanks

Oops... my bad. I digged into your discussion and here's a new solution.
That's still correct that React approach is advised so we should use reusable components for inputs (we can have any number of inputs right now). All the input's data are stored in parent component's store as a collection. We map through collection and send properties to each component (in the simplest version - id, isDisabled and disableInput() function).
class Titles extends Component {
constructor(props) {
super(props);
this.state = {
titles: [
{
id: 0,
disabled: true
},
{
id: 1,
disabled: false
}
]
};
}
addNewInput = () => {
const prevList = this.state.titles;
const newItem = {
id: prevList.length,
disabled: false
};
this.setState({ titles: [...prevList, newItem] });
};
disableInput = id => {
const titles = this.state.titles;
titles[id].disabled = !titles[id].disabled;
this.setState({ titles });
};
render() {
return (
<div>
<h1>Titles list</h1>
<form style={{ display: "flex", flexDirection: "column" }}>
{this.state.titles.map(title => (
<Title
key={title.id}
id={title.id}
isDisabled={title.disabled}
disableInput={id => this.disableInput(id)}
/>
))}
</form>
<button onClick={() => this.addNewInput()}>Dodaj nowy</button>
</div>
);
}
}
in Title component we just render the props in to <input> and a button with onClick function that sends id of this component to its parent, where disable attribute's value is being reversed.
const Title = ({ id, isDisabled, disableInput }) => {
return (
<div>
<input
id={id}
type="text"
placeholder={id}
disabled={isDisabled}
/>
<button type="button" onClick={() => disableInput(id)}>
disable input
</button>
</div>
);
};
Working example can be found here.
Please let me know if it works for you.

Related

Hide Child Component Element After onClick using ReactJS

I'm new to React, learning by coding, here i have component A, which has select element with menuItems (all material ui), when user clicks select element and chooses from drop down, right after user has chosen whole component should go display:none, is this possible ? i mean user should not be able to see select element anymore on the page
English is not my mother language, so there might be mistakes.
suggestions/help is appreciated.
component A:
const A: React.FC<AProps> = (props) => {
const handleChange = (e: React.ChangeEvent<{ value: unknown }>) => {
const site = e.target.value as string;
dispatch(changeActiveSite(site));
if (site) {
dispatch(getAnalysers(site));
} else {
dispatch(clearSiteData(site));
}
};
const sites = [
{
ident: "",
name: "None",
},
].concat(sitess);
return (
<React.Fragment>
<FormControl className={classes.formControl}>
<InputLabel id="site-select-input-label">site</InputLabel>
<Select
id="site-select"
value={currentSiteId}
labelId="site-select-input-label"
onChange={(e) => handleChange(e)}
>
{sites.map((site) => {
return (
<MenuItem key={site.ident} value={site.ident}>
{site.name}
</MenuItem>
);
})}
</Select>
</FormControl>
</React.Fragment>
);
};
that component is in component B like this: <div > <A site={site} /> </div>
Let's imagine your MenuItem.js, specifically, its render() and constructor(). You'll want it to be able to be hidden/not-displayed, or visible/displayed. Use a state attribute for hidden to control this, your render will probably look like...
constructor(props) {
super(props);
this.state = {
'hidden':false,
};
}
render () {
if(this.state.hidden) {
return '';
}
return (
<div
onClick={(e) => this.handleOnClick(e)}
>
{this.props.value}
</div>
);
}
Notice I also added a handleOnClick(e) handler up above! That will simply call this.setState({'hidden':true}), so like...
handleOnClick(e) {
this.setState({'hidden':true});
}
I have an answer to a similar question elsewhere, if it might also help: How to set one component's state from another component in React

React - state doesn't update in class component even though the message printed on the screen changes

I have an App component which holds an input. Every time I type in the input, the value of the input updates and a Message component prints a different message, depending on how long the input is. At the same time, a third component called Character print to the screen every letter of the string, individually. The desired behavior is that when I click on one of the letters, it gets removed from the string, the new string is displayed on the screen and the input also gets updated with the new string.
I used some console.logs to debug and everything seems to be happening as expected, until the last step when I am trying to update the state, but for some reason, it doesn't get updated.
class App extends React.Component {
constructor(props) {
super(props);
this.state = { text: "" };
}
render() {
const handleUpdateText = event => {
this.setState({
text: event.target.value
});
};
const inputLength = this.state.text.length;
const toArray = this.state.text.split("");
const handleDeleteLetter = index => {
toArray.splice(index, 1);
console.log(toArray);
const updatedArray = toArray.join("");
console.log(updatedArray);
this.setState({ text: updatedArray });
console.log(this.state.text);
};
return (
<>
<input type="text" onChange={handleUpdateText} />
<Message inputLength={inputLength} />
{toArray.map((letter, index) => (
<Character
key={index}
theLetter={letter}
deleteLetter={() => handleDeleteLetter(index)}
/>
))}
</>
);
}
}
class Message extends React.Component {
render() {
const { inputLength } = this.props;
let codeToPrint = "The text is long enough!";
if (inputLength <= 5) {
codeToPrint = "The text is not long enough!";
}
return <p>{codeToPrint}</p>;
}
}
class Character extends React.Component {
render() {
const { theLetter, deleteLetter } = this.props;
return (
<div
style={{
display: "inline-block",
padding: "16px",
textAlign: "center",
margin: "16px",
backgroundColor: "tomato"
}}
onClick={deleteLetter}
>
{theLetter}
</div>
);
}
}
The complete code is here:
https://codesandbox.io/s/react-the-complete-guide-assignment-2-list-conditionals-e6ty6?file=/src/App.js:51-1007
I don't really understand what am I doing wrong and I have a feeling is somehow related to a life cycle method. Any answer could help. Thank you.
State is getting updated, you just need to pass value prop to the input so that input's value can be in sync with your state
<input type="text" value={this.state.text} onChange={handleUpdateText} />
And you're not seeing updated state just after setting it because setState is asynchronous. That's why the console statement just after the setState statement shows the previous value.
Also you should move functions out of your render method, because everytime your component re-renders, new functions would be created. You can declare them as class properties and pass their reference
handleUpdateText = event => {
this.setState({
text: event.target.value
});
};
render() {
.......
return (
<>
<input type="text" onChange={this.handleUpdateText} />

How to filter data, using checkboxes, without using redux

I am using React to display book titles that I want filtered by category. I want the titles filtered once a checkbox next to the category name is clicked. I am not using a submit button.
I am somewhat new to React and read the documentation about "lifting state," but I haven't been able to get that to work. I have not yet read the Hooks or Context API documentation. Perhaps that's the solution, but it seems what I'm doing isn't complex enough for that...maybe not?
class Checkbox extends Component {
state = {
checked: false
}
handleClick = (e) => {
this.setState(() => ({ checked: !this.state.checked }))
}
render() {
const name = this.props.name;
return (
<label className="form__group">
<input type="checkbox" checked={this.state.checked} onChange={this.handleClick} className="form__input" />
<span className="form__faux-input"></span>
<span className="form__label">{name}</span>
</label>
)
}
}
function Sidebar({ categories }) {
return (
<div className="sidebar">
<div className="controls">
<div className="filter">
<h2 className="filter__heading">Filter By Category</h2>
<form className="filter-form">
{!categories
? <Spinner />
: categories.map((item) => (
<Checkbox key={item} name={item} />
))
}
<div className="form__group">
<button className="btn btn--rectangle btn--green">
<span className="btn-wrapper">Reset</span>
</button>
</div>
</form>
</div>
</div>
</div>
);
}
class App extends Component {
state = {
books: null,
categories: null
}
async componentDidMount() {
const { books, categories } = await getBooks();
this.setState(() => ({
books: books,
categories: categories
}));
}
render() {
const { books } = this.state;
const { categories } = this.state;
return (
<div className="App">
<Header />
<main className="main">
<div className="uiContainer">
<Sidebar
categories={categories}
/>
{!books
? <Spinner />
: <Card books={books} />
}
</div>
</main>
</div>
);
}
}
I dont 100% understand the question, but if you want to make a section like
[x] cats
[x] dogs
[ ] rabbits // dont show rabbits
Then you can keep the selection and the result part in one react element, if you dont understand the 'lifting state up' section
the state should contain an array of objects like this:
[{
allow: true,
title: 'cat'
},
{allow: false, title: 'rabbit'}]
To update the list use something like this:
this.state.map(({title, allow}) => (
<div>
<TickBox onClick={() => this.toggleAnimal(title)} value={allow}/>
<p>{animalName}</p>
</div>
)
toggleAnimal function should find the animal using the title, and update the state
Then you can filter out all the not allowed animals
this.state
.filter(animal => animal.allowed)
.map(a => <p>{a.title}</p>)
lifting state up
At this point you have 1 component, and the render function looks like this:
<h1>Please select the animals</h1>
{
animals.map(_ => <div><tickbox onClick={() => this.handleToggle(title)} /><title></div>)
}
<h1>Here are the filtered animals</h1>
{
animals.filter(a => a.allow).map(animal => animal.title).map(/* to JSX */)
}
It would be prettier and more responsive if the root component would look like this:
render () {
<SelectAnimals toggle={handleToggle} animals={this.state} />
<ShowFilteredAnimals animals={this.state} />
}
handleToggle (title) {
this.setState(...)
}
As so can see, the SelectAnimals gets a function as an argument, it can communicate with it's parent, by calling props.toggle (with the title as argument)
So SelectAnimals would look like this:
props.animals.map(animal => (
<div>
<TickBox onClick=(() => {props.toggle(animal.title)}) /> // HERE
<p>{animal.title}</p>
</div>
))
So when the tick-box fires a click event, it calls an arrow func. that calls props.toggle function with the title
In the parent of SelectAnimals, the parent element binds a handler function to SelectAnimals.toggle like this:
handleToggle (title) { // the child element called this function, it just got copied
}
PS: I made some renames in my code the handleToggle function can be the same as toggleAnimals
The parent component App needs to be able to tell Card what the selected category is, assuming Card is where the list renders.
To do that, you can:
1) create a callback function inside <App>:
_setCurrentCategory(selection) {
this.setState({currentCategory: selection})
}
2) pass it to <Checkbox /> as a prop and use it in an onChange:
class Checkbox extends Component {
render() {
const {name, setCurrentCategoryCallback } = this.props
return (
<label className="form__group">
<input
type="checkbox"
onChange={() => setCurrentCategoryCallback(name)}
className="form__input"
/>
<span className="form__faux-input"></span>
<span className="form__label">{name}</span>
</label>
)
}
}
.. this will change the state in the parent so that you can then
2) then pass the state from <App /> to <Card />:
<Card
currentCategory={this.state.currentCategory}
books={books}
/>
^^ assuming that this is where the filtered list will render. Inside the Card component, you can filter/order then render the list as you please since it now has both the list of books, and the currently selected category.
This is very loosely coded, but hopefully you get the idea!
also, when deconstructing you don't need to do this:
const { books } = this.state;
const { categories } = this.state;
you can instead do this: const { books, categories} = this.state since they are both coming from state :)

(ReactJS) List of items doesn't get updated on page, even though database gets updated

I have this single page application that uses firebase and reactjs/nodejs that updates/removes/adds html code (body and description). The functions work fine and the data gets updated accordingly in the database. However, the list doesn't refresh once I do any function (only if I press F5 manually). I want the list to dynamically change as I add/edit/delete any of the content. How can I do this?
Here's my code:
HTML.js:
const updateByPropertyName = (propertyName, value) => () => ({
[propertyName]: value,
});
class HTML extends Component {
constructor(props) {
super(props);
this.state = {
html: []
};
}
componentDidMount() {
db.onceGetHTML().then(snapshot =>
this.setState(() => ({ html: snapshot.val() }))
);
}
render() {
const { html } = this.state;
const { description } = this.state;
const { body } = this.state;
return (
<div>
<h1>Home</h1>
<p>The Home Page is accessible by every signed in user.</p>
<input value={description}
onChange={event => this.setState(updateByPropertyName('description', event.target.value))}
type="text"
placeholder="Description..."
/>
<input value={body}
onChange={event => this.setState(updateByPropertyName('body', event.target.value))}
type="text"
placeholder="Body..."
/>
<button onClick={() => addHTML(description, body)}>Add Content</button>
{!!html && <HTMLList html={html} />}
</div>
);
}
}
These are all in one file, I just split them to make it easier to read (HTML.js):
function addHTML(description, body, callback) {
addAnHTML(description, body);
}
And here's the 2nd class in the same file that is responsible for displaying the list of items:
class HTMLList extends Component {
constructor(props) {
super(props);
this.state = {
BODY: '',
desc: '',
html: ''
};
}
render() {
const { html } = this.props;
const { desc } = this.state;
const { BODY } = this.state;
return (
<div>
<h2>List of HTML available:</h2>
{Object.keys(html).map((key, index) =>
<div>
{index + 1}.
{html[key].description}
<img src="http://www.stilltimecollection.co.uk/images/english/b_delete.gif" onClick={() => deleteHTML(key)} />
<Popup trigger={<img src="https://www.faktorzehn.org/de/wp-content/uploads/sites/2/2015/03/f10-org-new_3_6_0-edit.gif" />
} position="right center">
<div>
<input value={desc}
onChange={event => this.setState(updateByPropertyName('desc', event.target.value))}
type="text"
placeholder="Descripton.."
/>
<input value={BODY}
onChange={event => this.setState(updateByPropertyName('BODY', event.target.value))}
type="text"
placeholder="Body..."
/>
<button onClick={() => updateHTML(key, desc, BODY)}>Update Content</button>
</div>
</Popup>
<br></br>
</div>
)}
</div>
);
}
}
The function addAnHTML is in a different file where the database is synchronized:
export const addAnHTML = (description, body) => {
var html =
{
description: description,
body: body,
created_at: format.asString(),
updated_at: ""
}
db.ref('Content').push(html);
alert("Content Added");
}
My page looks like this:
https://preview.ibb.co/fTwiaT/Untitled.png
My database looks like this (added in db but not dynamically):
https://image.ibb.co/nNEapo/database.png
EDIT: so here's the edit on the functions I use:
export const onceGetHTML = () =>
db.ref('Content').once('value');
export const addAnHTML = (description, body) => {
console.log(description);
console.log(body);
var html =
{
description: description,
body: body,
created_at: format.asString(),
updated_at: ""
}
db.ref('Content').push(html);
}
and the edited add function in my class looks like this:
addContent(description, body) {
this.setState({
html: [
...this.state.html,
{
description: this.state.description,
body: this.state.body
}
]
});
addAnHTML(this.state.description,this.state.body);
}
Snapshot.val() contains all the child values of my "Content" parent:
https://preview.ibb.co/jetOc8/edit.png
I will showcase how to correctly map your data to the DOM for the "add" functionality.
Here is the modified HTML class
export class HTML extends Component {
constructor(props) {
super(props);
this.state = {
html: [],
// if you keep your user inputted data in the DOM state, it's good to initialize them first, otherwise your component will suddenly change from an uncontrolled component to a controlled component.
description: "",
body: ""
};
}
componentDidMount() {
// EDIT: code here for initializing `html`
db.onceGetHTML().then(snapshot =>
// EDIT: it's simpler to just call setState with the new state object you want
this.setState({ html: snapshot.val()})
);
}
// use class methods to update your DOM state, because inside these methods
// also don't forget to call your API here
addContent(description, body) {
// EDIT: make sure to make your call to the db for adding an entry
this.setState({
// please keep in mind that you should NEVER mutate your state, so in here I'm using the spread operator to create a new array instance
// this is appending a new entry to the old "html"
html: [
...this.state.html,
{
description: this.state.description,
body: this.state.body
}
]
});
}
// use this to update the state for each input field
updateByPropertyName(property, e) {
this.setState({
[property]: e.target.value
});
}
render() {
const { html } = this.state;
const { description } = this.state;
const { body } = this.state;
return (
<div>
<h1>Home</h1>
<p>The Home Page is accessible by every signed in user.</p>
<input
value={description}
onChange={this.updateByPropertyName.bind(this, "description")}
type="text"
placeholder="Description..."
/>
<input
value={body}
onChange={this.updateByPropertyName.bind(this, "body")}
type="text"
placeholder="Body..."
/>
// this onClick event will call "this.setState(...)" which will trigger the re-render (or the "refresh" you are looking for)
<button onClick={this.addContent.bind(this)}>Add Content</button>
{!!html && <HTMLList html={html} />}
</div>
);
}
}
This will be enough for your successful re-render.
On a side note, it's also useful to index your elements in the list. It's really useful for React to render efficiently. For that, you can modify your HTMList class like so:
//...
<h2>List of HTML available:</h2>
{Object.keys(html).map((key, index) => (
// using the index parameter supported in the .map callback it's okay
// because it makes your DOM list nodes predictable and easy for React to re-render
<div key={index}>
{index + 1}.
{html[key].description}
<img
//...
For the update and delete you can follow the same pattern, only change the state (in which you hold the html object) accordingly.
You can use these readings for the stuff I just explained above
controlled vs uncontrolled components
handling lists components in React

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