React-Native Prevent keyboard dismiss on state update - reactjs

I have a button that is disabled if a state is false and the state is change if some conditions are not met on each item in an array, one of the condition is that a textfield should contain something, I check for that condition each time the text change. The condition work but each time I change the state the keyboard is dismiss. ie: each time I enter or remove the first letter of the word.
const [canSave, setCanSave] = useState<boolean>(true);
//this function is called each time one of the textfield is changed
const updateCanSave = () => {
for (let i = 0; i < currentSteps.length; i++) {
const step = currentSteps[i];
if (
(step.name.length > 0 && step.media === undefined) ||
(step.name === "" && step.media !== undefined)
) {
setCanSave(false);
break;
}
setCanSave(true);
}
};
return (
//the flatList renderItem contains many textfield and imagePicker
<FlatList
data={array}
...
ListFooterComponent={
<Button on Press={save} disable={!canSave}>Save</Button>
}
/>
)

Though you cannot do that but there are ways to go through it.
1) you can set autoFocus={true} for the input field. But, this is a little buggy. Keyboard stills closes after every change but opens just after that very quickly but not so good.
2) create some other dict (other than state) and store the value of different input fields in that and change the state when the input field loses focus (you can check that by using onBlur prop of input field).

Related

Keen Slider React Component Always One Step Behind

I installed the keen-slider library in my React project, and used the code from the App.js file in this example to set up a slider with page dots and navigation arrows. I am trying to modify that code, by passing in an array of React components, the size of which can be changed when the user selects or deselects options.
The problem is, the slider's dot count and arrow configuration always lags one step behind. If I move from 1 (default) to 2 pages selected, the rendered dot count stays at 1. When I increase to 3, it moves to 2. If I then decrease to 2, it goes to 3. It only catches up if I interact with the slider. In my App component's return, I place the slider as {keenSlider(outputComponentArray)}. To get outputComponentArray, I have some divs with onClick functions that toggle each page type's selected state. This array:
var selectedResultsConfig = [
['Proposal', outputs.proposal, resultSelectorProposal, setResultSelectorProposal],
['Map', outputs.map, resultSelectorMap, setResultSelectorMap],
['Front Page', outputs.frontPage, resultSelectorFrontPage, setResultSelectorFrontPage],
['Collage', outputs.collage, resultSelectorCollage, setResultSelectorCollage],
['Price Letter', outputs.priceLetter, resultSelectorPriceLetter, setResultSelectorPriceLetter],
['Line Items', outputs.lineItems, resultSelectorLineItems, setResultSelectorLineItems]
]
establishes what name, page component (in the 'outputs' object), and toggle state/setting function correspond to each other, then these buttons are rendered with .map on this array, like so:
{selectedResultsConfig.map((item, index) => {
return <>
{(index === 0) ? null : <> </>}
<div className={item[2] ? 'resultSelectorButton selectedButton' : 'resultSelectorButton'} onClick={() => { resultSelectToggle(item[0]) }}>
<Icon path={item[2] ? mdiCheckboxMarked : mdiCheckboxBlankOutline} size={1} color='#ecd670' />
<h2>{item[0]}</h2>
</div>
</>
})}
and their onClick function does the toggling like this:
function resultSelectToggle(button) {
if (screen === 'proposals') {
for (let i = 0; i < selectedResultsConfig.length; i++) {
if (button === selectedResultsConfig[i][0]) {
selectedResultsConfig[i][3](!selectedResultsConfig[i][2])
}
}
}
}
and then I have a useEffect hook that goes off after those toggles and sets up the final component array, which is fed to keen-slider:
//after the result selector button is toggled
useEffect(() => {
var tempComponentArray = [];
if (screen === 'proposals') {
for (let i = 0; i < selectedResultsConfig.length; i++) {
if (selectedResultsConfig[i][2]) {
tempComponentArray.push(selectedResultsConfig[i][1])
}
}
}
setOutputComponentArray(tempComponentArray);
}, [resultSelectorProposal, resultSelectorMap, resultSelectorFrontPage, resultSelectorCollage, resultSelectorPriceLetter, screen])
I'm not the most experienced with React, and I already know there are better ways of doing some of this, but it's not clear to me what is causing my issue. I was having a similar issue once that was fixed with useEffect, but I've already implemented that here. Any help would be appreciated, thanks.
I have made significant modifications to my original code to simplify it, and I originally passed keenSlider a functional component, but still the problem persists.

unable to edit pre populated textboxes using react

I have a form that has textboxes that are prepopulated from an WebAPI. When I try to delete the text in the textbox to make a change it doesn't delete the prepopulate text. If I try to type over the text, I can see only the first letter of the word I'm typing in the console, but nothing changes on the UI: It' like the textbox is in readonly mode WHICH IT IS NOT
const Details = () => {
const [ server, setServer] = useState([]);
useEffect(() = > {
getServerNames();
}
const getServerName = async() => {
//gets the list of server and their details from the API
}
const serverNameChange = (e) => {
setServer(e.target.value);
}
return (
<div>
{ details.map((data) => {
<input type="text" name="server" onChange={serverNameChange} value={data.serverName} />
))}
</div>
)
};
What am I missing to allow the users to edit the textbox? The textbox is prepopulated with data, however, it can be changed. This is only happening on textboxes that are prepopulated. I don't want to click an Edit button, I want to give the user the ability to make a change in the textbox and then save it.
That might be due to the fact, that data.serverName never changes. It’s basically a static value. If you set the value of an Input, you have to handle the changes (when typing) in the onchange event.
From what I assume, according to your code is that you have multiple input boxes with preloaded values in them and you want to change your serverName if one of them get changed by the value that is in the textinput.
If so, map your details into a state variable:
const [serverNames, setServerNames] = useState(details.map( data => data.serverName));
Map the inputs from your state variable like so:
{serverNames.map((name,index) => {
< input type="text" name="server" onChange={(e) => {updateServerState(e, index)}} value={serverNames[index]} />
}
}
And your updateServerState method looks like that:
updateServerState(e, index) {
let myStateData = [...serverNames];
myStateData[index] = e.target.value;
setServerNames(myStateData);
setServer(e.target.value);
}
Caution: I haven‘t tested the code, just wrote it down. But that should give you an idea of how to solve your issue.
TL;DR; Never use non-state variables for a dynamic value.

Control Focus of Multiple FormControl Fields

I have a mapped list of input fields:
<FormControl
name={row_index}
value={barcode.barcode}
placeholder="Barcode - then Enter"
onChange={this.onChange}
onKeyPress={this._handleKeyPress}
disabled={barcode.submitted}
/>
I am currently using onKeyPress to handle submit:
_handleKeyPress = (e) => {
if (e.key === 'Enter') {
const name = e.target.name;
const barcodes = this.state.barcodes;
const this_barcode = barcodes[name];
let apiFormatted = {"barcode": this_barcode.barcode, "uid": this.props.currentSession}
this.postBarcodeAPI(apiFormatted, name)
}
}
I am attempting to focus on the next input field after the current one is successfully submitted. React documentation has an example for manually setting focus on a single input field using ref={(input) => { this.textInput = input; }} />. I have tried using this[‘textInput’+‘1’].focus() (using computed property names, but am getting an error that function is invalid.
EDIT
Per Chase's answer, I am linking to the autofocus documentation, although it doesn't work in this case.
https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement/autofocus
My working solution:
const focusing = index === lastSubmittedIndex + 1 ? true : false;
const refText = focusing || index === 0 ? input => input && input.focus() : null;
<FormControl
name={row_index}
value={barcode.barcode}
placeholder="Barcode - then Enter"
onChange={this.onChange}
onKeyPress={this._handleKeyPress}
disabled={barcode.submitted || barcode.apiCalling}
inputRef={refText}
/>
What I corrected in my code:
1) I am supposed to use inputRef instead of ref for Bootstrap's FormControl component, see here.
2) I am using ilya-semenov's very neat code.
Update
I have other buttons on the page, when user presses them and is at bottom of page, page jumps up to top. Not sure why.
Unless you've set a ref with the key textInput1 then this won't work. Another suggestion would be to move all of the inputs into a separate component and then you can use this.props.children to traverse all your inputs and grab the input at whatever position you want.
EDIT:
You can also use the autoFocus prop to determine if an input should be focused or not.

Re-rendering state in Reactjs not working as expected

I have a parent component in React that holds the state of the application (I'm building a filterable table). In the state I have a the filters gathered by some inputs, the state looks like this filters: [{"filter1": "value1"}, {"filter2": "value2"}, { }, { }, ...]. The filters inputs are build in a child component FilterBar that receives the filters array as a propertie <FilterBar filters={this.state.filters}/>.
So when the user writes something in the input field it updates the state of the filters (parent) according to the field name and the new input value. So far everything works as I expected. But now I want to implement a button to clean the filters, so when the user click on it the state of the filters become empty (" " string for each filter value). I success on it, when I click the button it setState as I expected, and the table is updated as I wanted. But I'm doing some mistake, the input fields receive the new filters array properties but it doesn't clean the input text (so if I had a word or letter in the input text, it still there after the rerender, and I want the input empty as the state).
Here is the parent function to clean the filters (and I realize that it works as expected):
cleanFilters() {
let filters = this.state.filters.slice();
let f;
for (f in filters) {
filters[f]['value']="";
}
this.setState({filters: filters}, function(){this.updateData();});
}
And here is the function that renders the text inputs on the FilterBar component:
renderFilterInputs() {
var filters = [];
for (let i=0; i < this.props.totalFilters; i++) {
filters.push(<td key={i}><input type='text' className="form-control" defaultValue={this.props.filtersApplied[i].value} onChange={(evt) => this.handleFilterChange(evt, i)} /></td>);
}
return filters;
}
render() {
return (
<tr key="0" className="filters-bar">
{this.renderFilterInputs()}
</tr>
);
}
I don't understand why the text input still save the text when I click the button because it rerenders again and the filter state is empty so the defaultValue of that inputs should be ("") empty. Thanks you
Because you are using defaultValue not value means uncontrolled component. Default value will assign the initial value (default value) to input element only, it will not update the value.
Solution:
Since you are using onChange function with input element and updating the parent state also, so use controlled input field, use value property instead of defaultValue.
value = {this.props.filtersApplied[i].value}

Stop cursor jumping when formatting number in React

I have an input field on my react component that shows the line price for an item (two decimal places with thousands separators). I want the value shown to be in money format when the component first renders and also to be kept in money format as user types in the field.
At the moment I have the following code in my component:
var React = require('react');
import accounting from 'accounting';
MoneyInput = React.createClass({
propTypes: {
name: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired,
value: React.PropTypes.number,
error: React.PropTypes.string,
},
onChange(event) {
// get rid of any money formatting
event.target.value = accounting.unformat(event.target.value);
// pass the value on
this.props.onChange(event);
},
getValue() {
return accounting.formatNumber(this.props.value, 2)
},
render() {
return (
<div className="field">
<input type="text"
name={this.props.name}
className="form-control"
value={this.getValue()}
onChange={this.onChange} />
<div className="input">{this.props.error}</div>
</div>
);
}
});
module.exports = MoneyInput;
That code displays the data correctly formatted, but every time I enter a value the cursor jumps to the end of the number.
I understand why that's happening (I think) and I've read several questions here related to not losing cursor position in JavaScript (here and here for example).
My question is what's the best way to deal with this in React?
I think that ideally I wouldn't want to store the cursor position in state (e.g. I would want these to be Presentation Components in Dan Abramov syntax) so is there another way?
An easy solution for losing cursor/caret position in the React's <input /> field that's being formatted is to manage the position yourself:
onChange(event) {
const caret = event.target.selectionStart
const element = event.target
window.requestAnimationFrame(() => {
element.selectionStart = caret
element.selectionEnd = caret
})
// your code
}
The reason your cursor position resets is because React does not know what kinds of changes you are performing (what if you are changing the text completely to something shorter or longer?) and you are now responsible for controlling the caret position.
Example: On one of my input textfields I auto-replace the three dots (...) with an ellipsis. The former is three-characters-long string, while the latter is just one. Although React would know what the end result would look like, it would not know where to put the cursor anymore as there no one definite logical answer.
onKeyUp(ev) {
const cardNumber = "8318 3712 31"
const selectionStart = ev.target.selectionStart; // save the cursor position before cursor jump
this.setState({ cardNumber, }, () => {
ev.target.setSelectionRange(selectionStart, selectionStart); // set the cursor position on setState callback handler
});
}
I think we can do this at a DOM level.
What I did was provided id to the input field.
There is a property selectionEnd in the input element.
What you can do is just get the input element in the normalize function and get its selectionEnd property
const selectionEnd=inputElm &&inputElem.selectionEnd?inputElm.selectionEnd:0;
And since the problem is only while we press the back button. We add a condition as follows
if(result.length<previousValue.length){
inputElm.setSelectionRange(selectionEnd, selectionEnd)
}
But since this value will be set after we return from the function and the returned value will again be set pushing the cursor to the end, we return just add a settimeout.
setTimeout(() => {
if(result.length<previousValue.length){
inputElm.setSelectionRange(selectionEnd, selectionEnd)
}
}, 50);
I just faced this problem today and seems like a timeout of 50 is sufficient.
And if you want to handle the case of user adding the data in the middle. The following code seems to be working good.
if(result.length<previousValue.length){
inputElm.setSelectionRange(selectionEnd, selectionEnd)
} else if(selectionEnd!==result.length){ // result being the computed value
inputElm.setSelectionRange(selectionEnd, selectionEnd)
}
set value(val) { // Set by external method
if(val != this.state.value) {
this.setState({value: val, randKey: this.getKey()});
}
}
getKey() {
return 'input-key' + parseInt(Math.random() * 100000);
}
onBlur(e) {
this.state.value = e.target.value; // Set by user
if(this.props.blur) this.props.blur(e);
}
render() {
return(
<input className="te-input" type="text" defaultValue={this.state.value} onBlur={this.onBlur} key={this.state.randKey} />
)
}
If you need an Input that's both editable without cursor move and may be set from outside as well you should use default value and render the input with different key when the value is changed externally.
I have the same problem and for me it is because I am using Redux.
This article really explained it well.
https://medium.com/#alutom/in-order-to-understand-what-is-really-happening-it-might-be-helpful-to-artificially-increase-the-e64ce17b70a6
The browser is what manages the input curser and when it detects a new text it automatically kicks the curser to the end, my guess is that the state updates the textfield in a way the triggers that browser behaviour.

Resources