Sending row/cell data on click to the function - ReactJS - reactjs

I am retrieving table data from componentWillMount and showing it as UI. I want to trigger a fuction when the row/cell is clicked and return that value back to another PHP so that i can query the backend using that retrieved cell value.
index = 0,1,2,3 ( I have 4 columns )
row = data for each index.
I am not sure why this.state.testTime is not sending the correct value to handleClick function. Does anyone have any inputs on this? Thanks.
_handleClick: function (event) {
this.setState({testTime: event.target.value});
console.log(event.target.value);
var data = {
testTime: this.state.testTime
}
console.log("clicked");
$.ajax({
type: "GET",
crossDomain: true,
url: "http://localhost:8080/TEST/capture.php",
data: data,
success: function(data){
alert(data);
},
error:function(data)
{
alert("Data sending failed");
}
});
},
return (
<tbody>
{tableData.map((row, index) => {
return (
<tr key={"row_" + index} >
{row.map((cell, index) => {
return (
<td key={"cell_" + index} onClick={this._handleClick} value={this.state.testTime} >{cell}</td>
);
})}
</tr>
);
})}
</tbody>
)

setState is an async function and it accepts a callback as the second argument that will be executed once setState is completed and the component is re-rendered. So you either need to use event.target.value directly for the data variable or put your code into the callback:
this.setState({testTime: event.target.value} ,() => {
var data = {testTime: this.state.testTime}
...
$.ajax...
});

Related

Can't fill my state in componentDidMound function

inside of my componentDidMount method I'm trying to make array of objects by using fetch. In my head it looks like this - on state I keep variable "loading" (true by default), and when my method is done with fetching it set it to false. On render method I've put if statement. But in real life my method filled array doesn't get executed (first console.log gets executed, second is not), . I'm losing my mind with this.
import { Company } from "../company/company.component";
export class CompanyList extends Component {
constructor(props) {
super(props);
this.state = {
tempResult: 10,
newArray: [],
loading: true,
};
}
componentDidMount = () => {
console.log(this.state.loading,"1");
const filledArray = this.props.companies.map((item) => {
fetch(`xxxx/incomes/${item.id}`)
.then((response) => response.json())
.then((data) => {
let transactionsToFloat = data.incomes.map((item) =>
parseFloat(item.value)
);
let result = transactionsToFloat.reduce((acc, num) => {
return acc + num;
}, 0);
result = Math.round(result * 100) / 100;
this.setState({ tempResult: result, loading: false });
console.log(item.id, item.name, item.city, result);
return {
id: item.id,
name: item.name,
city: item.city,
totalIncome: result,
};
});
this.setState({ loading: false });
return true;
});
this.setState({ newArray: filledArray });
};
render() {
if (this.state.loading) {
return <h1>Loading...</h1>;
} else if (!this.state.loading) {
return (
<div>
{/* <button onClick={this.handleClick}>Button</button> */}
<table>
<thead>
<tr>
<th> Id </th>
<th> Name </th>
<th> City </th>
<th> Total income </th>
</tr>
</thead>
{this.state.newArray.map((item) => (
<Company key={item.id} company={item} />
))}
</table>
</div>
);
}
}
}
Cheers
fetch is async, when you do this.setState({ loading: false }) after fetch, this line of code will be executed right away, before promise is even resolved. you are also not returning the data, but true values instead.
given that you are executing an array of promises, you may consider return fetch promises and wrap your array of promises with a Promise.all:
Promise.all(this.props.companies.map((item) => { return fetch().then().then() })
.then(results => this.setState({ newArray: results, loading: false }))
.catch(error => ... handle error here)
there is a caveat that Promise.all will reject if one of the promises fails. if you dont want that behavior you could use Promise.allSettled instead. allSettled will never reject and it returns instead an array of objects, with status and value keys.

Why only one button?

I make a request to my local server using fetch() method. The server returns this response:
{
// total quantity elements in all page
"total":7,
// quantity elements in one page
"perPage":3,
// current page
"page":1,
// quantity page
"lastPage":3,
// it category list. I display my category list on page.
"data":[
{"id":1,"title":"animals","created_at":"/...","updated_at":"/..."},
{"id":2,"title":"space","created_at":"/...","updated_at":"/..."},
{"id":3,"title":"sport","created_at":"/...","updated_at":"/..."}
]
}
Also in my local server, I have ability to use query parameters page or limit which I insert in URL:
page - using this param I can implement pagination
limit - using this param I can implement choose quantity element
I have two tasks:
Make pagination (DONE)
Make ability to choose quantity element on page using three buttons (quantity three elements, quantity four elements, quantity five elements)
First task I've already done, however the second task is where I have a problem.
Instead of three buttons, I have only one button. When I click this button in my page it displays two elements. I also need to show the other 2 buttons which will display three and four elements respectively when clicked.
See screenshot:
What to fix in the code?
Maybe I wrote something wrong in the return?
I comment code line which implement choose quantity element
Home.js:
const Home = () => {
const [value, setValue] = useState({
listCategory: [],
currentPage: 1,
buttonsPagination: 0,
quantityElementPage: 3, // this line
buttonsQuantityElementPage: 3 // this line
});
useEffect(() => {
async function fetchData(currentPage, quantityElementPage) { // this line
try {
const res = await apiCategory('api/categories', {
method: 'GET',
}, currentPage, quantityElementPage ); // this line
console.log(res);
setValue({
listCategory: res.data,
currentPage: res.page,
buttonsPagination: Math.ceil(res.total / res.perPage),
quantityElementPage: res.perPage, // this line
});
} catch (e) {
console.error(e);
}
}
fetchData(value.currentPage, value.quantityElementPage); // this line
}, [value.currentPage, value.quantityElementPage]); // this line
const changePage = (argPage) => {
setValue((prev) => ({
...prev,
currentPage: argPage,
}));
};
const changeQuantityElementPage = (argElement) => { // this method
setValue((prev) => ({
...prev,
quantityElementPage: argElement,
}));
};
return (
<div>
<Table dataAttribute={value.listCategory} />
{[...Array(value.buttonsPagination)].map((item, index) => (
<button key={'listCategory' + index}
onClick={() => changePage(index + 1)}>{index + 1}
</button>
))}
//here I display button who choose quantity element:
{[...Array(value.buttonsQuantityElementPage)].map((item, index) => (
<button onClick={() => changeQuantityElementPage(index+2)}>quantity element - {index+2}
</button>
))}
</div>
);
};
apiCategory.js:
export const apiCategory = async (url, args, valuePage, valueElement) => { //add valueElement in argument
const getToken = localStorage.getItem('myToken');
const response = await fetch(`${apiUrl}${url}?page=${valuePage}&limit=${valueElement}`, { //add valueElement in param limit
...args,
headers: {
"Content-type": "application/json; charset=UTF-8 ",
"Authorization": `Bearer ${getToken}`,
"Accept": 'application/json',
...args.headers,
},
});
return response.json();
}
This is happening because your value.buttonsQuantityElementPage is undefined. When you call setValue in useEffect, you did not include the previous values. buttonsQuantityElementPage no longer exists on value.
Also, if the Array constructor Array() receives an undefined value, it will return an array with a single undefined value. i.e. [...Array(undefinedValue)] will yield [undefined]
There are two options for a fix.
Option 1. Update the setValue in your useEffect to include all previous values and then override the properties with new values.
setValue(prev => ({
...prev,
listCategory: res.data,
currentPage: res.page,
buttonsPagination: Math.ceil(res.total / res.perPage),
quantityElementPage: res.perPage,
}));
Option 2. Split out the buttonsQuantityElementPage into its own variable. As far as I can see in your code, buttonsQuantityElementPage does not change. If that is the case then use
const buttonsQuantityElementPage = 3
and in the return section
[...Array(buttonsQuantityElementPage)].map((item, index) => (
<button onClick={() => changeQuantityElementPage(index + 2)}>quantity element - {index + 2}
</button>
))

Display data from API

i have the following code in parent.js file which gets data from API using axios which is working fine
//Parent.js
componentDidMount() {
axios.get('URL', {
method: 'GET',
headers: {
'key': 'apikeygoeshere'
}
})
.then((response) => {
this.success(response);
})
}
successShow(response) {
this.setState({
person: response.data.data.Table
});
}
render() {
return (
<div class="row">
{this.state.person.map(results => (
<h5>{results.first_name}</h5>
)
)
}
and the above code display data from api perfect. i want to display api data in child component instead of displaying data from json file. In child component i have the following code which display data from local.json file
//
The issue is in the successShow function the you cannot change the array state value like that. According to the answer from here :
Correct modification of state arrays in ReactJS
You can update the state like this:
this.setState(prevState => ({
person: [...prevState.person,response.data.data.Table]
});
or
this.setState({
person: this.state.person.concat([response.data.data.Table])
});
Try using
const data = this.state.person && this.state.person.find(item => item.id === id);
const relatedTo = this.state.person && this.state.person.filter( item => item.manager_id === data.manager_id && item.id !== data.id );

React - Json Schema Form dropdowns won't load initally unless I use SetTimeout function

This has been driving me and my team crazy. Here is the relevant code.
In the component's CDM we have:
componentDidMount() {
this.getContextID();
this.getConsumerID();
this.getEnvType();
//setTimeout(() => this.setState({ populatedMultiSchema: this.multiSchema }), 200);
//setTimeout(() => this.setState({ populatedMultiUISchema: this.multiUISchema }), 200);
this.setState({ populatedMultiSchema: this.multiSchema });
this.setState({ populatedMultiUISchema: this.multiUISchema });
}
so any one of the 3 methods listed will fetch the data for the dropdown. Here is an example of one (they are all basically the same).
getContextID() {
contextIDOptions = [];
console.log("CONVERT_TARGET::", this.props.fetchTarget)
return (
fetch(this.props.fetchTarget + "Configuration/ContextIDs", {
method: 'GET',
//mode: 'cors',
credentials: 'include',
}).then(response => {
if (response.status >= 400) {
this.setState({
value: 'no response - status > 400'
});
throw new Error('no response - throw');
}
return response.json()
}).then(function (json) {
for (var contextID = 0; contextID < json.List.length; contextID++) {
contextIDOptions.push(json.List[contextID]);
}
this.setState({ contextIDArray: contextIDOptions });
console.log("got contextIDs");
}.bind(this)).catch(() => {
this.setState({
value: 'no response - cb catch'
})
})
)
}
So we set the state there to 'contextIDArray'.
Then the JSON Schema form through it's multiUISchema Object has references to these widgets that help set the values for the form.
ContextIDWidget = (props) => {
return (
<div>
<input type="text" placeholder="Select one..." className="form-control" list="contextIDSelect" onChange={(event) => props.onChange(event.target.value)} />
<datalist id="contextIDSelect">
{this.state.contextIDArray.map((value, index) => { return <option key={index} value={value}>{value}</option> })}
</datalist>
</div>
)
}
This is the multiUISchema object (the part that matters for this discussion)
multiUISchema = {
file: {
'ui:widget': this.MultiFileWidget,
classNames: "uiSchema"
},
contextID: {
'ui:widget': this.ContextIDWidget,
classNames: "uiSchema"
},
}
And finally here it is in the return in the component.
return (
<div className="container" >
<Form
schema={this.state.populatedMultiSchema}
uiSchema={this.state.populatedMultiUISchema}
formData={this.state.formData}
onChange={({ formData }) => { this.setState({ formData }); this.setState({ totalFileSize: this.getMultiFileSize() }); this.checkConversionSupport() }}
onSubmit={this.handleSubmit}
>
So long story short, if Im using state object in the form, and Im doing setState on the objects Im using. Why do I always get a blank dropdown when I first load the component. Shouldn't the DOM (the dropdown in this case) get repainted with the updated data from the fetches when the state object is changed? I have console logs that show the fetched data in my inspection window so I know the data has been fetched. This is tab component. If I leave the tab or navigate to another page in my SPA and then go back to this page, then the dropdowns are all fully loaded. But I can never get it just load initially unless I set these timeouts in CDM instead of just setting state.
setTimeout(() => this.setState({ populatedMultiSchema: this.multiSchema }), 200);
setTimeout(() => this.setState({ populatedMultiUISchema: this.multiUISchema }), 200);
I know the post is long but felt I needed to include all the parts to get help with this. I can assure you we have been trying to resolve the issue for over a week. We welcome any comments. Thanks!
I am not fully familiar to your codebase. But it looks like something related about asynchronous requests. Here:
this.setState({ populatedMultiSchema: this.multiSchema });
this.setState({ populatedMultiUISchema: this.multiUISchema });
These two lines will be executed BEFORE these ones:
this.getContextID();
this.getConsumerID();
this.getEnvType();
But you expect them to get executed in reverse order. No. Your getContextID method making a request to a server. Javascript is asynchronous. But, by using await expression in an asynchronous function, you can pause the execution and wait for the Promise.
So, just update your componentDidMount method as below:
async componentDidMount() {
await this.getContextID();
await this.getConsumerID();
await this.getEnvType();
this.setState({ populatedMultiSchema: this.multiSchema });
this.setState({ populatedMultiUISchema: this.multiUISchema });
}
Here i created a Codepen on usage of async/await. There are some details in comments. You can play with it as you want.
Even if your problem is not caused mainly by this, this approach is better. You should either use async/await or Promise to work with network requests.

React -render values onClick of tabs

I have an app that uses axios and that render the values (eg: ID and warehouseName) using map which i put on tabs. Now I want to do is post a state on warehouseID at loadBrandTotal() from the value ID that I get in the map.
So far my code is like this
export default React.createClass({
getInitialState(){
return {
warehouseName: ""
}
},
componentWillMount(){
this.loadWarehouse();
this.loadBrandTotal();
},
loadWarehouse(){
var that = this
var url = api + 'retrieveWarehouseList';
axios.post(url,
{
warehouseName : 'NONE'
})
.then(function (response) {
console.log(response.data);
that.setState({
warehouseList : response.data.retrieveWarehouseListResult
});
})
.catch(function (response) {
console.log(response);
});
},
loadBrandTotal(){
var that = this
var url = api + 'retrieveTotalSalesPerBrand';
axios.post(url,
{
warehouseID : "None"
})
.then(function (response) {
console.log(response.data);
that.setState({
totsalesperbrand : response.data.retrieveTotalSalesPerBrandResult
});
})
.catch(function (response) {
console.log(response);
});
},
render() {
var wareName = this.state.warehouseList;
return (
<ul className="tabs tabs-transparent">
{wareName.map(function(nameoObjects) {
return (
<li className="tab">
<a href="#test1" value={nameoObjects.ID} key={nameoObjects.ID}>{nameoObjects.warehouseName}</a>
</li>
)
})}
</ul>
)}
})
Thanks a lot in advance.
I'm not 100% sure what you're wanting to do, but is it that inside the loadBrandTotal() function you want to post data you've gotten in the loadWarehouse() function? And if so, WHEN are you wanting to do this posting, as it's rendered? On a button click?
Here's an example where each list element has a button that sends the ID value the element got in the map fucntion to the loadBrandTotal() function, where it's posted (see code comments for changes):
// Give the id as an argument to loadBrandTotal
loadBrandTotal(id){
var that = this
var url = api + 'retrieveTotalSalesPerBrand';
axios.post(url,
{
// Post the ID
warehouseID : id
})
.then(function (response) {
console.log(response.data);
that.setState({
totsalesperbrand : response.data.retrieveTotalSalesPerBrandResult
});
})
.catch(function (response) {
console.log(response);
});
},
render() {
var wareName = this.state.warehouseList;
return (
<ul className="tabs tabs-transparent">
{wareName.map(function(nameoObjects) {
return (
<li className="tab">
<a href="#test1" value={nameoObjects.ID} key={nameoObjects.ID}>{nameoObjects.warehouseName}</a>
// When clicked, this button sends THIS list object's nameoObject ID to the loadBrandTotal function, where it's posted
<button onClick{() => this.loadBrandTotal(nameoObjects.ID)}>Post ID</button>
</li>
)
})}
</ul>
)}
So in that example, every list element rendered in the map function includes a button that, when clicked, sends its own nameoObject ID to the loadBrandTotal function, where it's posted and the state is set. Is that the kind of thing you're trying to do?

Resources