I have a sorted list of divs. Within these divs, there are a few input fields. The value of these fields are maintained in the state - so every key press triggers a re-render. Everything works as expected, except for the fact that the input loses focus after each key press. See below:

I feel like I'm close to understanding this - but just can't figure out how to make it work. I've read through the issues, and it feels similar to #49. I know you have the shouldCancelStart and distance parameters (with shouldCancelStart defaulting to true for inputs, textareas, etc). But, it doesn't seem that disabling the drag is the problem (and indeed, perhaps the fact that I have a dragHandle makes these irrelevant).
I also thought that if I make sure everything in the SortableElement has a key, it would re-render and see that it was unchanged, but focus is still lost. Any ideas?
Thanks so much for your work on this project.
Hey @isTravis, try removing all instances of key, including in the elements inside your SortableElement, you shouldn't need those and they're probably what's causing the issues you're seeing. This issue is generally caused by having shifting keys, which would cause your input to be removed from the DOM and re-inserted, resulting in the loss of focus you're experiencing.
I put together a basic example here with what you seem to be describing, and all seems well: https://jsfiddle.net/vr2Lh1sm/
Thanks so much for the example fiddle! I removed all of the keys I had manually inserted, but still no luck on my end. Looking at your example though, I'm more sure this is something strange about my setup rather than a library bug.
I'm pulling my list of items from props rather than state - as I have a redux action that fires on each drag event. I can't see why the SortableList would care about this though - it just sees an array, regardless of where it's pulled from.
I'll keep poking at it, and if I can't figure out anything, I'll post a minimal version of my setup and see if that helps in the debugging.
Thanks so much!
@isTravis did you find anything? I have the same problem and can't find why it happen (keys are ok)
Unfortunately, I haven't found anything yet. I can't see anything shady that I'm doing besides using props (and having multiple renders fire) on each update. To make it work in the meantime though, I just wound up doing the unspeakable - grabbing the values by their ids when needed:
const labelUpdates = {
title: document.getElementById('editTitle').value,
description: document.getElementById('editDescription').value
};
this.props.dispatch(submitForm( labelUpdates);
<label>
Title
<input type="text" id={'editTitle'} defaultValue={collection.title} />
</label>
<label>
Description
<textarea type="text" id={'editDescription'} defaultValue={collection.description} />
</label>
This works because I'm not calling setState on every keystroke. This makes it so the component isn't re-rendering and the focus is maintained.
Of course, if you _need_ to update the state on every keystroke (to show a character count or validate or something) - then I think you'll still have the same problem.
@isTravis thx for the feedback :)
Ok, i found why this happened in my case.
I was creating the SortableItem and SortableList inside my render method (before return) to make quick lib testing.
In this case, classes was created at each render and in this case React reconciliation process discard and replace dom.
So i just needed to use same SortableItem and SortableList classes at each render to avoid this.
That makes sense! I'm also creating the SortableItem and SortableList components in my render method because the depend on a handful of props that I didn't want to send all over the place. Shame on me for bad code structure :)
This resolves my issue - so closing the issue. Though it may be a useful note somewhere in the docs that the sortable components can't be regenerated on each render.
@gouxlord you saved my life
@gouxlord Thanks so much for coming back and commenting on your solution to the problem. Legend. 馃憤
@gouxlord
just needed to use same SortableItem and SortableList classes at each render to avoid this.
There is some sample code?
Please give me an example code :)
Considering this example (my render method):
render() {
const SortableItem = SortableElement(({value, sortIndex}) =>
<li className="SortableItem">
<input/>
</li>
);
const SortableList = SortableContainer(({items}) => {
return (
<ul className="SortableList">
{items.map((value, index) => (
<SortableItem key={`item-${index}`} index={index} value={value} sortIndex={index}/>
))}
</ul>
);
});
return (
<SortableList items={this.state.items} onSortEnd={this.onSortEnd} helperClass="SortableHelper"/>
);
}
What should I change.
I don't have my project anymore so here is how you have to do, based on your example (not tested)
const SortableItem = SortableElement(({value, sortIndex}) =>
<li className="SortableItem">
<input/>
</li>
);
const SortableList = SortableContainer(({items}) => {
return (
<ul className="SortableList">
{items.map((value, index) => (
<SortableItem key={`item-${index}`} index={index} value={value} sortIndex={index}/>
))}
</ul>
);
});
class Toto extends Component {
render() {
return (
<SortableList items={this.state.items} onSortEnd={this.onSortEnd} helperClass="SortableHelper"/>
);
}
}
As far as i remember, @gouxlord is correct, you need to remove the <SortableList/> and <SortableItem/> outside of the render function to prevent it being re-instantiated on each render.
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>
);
}
}
@gouxlord Thank you very much
Thank you all! In my case it's functional component and I had to move container/element out of it and it solved the issue.
Thank you all! In my case it's functional component and I had to move container/element out of it and it solved the issue.
Hey! Can you please quote an example of how you solved it for a functional component? I have been trying it for a while now but doesn't seem to work. Any help would be appreciated. Thank you :)
Thank you all! In my case it's functional component and I had to move container/element out of it and it solved the issue.
Hey! Can you please quote an example of how you solved it for a functional component? I have been trying it for a while now but doesn't seem to work. Any help would be appreciated. Thank you :)
Hello, sorry I don't have an example on hand but IIRC I had a large end-result functional component that returns the SortableContainer and each SortableElement within the return.
Essentially what gouxlord and srsholmes mentioned above. Your component should return another component that implements SortableContainer and its SortableElement. Using the above example, my mistake was having the Toto component returning both SortableContainer and SortableElement directly.
Thank you all! In my case it's functional component and I had to move container/element out of it and it solved the issue.
Hey! Can you please quote an example of how you solved it for a functional component? I have been trying it for a while now but doesn't seem to work. Any help would be appreciated. Thank you :)
Hello, sorry I don't have an example on hand but IIRC I had a large end-result functional component that returns the SortableContainer and each SortableElement within the return.
Essentially what gouxlord and srsholmes mentioned above. Your component should return another component that implements SortableContainer and its SortableElement. Using the above example, my mistake was having the Toto component returning both SortableContainer and SortableElement directly.
Thank you so much for replying :) I had actually done that way from the very beginning. I had created a component which had SortableContainer and SortableElement implementation. My main component was returning this component. But no luck. I have now moved to react-beautiful-dnd. It works well for me. Thanks, again :)
Hello @jeremycy and @prateeksha-nf
Idk maybe it's late, but.. this is an example of how to solve this problem in a FUNCTIONAL COMPONENT case.
You just need to take 'em out of the main function:
const SortableItem = SortableElement(({value, sortIndex}) =>
<li className="SortableItem">
<input/>
</li>
);
const SortableList = SortableContainer(({items}) => {
return (
<ul className="SortableList">
{items.map((value, index) => (
<SortableItem key={`item-${index}`} index={index} value={value} sortIndex={index}/>
))}
</ul>
);
});
function MyApp() {
const [items,setItems] = useState(["foo","bar"]);
const onSortEnd = ({oldIndex, newIndex}) => {
// ...
}
return (
<SortableList items={items} onSortEnd={onSortEnd} helperClass="sortable-helper"/>
);
}
export default MyApp;
Thank you @gouxlord for the inspiration.
Most helpful comment
Ok, i found why this happened in my case.
I was creating the SortableItem and SortableList inside my render method (before return) to make quick lib testing.
In this case, classes was created at each render and in this case React reconciliation process discard and replace dom.
So i just needed to use same SortableItem and SortableList classes at each render to avoid this.