React: Form Submit how to pass multiple row data? - javascript

I am getting the data from api. I am displaying Feature ID, DisplayOrder textbox in the rows. User can change the Display Order value in the multiple rows. How to update the information using Post API? I am passing one value FeatureID and DisplayOrder in form submit. Please help to pass all the values that are changed(FeatureID, DisplayOrder) in form submit. If suppose FeatureID 11 and FeatureID 13 Display order changes, then form submit needs to pass these information only.
{"FeatureID":"11","DescriptionText":"Travel","FeatureText":Feature2,"DisplayOrder":"1","Date":"08/30/2011","FeatureName":"Research"},
{"FeatureID":"12","DescriptionText":"Sport","FeatureText":Feature3,"DisplayOrder":"2","Date":"08/30/2011","FeatureName":"Research"},
{"FeatureID":"13","DescriptionText":"Art","FeatureText":Feature4,"DisplayOrder":"3","Date":"08/30/2011","FeatureName":"Research"}]
import React from "react";
export class EditFeatures extends React.Component {
constructor(props) {
super(props);
this.state = {
FeatureID: "",
DisplayOrder: "",
DescriptionText: "",
FeatureText: "",
Feature: [],
};
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
this.DisplayFeatures();
}
DisplayFeatures() {
fetch(REQUEST_URL, { "Content-Type": "application/xml; charset=utf-8" })
.then((response) => response.json())
.then((data) => {
this.setState({
Feature: data,
loading: false,
});
});
}
handleSubmit(event) {
event.preventDefault();
const FeatureID = this.state.FeatureID;
const DisplayOrder = this.state.DisplayOrder;
const data = {
FeatureID,
DisplayOrder,
};
fetch(REQUEST_URL, {
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
})
.then((response) => response.json())
.catch((error) => console.error("Error:", error))
.then((response) => console.log("Success", data));
window.location.href = "/";
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<table>
<tbody>
{this.state.Feature.map((item, index) => {
return [
<tr key={item.FeatureID}>
<td>
<input
type="text"
id={item.FeatureID}
name="DisplayOrder"
value={item.DisplayOrder}
onChange={(ev) => {
const newFeature = this.state.Feature.map((f) => {
if (f.FeatureID == ev.target.id) {
f.DisplayOrder = ev.target.value;
}
return f;
});
this.setState({ Feature: newFeature });
}}
/>
</td>
<td>{item.DescriptionText}</td>
<td>{item.FeatureTex}</td>
</tr>,
];
})}
</tbody>
</table>
<button type="submit" name="submit">
Update
</button>
</form>
</div>
);
}
}
export default Edit_Features;

The answer is simple, just sort Feature array on DisplayOrder in handleSubmit like this:
import React from "react";
export class EditFeatures extends React.Component {
constructor(props) {
super(props);
this.state = {
FeatureID: "",
DisplayOrder: "",
DescriptionText: "",
FeatureText: "",
Feature: [],
};
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
this.DisplayFeatures();
}
DisplayFeatures() {
fetch(REQUEST_URL, { "Content-Type": "application/xml; charset=utf-8" })
.then((response) => response.json()) // you passed Content-Type: "application/xml" as request header but here you use response.json, remove Content-Type header if server API returns json
.then((data) => {
this.setState({
Feature: data.map((feature) => ({ ...feature, changed: false })),
loading: false,
});
});
}
handleSubmit(event) {
event.preventDefault();
const FeatureID = this.state.FeatureID;
const DisplayOrder = this.state.DisplayOrder;
const Feature = this.state.Feature;
const data = {
FeatureID,
DisplayOrder,
Feature, // this is how you pass an array to server, how will the server deserialize this depends on the framework used there
};
const self = this;
fetch(REQUEST_URL, {
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
})
.then((response) => response.json())
.catch((error) => console.error("Error:", error))
.then((response) => {
/**
* sort manipulates the array so we clone the Feature array before sorting it
* we pass comparator function to sort so that we sort on DisplayOrder
*/
const newFeature = [...this.state.Feature];
newFeature.sort((f1, f2) => f2.DisplayOrder - f1.DisplayOrder);
self.setState({ Feature: newFeature });
});
window.location.href = "/"; // ok why does this exist?!!
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<table>
<tbody>
{this.state.Feature.map((item) => {
return [
<tr key={item.FeatureID}>
<td>
<input
type="text"
id={item.FeatureID}
name="DisplayOrder"
value={item.DisplayOrder}
onChange={(ev) => {
// this is the proper way to update an element inside an array
const newFeature = [...this.state.Feature];
// I prefer === over == to avoid errors
const featureIndex = newFeature.findIndex(
(f) => f.FeatureID === ev.target.id
);
newFeature[featureIndex].DisplayOrder =
ev.target.value;
this.setState({ Feature: newFeature });
}}
/>
</td>
<td>{item.DescriptionText}</td>
<td>{item.FeatureTex}</td>
</tr>,
];
})}
</tbody>
</table>
<button type="submit" name="submit">
Update
</button>
</form>
</div>
);
}
}
export default EditFeatures;
this way when you click button submit, if the POST request to the server succeeds, the table will be updated according to DisplayOrder.
Note
If the request to the server fails for any reason the table won't be updated, if you don't care about the response of the server just sort the Feature array outside the .then before issuing the request.

Related

How to POST data from this.state?

I'm trying to use this.state in a POST call with Axios, and I can't understand why it isn't working. I tried to connect (getting a "bind () is not a function" error) and setState, but nothing seems to work . I can't find any good solution for this problem, follow the code below, thanks for your help in advance!
import ReactDOM from 'react-dom';
import axios from 'axios';
class AccountSettings extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
projects: "Loading...",
};
}
componentDidMount() {
fetch("/../api/****/account/projects")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
projects: result
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
saveProjects(event) {
axios.post('/../api/****/account/projects/save',{
projects: this.state.projects,
})
.then()
.catch();
event.preventDefault();
}
handleOnChange(event) {
this.setState({
projects: event.target.value
})
}
render() {
return (
<form method="post" onSubmit={this.saveProjects}>
<label htmlFor="projectsInput">My projects</label>
<textarea className="form-control" id="projectsInput" rows="3" aria-describedby="projectsInputHelp"
name="projectsInput" value={this.state.projects}
onChange={(event) => this.handleOnChange(event)} />
<small id="projectsInputHelp" className="form-text text-muted">
Add every project on a new line.<br/><br/>
</small>
<button type="submit" className="btn btn-primary">
Save projects
</button>
</form>
)
}
}
ReactDOM.render(<AccountSettings />, document.getElementById("account-settings"));```
Binding works for me this.saveProjects=this.saveProjects.bind(this); and if somehow binding fails then you can always use arrow functions in class components.
I'm doing this:
let data = Object.keys(this.state.fields).map(key => encodeURIComponent('formName['+key+']') + '=' + encodeURIComponent(this.state.fields[key])).join('&'))
fetch('....', {
method: "POST",
body: data,
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Accept': 'application/json, text/javascript, */*',
'X-Requested-With': 'XMLHttpRequest' <--- if needed for php framework isXmlHTTPRequest()
}
})
.then( response => response.json() )
.then( json => {
console.log(json);
})
.catch(error => console.log(error))

loop in react when updating status

I have a react component that is in charge of performing a network request using fetch. The API response will be shown on cards within the component.I have defined the structure of the response in the state as movimientos. But when updating the state inside filtrarDatos function with the response, an infinite loop is created and fetch requests are performed infinitely.
Here is my code:
export class Datepicker extends Component {
constructor(props) {
super(props)
this.state = {
startDate: "",
endDate: "",
focusedInput: "",
movimientos: {}
}
}
filtrarDatos(startDateString, endDateString) {
if (startDateString !== '' && endDateString !== '') {
const empresa = {
FECHA_INICIAL: startDateString,
FECHA_FINAL: endDateString
};
const options = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(empresa)
}
fetch('http://localhost:4000/api/movimiento/filtrarfecha', options)
.then((res) => res.json())
.then((data) => {
const { movimientos } = data
console.log({ movimientos })
this.setState({ movimientos })
})
.catch((err) => console.log(err))
}
}
render() {
const endDateString = this.state.endDate && this.state.endDate.format('YYYY-MM-DD') + "T13:47:14.985+00:00";
const startDateString = this.state.startDate && this.state.startDate.format('YYYY-MM-DD') + "T13:47:14.985+00:00";
return (
<div className="DatePicker">
<DateRangePicker
startDate={this.state.startDate} // momentPropTypes.momentObj or null,
endDate={this.state.endDate} // momentPropTypes.momentObj or null,
onDatesChange={({ startDate, endDate }) => this.setState({ startDate, endDate })} // PropTypes.func.isRequired,
focusedInput={this.state.focusedInput} // PropTypes.oneOf([START_DATE, END_DATE]) or null,
onFocusChange={focusedInput => this.setState({ focusedInput })} // PropTypes.func.isRequired,
endDatePlaceholderText={"Fecha inicial"}
startDatePlaceholderText={"Fecha final"}
displayFormat={"DD/MM/YYYY"}
numberOfMonths={1}
isOutsideRange={() => false}
showClearDates={true}
/>
{this.filtrarDatos(startDateString, endDateString)}
</div>
)
}
}
To be more clear the error is in the following part of the code, if I comment on the status update the program works correctly and only makes a single request. I am new to react and I cannot understand what is happening.
.then((data) => {
const { movimientos } = data
console.log({ movimientos })
this.setState({ movimientos })
})
This is a screenshot of my console during infinite network requests
Its happening because this.filtrarDatos is being called after each re-render (state changes), creating a infinite loop (change data, render, change data, rend...)
You can move the { this.filtrarDatos(startDateString, endDateString) } to componentDidMount:
Remove { this.filtrarDatos(startDateString, endDateString) }
Add this lifecycle function, after constructor:
componentDidMount() {
const endDateString = this.state.endDate && this.state.endDate.format('YYYY-MM-DD') + "T13:47:14.985+00:00";
const startDateString = this.state.startDate && this.state.startDate.format('YYYY-MM-DD') + "T13:47:14.985+00:00";
this.filtrarDatos(startDateString, endDateString);
}
You are calling {this.filtrarDatos(startDateString, endDateString)} in your render body, then in this method you update state, so it creates an infinite loop because react rerender your component after state changes.

React Autosuggest throwing error on clicking the suggestions

I am using React-autosuggest in my code but the issue i am facing is this that whenever i am clikcing any suggestion i am getting error that
Uncaught TypeError: Cannot read property 'trim' of undefined
Here is my code
var subjectsToBeSearched= []
const getSuggestions = value => {
const inputValue = value.trim().toLowerCase();
const inputLength = inputValue.length;
return inputLength === 0
? []
:subjectsToBeSearched.filter(
lang => lang.name.toLowerCase().slice(0, inputLength) === inputValue
);
};
const getSuggestionValue = suggestion => suggestion.name;
`
const renderSuggestion = suggestion => <div>{suggestion.name}</div>;
export default class Searchbar extends Component {
state = {
language_id: "",
subjects: [],
value: "",
suggestions: []
};
onChange = event => {
this.setState({
value: event.target.value
},()=>console.log(this.state.value));
};
onSuggestionsFetchRequested = ({ value }) => {
this.setState({
suggestions: getSuggestions(value)
});
};
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
});
};
componentWillMount() {
let languageid = localStorage.getItem("language_id");
var userdata = window.localStorage.getItem("userdata");
if (languageid == null) {
localStorage.setItem("language_id", 0);
}
this.setState({ language_id: languageid, userdata: JSON.parse(userdata) });
this.getAllSubjects();
}
getAllSubjects = async () => {
let details = {
language_id: this.state.language_id
};
this.setState({
response: fetch("http://18.221.47.207:3000/get_subjects", {
method: "GET",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Cache-Control": "max-age=31536000"
}
})
.then(response => response.json())
.then(responseJson => {
this.setState(
{
subjects: responseJson
},
() => {
let subjectsToBeFind = this.state.subjects.map(item => {
return { id: item.subject_id, name: item.subject_name };
});
subjectsToBeSearched=subjectsToBeFind
}
);
})
.catch(error => {
this.setState({
loading: false
});
swal("Warning!", "Check your network!", "warning");
console.log(error);
})
});
};
render() {
const { value, suggestions } = this.state;
const inputProps = {
value,
onChange: this.onChange
};
return (
<div className={`${styles.InputHeaderSearchDiv} `}>
<Autosuggest
inputProps={inputProps}
className={`${styles.InputHeaderSearch}`}
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
// alwaysRenderSuggestions={true}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
/>
<div className={`${styles.SearchIcon}`}>
<img src={SearchIcon} alt="search" />
</div>
</div>
);
}
}
So when i click on any suggestion i just need to console and do some stuff but right away it throws error
When you click a suggestion, the value of the "value" key in the inputProps is undefined thats the reason that you are getting a cannot trim error.
A workaround I did is add a props called "onSuggestionSelected" ( Documentation for onSuggestionSelected found here ) and added a function that set the value of the "value" key in the inputProps to whatever value you want your input tag should have after the click event.
THIS IS HOW MY AUTOSUGGEST LOOKSLIKE
HERE IS MY FUNCTION
in my case simply setting value of inputProps to string did it
<Autosuggest
inputProps: {
value: whateverValue.toString()

How to show information from API when using search box in ReactJS?

I'm using the Star Wars API to build a React JS project. The aim of my app is to be able to search for characters.
Here is my code for the search component in the my app.
At the moment I'm able to retrieve data the API and show the information on the page but I can't work out how to show this information when it's searched for.
Any ideas?
import React, { Component } from 'react';
class Search extends Component {
constructor(props){
super(props)
this.state = {
query:'',
peoples: [],
}
}
onChange (e){
this.setState({
query: e.target.value
})
if(this.state.query && this.state.query.length > 1) {
if(this.state.query.length % 2 === 0){
this.componentDidMount()
}
}
}
componentDidMount(){
const url = "https://swapi.co/api/people/";
fetch (url,{
method:'GET'
}).then(results => {
return results.json();
}).then(data => {
let peoples = data.results.map((people) => {
return(
<ul key={people.name}>
<li>{people.name}</li>
</ul>
)
})
this.setState({peoples: peoples});
console.log("state", peoples)
})
}
render() {
return (
<form>
<input
type="text"
className="search-box"
placeholder="Search for..."
onChange={this.onChange.bind(this)}
/>
{this.state.peoples}
</form>
)
}
}
export default Search
You could put your fetch in a separate function instead of in componentDidMount and call that when the component mounts and when your query changes.
Since you might be creating multiple requests if the user types quickly, you could use a debounce to only send one request, or use something that verifies that you always use the result of the latest request, like e.g. a token.
Example
class Search extends Component {
token = null;
state = {
query: "",
people: []
};
onChange = e => {
const { value } = e.target;
this.setState({
query: value
});
this.search(value);
};
search = query => {
const url = `https://swapi.co/api/people?search=${query}`;
const token = {};
this.token = token;
fetch(url)
.then(results => results.json())
.then(data => {
if (this.token === token) {
this.setState({ people: data.results });
}
});
};
componentDidMount() {
this.search("");
}
render() {
return (
<form>
<input
type="text"
className="search-box"
placeholder="Search for..."
onChange={this.onChange}
/>
{this.state.people.map(person => (
<ul key={person.name}>
<li>{person.name}</li>
</ul>
))}
</form>
);
}
}
You have to define it in diff function to manage easy.
import React, { Component } from 'react';
class Search extends Component {
constructor(props) {
super(props)
this.state = {
query: null,
peoples: [],
}
}
componentDidMount() {
this.serachPeople(this.state.query);
}
onChange(e) {
this.setState({ query: e.target.value }, () => {
if (this.state.query && this.state.query.length > 1) {
if (this.state.query.length % 2 === 0) {
this.serachPeople(this.state.query);
}
} else {
this.serachPeople(this.state.query);
}
})
}
serachPeople(query) {
const url = "https://swapi.co/api/people/";
if (query) {
// if get value ion query so filter the data based on the query.
fetch(url, {
method: 'GET'
}).then(results => {
return results.json();
}).then(data => {
let peoples = data.results.filter(people => people.name === query).map((people) => {
return (
<ul key={people.name}>
<li>{people.name}</li>
</ul>
)
})
this.setState({ peoples: peoples });
console.log("state", peoples)
})
} else {
fetch(url, {
method: 'GET'
}).then(results => {
return results.json();
}).then(data => {
let peoples = data.results.map((people) => {
return (
<ul key={people.name}>
<li>{people.name}</li>
</ul>
)
})
this.setState({ peoples: peoples });
console.log("state", peoples)
})
}
}
render() {
return (
<form>
<input
type="text"
className="search-box"
placeholder="Search for..."
onChange={this.onChange.bind(this)}
/>
{this.state.peoples}
</form>
)
}
}
export default Search;
I hope this will help for u. Let me know if u have any query.

React 'this' undefined when adding table row

I'm attempting to add a new row of data to a table that occurs when a user inputs text into a field and clicks a button.
The button click is tied to a function (AddNewRow) which sends the data to a controller and then adds a new row to the table with the data.
The data is sent to the controller correctly and if the page is refreshed the new row is showing (because of the get request after mount) but the problem is the table doesn't update dynamically.
I keep getting a console error saying 'this is undefined' in the AddNewRow function.
Ive attempted to bind 'this' to the constructor by using both '.bind(this)' and AddNewRow() => {} but it still doesn't bind?
class App extends React.Component {
constructor() {
super();
this.state = {
tableData: [{
}],
};
}
componentDidMount() {
axios.get('/Jobs/GetJobs', {
responseType: 'json'
}).then(response => {
this.setState({ tableData: response });
});
}
AddNewRow(){
axios.post('/Controller/CreateJob', { Name: this.refs.NewJobName.value})
.then(function (response){
if(response.data.Error) {
window.alert(response);
}
else {
var data = this.setState.tableData;
this.setState.tableData.push(response);
this.setState({ tableData: data });
}
})}
render() {
const { tableData } = this.state;
return (
<div>
<button onClick={() => this.AddNewRow()} >ADD</button>
<input ref="NewJobName" type="text" placeholder="Name" />
<ReactTable
data={tableData}
/>
</div>
)
}
Use arrow function to make this available in the then function:
axios
.post('/Controller/CreateJob', { Name: this.refs.NewJobName.value })
.then((response) => {
if (response.data.Error) {
window.alert(response);
} else {
this.setState(prevState => ({
tableData: prevState.tableData.concat([response])
}));
}
});

Categories

Resources