The problem I face is that it doesn't seem that componentDidMount is re-rendering my component, even though it is updating the state. Lot of code coming up, but it gives context to the issue I'm having. If I need to, I can upload screenshots of what is happening.
Here's the constructor:
export class Upload extends React.Component<RouteComponentProps<{}>, UploadTaggingOptions> {
constructor(props: any) {
super(props);
this.state = {
photographer: [
{ id: null, value: '', label: '' },
],
};
}
Here's component did mount:
componentDidMount {
//Fetch request for photographers from the db
fetch("http://localhost:49775/api/photographers")
.then(res => res.json())
.then((result) => {
var photographerData = this.state!.photographer;
var y = 0;
//Remove the empty object first and foremost. The list should now be totally empty
photographerData.shift();
//The loop to add the galleries to the galleryData array
for (var i in result) {
var id = result[i].id;
var value = result[i].firstname + ' ' + result[i].lastname;
var label = value;
var photographer = { "id": id, "value": value, "label": label };
photographerData.push(photographer);
y++;
}
this.setState({
isLoaded: true,
photographer: photographerData
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
alert("Error loading options for the photographers. Refresh the page. If the error persists, please contact your administrator");
}
)
And finally Render:
public render() {
return <div>
<div className="photographers">
<p><b>Photographer:</b></p>
<DropDown options={this.state!.photographer} />
</div>
}
Just for clarity's sake, there are more components on the screen (hence the extra div for the dropdown component).
I'm not sure why, but the dropdown renders with the blank options I intitialize in the constructor, componentdidupdate does the fetch request AND updates the state to the data that was fetched, but I have to click the blank value in order to load those data values into the dropdown. It is almost like it re-renders after I change the selected value instead of on state change.
I've tried moving those requests into the constructor, but have the same problem. Perhaps
EDIT: Here's the code for the Dropdown component:
import * as React from 'react';
import Select from 'react-select';
const DropDown = (props: any) => {
return (
<div className="dropdown">
<Select
closeOnSelect={!(props.stayOpen)}
disabled={props.disabled}
options={props.options}
placeholder="Select an option..."
removeSelected={props.removeSelected}
simpleValue
value={props.selectedPhotographer}
searchable={true}
multi={true}
/>
</div>
)
}
export default DropDown;
From react official documentation:
Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
But in your code you are mutating it, albeit via an assignment to another variable:
var photographerData = this.state!.photographer;
// this DOES mutate the original array.
photographerData.shift();
This can mess with Reacts batching update strategy and can cause delays.
If you do not need the data from original state, you can just do:
var photographerData = [];
window.onload = function() {
console.log('Testing');
let testArr1 = [1, 2, 3]
console.log('TestArr1 length before shift: ' + testArr1.length);
let testArr2 = testArr1;
testArr2.shift();
console.log('TestArr1 length after shift: ' + testArr1.length);
}
Related
The values of skillList is not showing in the drop down.it shows an empty dropdown.(the data fetched from api is pushed into the skillList array without any issue)
import React, { Component } from 'react'
let skillList = [];
export class DataCheck extends Component {
constructor(props) {
super(props)
this.state = {
skill: "",
skillId: "",
}
this. handleSkillChange=this. handleSkillChange.bind(this);
}
componentDidMount() {
// worker skill selection
fetch("http://localhost:3001/dataservices/getallskills")
.then (res=>res.json())
.then(res => {
console.log(res);
let temArray = {};
for (let i = 0; i < res.recordsets[0].length; i++) {
temArray["value"] = res.recordsets[0][i].SkillId;
temArray["label"] = res.recordsets[0][i].SkillTitle;
skillList.push(temArray);
console.log(skillList);
temArray = {};
}
})
.catch(function(error) {
console.log(error);
});
}
handleSkillChange(skill) {
this.setState({
skill: skill,
skillId: skill.value
});
}
render() {
return (
<div>
<form>
<select
value={this.state.skill}
onChange={this.handleSkillChange}
options={skillList}
placeholder="Skills"
/>
</form>
</div>
)
}
}
export default DataCheck
when I check the dev-tools, it shows like this:
options:Array
0:Object
label:"wood works"
value:6
(Array consists of 16 objects like this)
console says:
warning.js:36 Warning: Unknown prop options on tag. Remove this prop from the element.
Native select does not have an options props. You have to manually map the option tag:
<select
value={this.state.skill}
onChange={this.handleSkillChange}
placeholder="Skills"
>
{this.state.skillList.map((optionSkill) => (
<option value={optionSkill.value}>{optionSkill.label}</option>
)}
</select>
Currently you have added skillList as a normal variable with module scope. Even though you have changed skillList, the same will not be reflected in the UI because React does not detect this change. you will have to change skillList to a state variable for React.
this.state = {
skill: "",
skillId: "",
skillList: []
}
fetch("http://localhost:3001/dataservices/getallskills")
.then (res=>res.json())
.then(res => {
this.setState({
skillList: res.recordsets[0].map((recordSet) => ({
label: recordSet.SkillTitle,
value: recordSet.SkillId,
}))
});
});
You have to add option tag inside select. Loop array of items using .map and you will get your result.
This codepen can help you to make this work,
select example
Hope this can help you!
I am using reactjs select 2 but I don't know how to make it work so that when a user types something in a ajax request is made and the results are sent back.
I see it has some async options but I don't get how it works and how I would get it to work with axios.
I come up with this but it is kinda laggy when a user types(probably because it is re-rendering it after each type) and when the user selects a choice the value disappears.
export default class TestComponent extends Component {
constructor(props) {
super(props);
this.state = {value: ""};
}
onInputChange(option) {
this.getOptionsAsync(option)
}
getOptionsAsync(newInput) {
var that = this;
console.log("ffd", newInput)
axios.get(`https://localhost:44343/api/States/GetStatesByText?text=${newInput}`)
.then(function (response) {
var formatedResults = response.data.map((x)=> {
return {value: x.id, label: x.name}
})
that.setState({
options: formatedResults,
value: newInput
})
})
.catch(function (error) {
});
}
render() {
console.log(this.state.value, "value")
return (
<div className="test">
<Select
onInputChange={this.onInputChange.bind(this)}
value={this.state.value}
options={this.state.options }
/>
</div>
);
}
}
You're going to be doing an api call every single time that you type a letter with the current way you're doing things. I would recommend just loading the states once at the beginning, perhaps in your ComponentDidMount() method.
If you pass the isSearchable prop to React-Select it will automatically work as a filter anyways.
Another thing I've had to do in this case which I believe will fix your change problem is to make sure it calls the handler on change not just on input change.
Pass this prop:
<Select
value={this.state.value}
options={this.state.options }
onChange={value => {
if (value) this.onInputChange(value)
else this.onInputChange('')
}
/>
Due to the way this is automatically bound to arrow functions, you won't have to bind to this if you change your onInputChange to the following:
onInputChange = (value) => {
this.getOptionsAsync(value)
}
Finally, you should be setting the state in the above function so the value is stored.
onInputChange = (value) => {
this.getOptionsAsync(value)
this.setState({value})
}
I tried to push a value into a state array but I get an issue TypeError: Cannot read property 'state' of undefined at this.state.rows.push(a);
Know why? I am trying to push a new value into the array after i click a button.
App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor() {
super();
this.state = {
name: '',
rows: ['hello',<p>gfdfg</p>,'mello']
}
}
handle(e){
e.preventDefault();
var a = "h";
this.state.rows.push(a);
alert("hello");
}
render() {
return (
<div className="App">
Button<br/>
<input type="submit" id="black" onClick={this.handle}/><br/>
<p>{this.state.rows}</p>
</div>
);
}
}
export default App;
There are couple of things that are wrong here:
you should NEVER change the state directly:
This is a big No No:
this.state.rows.push(a);
instead you should do something like this:
this.setState({ rows : [...this.state.rows, a] })
or without ES6:
const newArray = this.state.rows.slice();
newArray.push(a);
this.setState({ rows: newArray })
You should always replace the state with a new one.
this in a react component is not what you think it is, in order to make it work you can do one of two things:
a. change your method to an arrow function:
handle = (e) => {
e.preventDefault();
var a = "h";
this.state.rows.push(a);
alert("hello");
}
b. bind this to the method:
constructor() {
super();
this.state = {
name: '',
rows: ['hello',<p>gfdfg</p>,'mello']
}
this.handle = this.handle.bind(this);
}
the method handle does not have access to the context of the class i.e this; consider writing it as a fat arrow function
// class definition
handle = () => {
e.preventDefault();
var a = "h";
this.state.rows.push(a);
alert("hello");
}
render() {
// render logic
}
Having said this, mutating the state is not a good idea, consider using setState if you want your component to re-render as a result of state change
handle = () => {
e.preventDefault();
let { rows } = this.state;
var a = "h";
rows.push(a);
this.setState({
rows,
});
}
You are doing wrong, you have to use setState() method to push the value in the array:
handle = (e) => {
e.preventDefault();
var a = "h";
let tempRows = [...this.state.rows];
tempRows.push(a)
this.setState({rows:tempRows})
alert("hello");
}
You have two problems.
Event handlers require 'this' to be bound: https://reactjs.org/docs/handling-events.html So following this, you must either write: this.handle = this.handle.bind(this) in your contructor, or change handler to arrow function, if your build process supports transpilation of class fields.
React component will only update if component props change, or component state changes. Which is done by comparing references. In your case, when you push to the array, you are mutating the state, so the new reference is never created, and component does not re-render. If you want to verify that, just put console.log(this.state.rows) below this.state.rows.push(a) and you'll see that the array has received the new value, but component does not represent it. You need to use this.setState to create a new reference for your state, like so: this.setState({ rows: [...this.state.rows, a] })
Another way of returning a new array from the current array with additional elements and then pushing to state is to use the concat method.
Example :
this.setState({ users: this.state.users.concat(<Additonal Items Here>)}
I have a table of about 500 items, all I'm trying to do is push data to the this.state.data and re-render the table. What's happening is that the row is added, but the data is not shown in the row. If I do a this.forceUpdate(), after a short time, then the data magically appears in the row. I'm assuming my re-render is occurring before the state is updated, how do I get around this? Here's the code that's adding to this.state.data and re-rendering:
// the format we expect
scrubData: function(rawData, cb) {
const scrubbedData = rawData.map((object, idx) => {
let { mediaserver = [] } = object.Relations;
let { latitude = [0], longitude = [0] } = object.Properties;
return {
label: object.Label || '',
name: object.Name || '',
mediaserver: mediaserver[0] || '',
geo: `${latitude[0]}, ${longitude[0]}`,
status: '',
event: '',
}
});
if (cb) {
cb(scrubbedData);
return;
}
return scrubbedData;
},
// push one item to the data array, so we don't have to call
// the entire data set all over again
addData: function(rawData) {
const scrubbedData = this.scrubData([rawData], (scrubbedData) => {
this.state.data.unshift(scrubbedData);
this.setState({
data: this.state.data,
});
});
},
It's because you are using unshift. With react, you should always mutate state by providing updated state, not by changing the current state.
Instead of using unshift, you can use something like concat, or the spread operator.
Using concat:
this.state.data.concat([scrubbedData]);
this.setState({
data: this.state.data,
});
Or using the spread operator:
this.setState({
data: [...this.state.data, scrubbedData]
});
I recommend checking out this stackoverflow post which lists all the mutating methods for arrays.
React works internally when doing the state diff by comparing references to objects/arrays, and since you are mutating the array, the reference is still the same, so React does not detect that a change has been made.
Herein lies the benefit of using an immutable library with React, as changes will always produce a copy, so you can remove an entire class of bugs such as this.
EDIT:
You are calling this.scrubData before adding the new row, and the result is that the new row has none of the additional data that you want appended to it. Try adding the new row to the array first, and then calling that function to append data to each row.
Okay, so I finally got this to work. I had to use componentWillUpdate. Not sure if this is correct, but it works now.
export default class Table extends React.Component {
constructor(props) {
super(props);
this.state = {
tableHeight: "50vh",
data: [],
}
}
componentWillUpdate(nextProps, nextState) {
this.state.data = nextProps.data;
}
render() {
const tableRow = this.state.data.map((object, idx) => {
return (
<TableRow key={idx} data={object} />
)
})
return (
<div className="table">
<tbody>
{tableRow}
</tbody>
</table>
</div>
</div>
)
}
}
When props data are passed as props then it's undefined inside componentWillMount but defined inside render.
What might be the problem???
render:
public state: any = {
authority: [],
cid: "1",
data: [],
id: [],
menuTitle: []
};
public componentWillMount() {
var temp: any;
let url: String = "http://localhost:9000/getFieldTitle/";
fetch(url + this.state.cid + "")
.then((response) => response.text()).then((value) => {
let jsonObject = JSON.parse(value);
for (let index in jsonObject) {
for (let header in jsonObject[index]) {
temp = [];
if (header === "id") {
temp = this.state.id;
temp.push(jsonObject[index][header])
this.setState({ id: temp })
}
if (header === "menuTitle") {
temp = this.state.menuTitle;
temp.push(jsonObject[index][header])
this.setState({ menuTitle: temp })
}
if (header === "dataFormat") {
temp = this.state.data;
temp.push(jsonObject[index][header])
this.setState({ data: temp })
}
if (header === "authority") {
temp = this.state.authority;
temp.push(jsonObject[index][header])
this.setState({ authority: temp })
}
}
}
})
.catch(e => console.log(e));
}
public render() {
let row = []
for (let i = 0; i < this.state.authority.length; i++) {
row.push(<FormSection
key={i}
id={this.state.id[i]}
cid={this.state.cid[i]}
menuTitle={this.state.menuTitle[i]}
data={this.state.data[i]}
/>
)
}
return (
<div className="container-fluid">
{row}
</div>
);
}
FormSection.tsx:
<MenuSectionStructure data={this.props.data} check="check" />
MenuSectionStructure.tsx:
import * as React from "react";
export class MenuSectionStructure extends React.Component<any, any> {
public state: any = {
authority: [],
dataType: [],
fieldName: [],
};
constructor(props: any) {
super(props);
}
public componentWillMount() {
console.log(this.props.data) // Gives undefined
}
public render() {
return (
<div>{this.props.data}</div> //Gives value of this.props.data
);
}
}
I have shown all data
I think that your problem is definitely the one Max Sidwani commented. When you first load the parent component, you launch various setState in componentDidMount. Probably the header authority goes before the dataFormat one. This means that your component will re-render (and all its children) twice. The first time, authority.length will be an integer bigger than 0 and so the render will loop and try to render FormSection components where the data prop will be undefined because the dataFormat header hasn't already been processed and the data state is still []. Then, the data state is set and in the second re-render the data is not undefined. You can't watch two renders because the first one renders nothing and the second one happens inmediately after, but since you are using setState twice, render is being called twice (the first time with authority set and the second with data set). You can probably check this out with:
public componentWillUpdate() {
console.log(this.props.data) // Not undefined
}
in the MenuSectionStructure component.
You can solve it by setting both states at the same setState at the initial fetch or checking if data is not empty in the render.
Yes, I found the answer by sending the whole data as a single props and then parsing at the lowest component so that i could display all objects as a props in component as required. The problem was with sending multiple data as props and choosing one props as props length in loop which cause all problem since they all were all array and set state was hitting randomly causing the loop with unwanted sequence. However, sending the whole data as a single props and then looping inside came as a solution to my problem.
Thanks all for your contribution, which help me allot to visualize the reason of the scenario.