Re-rendering state in Reactjs not working as expected - reactjs

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}

Related

Updating a value that's within an array within an array in a state

I'm trying to update the state of the id value in shoeList. Currently I have a textfield that allows for entering of a new ID and I want the state to update when the OK button is clicked.
Here is some of the relevant code:
state = {
editingToggle: false,
shoeList : [
{name: 'bob', id: '123213-0', shoeSize: 'L'}
],
}
<TextInput className='text-area-header' id="textM" width="m" type="text" placeholder={this.state.shoeList[0].id} />
<Button className="ok-button" variant="tertiary" size ='xs' type="button" handleClick={this.saveHeader}>OK</Button>
saveHeader(e) {
this.setState(state=> ({
shoeList[0].name:
}))
alert('Header changed to ' + this.state.shoeList[0].id);
e.preventDefault();
}
I'm not sure what to put in this.setState as I haven't found anything on how to update nested values through a google search. Also whenever I put a value attribute to the TextInput tags it doesn't allow me to edit in the textinput on the webpage anymore. Any help would be great
Consider this example:
saveHeader() {
this.state.shoeList[0].id = 'newid'
this.setState({ shoeList: this.state.shoeList })
}
setState() checks if the values have changed, and if not, it does not update the component. Changing a nested value does not change the reference to the array, which means that simply calling setState() is not enough.
There are two ways around this. The first is to use forceUpdate():
saveHeader() {
this.state.shoeList[0].id = 'newid'
this.forceUpdate()
}
This forces the component to re-render, even though the state didn't change.
The second way is to actually change the shoeList array by creating a new one:
saveHeader() {
let newShoeList = this.state.shoeList.slice()
newShoeList[0].id = 'newid'
this.setState({ shoeList: newShoeList })
}
Using .slice() on an array creates a new array that is completely identical. But because it is a new array, React will notice that the state changed and re-render the component.

how to validate drop down menu in react js

i'm trying to validate a basic form in reactjs which contains select dropdown box.i'm new to react and not able to figure out the correct tutorial.please suggest.
<select required className = "custom form-group" name = "workFlow"
value = {this.state.result.workflow}
required onChange = { this.handleChange} >
{
data.map((data1) => {
return <option key={data1.id}>{data1.workType}
</option>;
})
}
</select>
i am fetching data from a json file and i need to made this select field as mandatory
Using the "required" attribute with a element
you must have at least one child element
the first child element must have either an empty value attribute OR
the first child element must have no text content.
Other than that if you want to make use of event handlers then check the data that you received from the form in the onSubmit form Handler and then by making use of state display the error message or if check pass then submit the request to server.
Hope this help you out.

React rendering html table and reading from html table

This is a conceptual question in that I'm trying to understand the best way to handle tabular data in react without using any special component or library.
I have data in a html table created dynamically in my child component. The data comes from the parent component. Some of the columns have editable content that I trigger with an edit button to re-render a version of the table that has inline text boxes for all rows of the columns that are editable.
When I change the content of the text box, I want to be able to click on my save button and have all the rows get saved.
The save and edit buttons are not inline on the table, but just sit outside the table in my component.
How do I access the html table in my child component from the the parent component to read all the rows and save the values in the textboxes to a data store?
Here is a snippet of code where I'm attempting to build the select list dynamically. I'm having trouble with some syntax errors and it is not working, but it gives an idea of what I'm trying to do.
I'm passing in the category and the transaction id. I want to add the select list to each category cell in every row in my table when the edit mode is selected. The transaction id is my solution for having the index of the current row available on the list by adding 1 to the transaction id. I will then use the selected index - 1 to get the transaction id for updating the corresponding records category. This may be a hack, but I can't think of the right way or better way to link the transaction to the select list.
renderCategory(category,transid){
console.log(category);
if (this.props.editMode === true){
return <div>
<select id="selCategory" onChange={this.props.onCategoryChange}>
const arrCategory = ["Category1","Category1","Category2","Category3","Category4","Category5","Category6","Category7"];
var i;
for (i = 1;arrCategory.length+1;i++){
<option value={transid+1}>{arrCategory[i-1]}</option>
if(arrCategory[i-1] === category) {
selectedIndex = i-1;
}
}
</select>
</div>
}
else {
return category
}
}
Here I have the code in the parent for handling the onChange event of the select list in my child.
handleCategoryChange(e) {
// this will have to be changed because I'm using the index value to store the transaction id
alert("The Child HTML is: " + e.target.innerHTML + "The selected value is: " + e.target.options[e.target.selectedIndex].value);
//// update the date which is in parent state to reflect the new value for category for the passed in transaction id (the problem is I don't know what the transaction id is...)
}
To achieve this, you need to do the following
In your parent component:
Maintain a state in your parent component which will store the data that has to be rendered in the child component.
Write a function in parent component which will update the state(i.e. the data to be rendered in the child component).
Then pass the data in your parent component's state and the state update function to child component via props.
In your child component:
Retrieve the data and the function passed by the parent component from the props of the child component.
Use the data to populate your table and to each editable box's input, pass an onChange and provide it the reference of the function passed from your parent component.
Here is a small snippet to take reference from:
class Parent extends Component {
constructor() {
super()
this.state = {
data: ''
}
}
//update your state here
stateUpdateHandler = (value) => {
this.setState({data: value})
}
render () {
return(
<Child data={this.state.data} stateUpdateHandler={stateUpdateHandler} />
)
}
}
class Child extends Component {
render() {
const {data, stateUpdateHandler}
return(
<div>
<input
type='text' value={d.value}
onChange={(e) => stateUpdateHandler(e.target.value)} />
</div>
)
}
}
EDIT: This is how you should handle onChange event
onCategoryChange: function(event) {
console.log(event.target.value) //This would have the value of transaction provided in the value attribute of option tag
}
If you want to get the transaction id and not the value in the value attribute of the option tag, you will have to change your select tag and write it like this:
<select id="" onChange={function() {this.props.onCategoryChange(transid)}}>

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.

React rerender only one child

in my form i have a few dropdown components. Whenever first dropdown option changes i want to update props for the second dropdown and rerender it. My code looks like this
handleProjectChange(option) {
//this.setState({ selectedProject: option })
this.refs.phase.props = option.phases;
//this.refs.forceUpdate()
this.refs.phase.render()
}
render() {
var projectOptions = this.projectOptions
var defaultProjectOption = this.state.selectedProject
var phaseOptions = defaultProjectOption.phaseOptions
var defaultPhaseOption = phaseOptions[0]
var workTypeOptions = api.workTypes().map(x => { return { value: x, label: x } })
var defaultWorkTypeOption = workTypeOptions[0]
return (
<div>
<Dropdown ref='project' options={projectOptions} value={defaultProjectOption} onChange={this.handleProjectChange.bind(this)} />
<Dropdown ref='phase' options={phaseOptions} value={defaultPhaseOption} />
<Dropdown options={workTypeOptions} value={defaultWorkTypeOption} />
<button className="btn btn-primary" onClick={this.handleAddClick.bind(this)}>Add</button>
</div>
)
}
But props are not changed, so it rerenders the same options. At the moment im just rerendering entire form by setting new state on it. Is there any way to rerender only one child/Dropdown with new props?
The way to do this is to put the selected option in first dropdown selectedProject in state.
And inside your render function, fetch/ populate the options in the second dropdown, dependent on the selected project.
Flow will then be:
User selects an option in the first dropdown.
This triggers handleProjectChange()
Inside handleProjectChange(), the newly selected option is put in state, by a this.setState() call
Because state changed, react re-runs the entire render() function.
Under the hood, react figures out that only the second dropdown has changed, so react will only re-render the second drop-down on your screen/ in the DOM.
Although React does have a reconciliation algorithm that dynamically checks whether each component should be rerenader or not in every rendering of its parent, it doesn't always work as we intended.
https://reactjs.org/docs/reconciliation.html
For this kind of issues, you have two options. You can use either React.pureComponent or React.useMemo().

Resources