React - Keep newly added key & value in object after re-render - javascript

I am trying to add a "sorting" system to a clothing website I am building. The issue I am having is that whenever a new parameter is being added, it removes the old one added. I would guess the reason is that the variable holding the parameters are being re-rendered whenever you sort the products.
Here is my code:
const FetchAPI = (props) => {
const [product, setProducts] = useState([]);
// Key and Value
let facetKey = props.facetKey;
let facetValue = props.facetValue;
let params = {
store: "US",
offset: props.offset,
categoryId: props.categoryId,
limit: props.limit,
country: "US",
sort: "freshness",
currency: "USD",
sizeSchema: "US",
lang: "en-US",
};
if (facetKey) {
params = { ...params, offset: 0, limit: 0, [facetKey]: facetValue };
}
useEffect(() => {
const options = {
method: "GET",
url: "https://asos2.p.rapidapi.com/products/v2/list",
params: params,
headers: {
"x-rapidapi-key": "",
"x-rapidapi-host": "",
},
};
axios
.request(options)
.then(function (response) {
setProducts(response.data.products);
props.items(response.data.itemCount);
props.facets(response.data.facets);
})
.catch(function (error) {
console.error(error);
});
}, [props.limit, facetValue]);
return (
<div>
<div className={classes.container}>
{product.map((product) => (
<ProductCard
key={product.id}
img={product.imageUrl}
name={product.name}
price={product.price.current.text}
/>
))}
</div>
</div>
);
};
The re-rendering of params occurs because it is inside of the const FetchAPI, but I am not that sure how I can "ignore" that and make the params keep the first value. Perhaps could I solve this by putting the values in localstorage? Or is there a better way?

Related

State not changing after update

I have a problem with use state component of property where the state does not change when loading component. The state of the item should change since i am updating it after receiving a response in the previous form stepper but when i add a new dynamic input field, it changes the state but not for the first one
Here is the code that is bringing the error
export default function AddProp() {
const [propertyID, setPropertyID] = useState(0);
const [step,setStep] = useState(1);
const [values,setValues] = useState({
responseMessage: "",
loading: false,
name: "",
address: "",
county: "",
city: "",
zipcode: "",
type: "",
specifictype: "",})
const [formValues, setFormValues] = useState([
{ number_of_units: "", market_rent: "", square_feet: "", beds: "", property:propertyID.property },
])
let addFormFields = () => {
setFormValues([
...formValues,
{ number_of_units: "", market_rent: "", square_feet: "", beds: "" ,property:propertyID.property},
]);
};
let sendProperties = async () => {
const response = await axios
.post(
"http://127.0.0.1:8000/property/api/v1/addProperty/",
{
property_name: values.name,
address: values.address,
county: values.county,
city: values.city,
zipcode: values.zipcode,
property_type: values.type,
},
{ headers: headers }
);
setPropertyID(response.data);
if(response.status == 200){
setStep(step + 1 );
}else{
alert("An error occurred");
}
};
switch (step) {
case 3:
return (
<PropertyType
values={formValues}
handleChange={handleFormChange}
add={addFormFields}
remove={removeFormFields}
prevStep={prevStep}
nextStep={getandSend}
/>
);
case 4:
return <Success message={"DONE"} />;
default:
}
}
Instead of using the current formValues, pass a callback to setFormValues that takes in one parameter, say currentFormValues, and use that to update the state instead.
const addFormFields = () => {
setFormValues(currentFormValues => [
...currentFormValues,
{ number_of_units: "", market_rent: "", square_feet: "", beds: "" ,property:propertyID.property},
]);
};
This problem is related to stale state, a problem that occurs whenever we're trying to update state, often within a closure.

React js: ErrorTypeError - Can't read property of undefined array

Before passing real time data of a fleet of vessels into a table after an API request, I wanted to try to inject sample data, in fact I hardcoded them. The goal would be: if I can read sample data, than almost surely the API will show the data into the table of all the vessels I am looking for.
However the debugger says that ErrorTypeError - Can't read property of indefined variable properly and the Console says:
Response { type: "opaque", url: "", redirected: false, status: 0, ok:
false, statusText: "", headers: Headers, body: null, bodyUsed: false }
ErrorTypeError: this is undefined if that is useful I am also including a screen-shot of my desktop:
Below the code I am using:
import React, { Component } from 'react';
import styled from 'styled-components';
import GoogleMapReact from 'google-map-react';
const resultArea = document.getElementById('result');
let out = '';
const fetchConfig = {
method: 'GET',
mode: 'no-cors'
};
const MapContainer = styled.div`
// some components
`;
class BoatMap extends Component {
constructor(props) {
super(props);
this.state = {
buttonEnabled: true
};
this.updateRequest = this.updateRequest.bind(this);
}
updateRequest() {
const url =
'http://data.aishub.net/ws.php?username=My_KEY&format=1&output=json&compress=3&latmin=12.11&latmax=48.95&lonmin=-124.97&lonmax=-58.95';
console.log(url);
fetch(url, fetchConfig)
.then(function(data) {
console.log(data);
return this.dummyData; // <-- Can't read this
})
.then(function(jsonObject) {
const boatData = JSON.parse(jsonObject);
for (boat in jsonObject) {
const boatInfo = [
// parsing data from the API after confirming with hardcoded dummyData
];
boatOut(boatInfo);
console.log(boatInfo);
}
resultArea.innerHTML = out;
})
.catch(function(e) {
console.log('Error' + e);
});
this.setState({
buttonEnabled: false
});
setTimeout(() => {
this.setState({ buttonEnabled: true });
});
}
dummyData = [
{
ERROR: false,
USERNAME: 'My_KEY',
FORMAT: 'HUMAN',
LATITUDE_MIN: 20.5,
LATITUDE_MAX: 30.8,
LONGITUDE_MIN: -15,
LONGITUDE_MAX: 18.6,
RECORDS: 14
},
[
{
MMSI: 566619000,
TIME: '2020-01-25 19:51:38 GMT',
LONGITUDE: -14.84344,
LATITUDE: 28.282,
COG: 15.7,
SOG: 11.3,
HEADING: 16,
ROT: 0,
NAVSTAT: 0,
IMO: 9529504,
NAME: 'NORD SUMMIT',
CALLSIGN: 'S6RW5',
TYPE: 70,
A: 174,
B: 26,
C: 20,
D: 12,
DRAUGHT: 12.1,
DEST: 'NO SAU',
ETA: '02-02 12:00'
},
{
MMSI: 236446000,
TIME: '2020-01-25 19:51:28 GMT',
LONGITUDE: -14.83202,
LATITUDE: 28.64639,
COG: 38,
SOG: 12.1,
HEADING: 38,
ROT: 3,
NAVSTAT: 0,
IMO: 9291561,
NAME: 'KEY BAY',
CALLSIGN: 'ZDIJ4',
TYPE: 83,
A: 82,
B: 18,
C: 1,
D: 19,
DRAUGHT: 6.1,
DEST: 'CASABLANCA',
ETA: '01-27 15:00'
}
]
];
render() {
return (
<div className="google-map">
<GoogleMapReact
bootstrapURLKeys={{ key: 'My_KEY' }}
center={{
lat: 42.4,
lng: -71.1
}}
zoom={11}
<button className="btn-next-request" onClick={() => this.updateRequest()}>
Time to Next API Request
</button>
</GoogleMapReact>
</div>
);
}
}
What I have done so far:
1) I tried to solve the problem acting directly on the dummyDaya component trying to parse it manually. However the API already gives as answer the template file I included in the updateRequest() function. Nothing is shown.
2) I am not sure why I am not able to read the data of the two vessels as I copy/past the answer of the API for the two data. Technically it should be injected with no problem.
3) I am now trying to investigate the possibility that according to the official documentation the dummyData should not carry (and don't know if I should erase) the initial value of the request when passing the data. What I am referring to is the following part of the dummyData array:
dummyData = [
{
ERROR: false,
USERNAME: 'My_KEY',
FORMAT: 'HUMAN',
LATITUDE_MIN: 20.5,
LATITUDE_MAX: 30.8,
LONGITUDE_MIN: -15,
LONGITUDE_MAX: 18.6,
RECORDS: 14
},
[ …...
Thanks for pointing in the right direction for solving this problem.
Use arrow function to access this as component ref/instance. function has it's own this which will be misleading
fetch(url, fetchConfig)
.then((data) => {
console.log(data);
return this.dummyData; // <-- Can't read this
})
Same applies to all such function in your code here
First and foremost, if you wish to return the response from via fetch, you will need to call Body.json() to return the Response stream.
fetch(url, fetchConfig)
.then((data) => {
return data.json();
}).then((data) => {
console.log(data);
// do the rest here
});
Next, if you wish the reference this, you will need to use arrow functions,
fetch(url, fetchConfig)
.then((data) => {
return this.dummyData;
}).then((data) => {
console.log(data);
// do the rest here
});
Create state for dummyData.
constructor(props) {
super(props);
this.state = {
buttonEnabled: true,
dummyData : [], // Create state for dummyData
};
this.updateRequest = this.updateRequest.bind(this);
}
Then fetch data(I used async await) from your url and set it to dummyData.
const request = async () => {
const url = 'http://data.aishub.net/ws.php? username=My_KEY&format=1&output=json&compress=3&latmin=12.11&latmax=48.95&lonmin=-124.97&l onmax=-58.95';
const response = await fetch(url, fetchConfig);
const json = await response.json();
this.setState(dummyData : json);
}
request();

post request without a form or a button in React?

So I have to make a post request without a form or a button. I have the patientInfo array that is rendered on a table. When the user chooses a location for a patient, then that patient will have a timestamp value. When the patient in the array has a timestamp that's when I am supposed to auto post the patient with the timestamp.
My handleAutoObsSubmit() is kinda working but the problem is, it maps over the patienArray and sends the patient multiple time so if the user chooses the third patient's location, there will be three object of the same patient that is sent.
Another issue I am having with is componentDidUpdate, it sends the post request every second. I suspect that is because the patient count is being count down every sec. Not 100% sure though. Is it even a good idea to send post request in componentDidUpdate?
patientInfo = [
{ count: 100, room: "1", name: 'John Nero', timeStamp: '', location: ''},
{ count: 100, room: "2", name: 'Shawn Michael', timeStamp: '', location: ''},
{ count: 100, room: "3", name: 'Gereth Macneil', timeStamp: '', location: ''}
]
handleAutoObsSubmit = () => {
const postUrl = '/send_patient_that_has_timeStamp';
const timeStampedPatients = this.state.patientInfo.filter(patient => patient.timeStamp !== '');
let data = {};
timeStampedPatients.map((patient) => {
data = {
room: patient.room,
patient: patient.name,
timestamp: patient.timeStamp,
location: patient.locationInfo,
};
});
fetch(postUrl, {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
}
})
.then((res) => {
if (!res.ok) {
console.log('request failed');
} else {
console.log('request sent');
}
});
}
componentDidUpdate() {
this.state.patientInfo.map(patient => {
if (patient.timeStamp !== '') {
this.handleAutoObsSubmit();
}
});
}
componentDidMount() {
this.countDownInterval = setInterval(() => {
this.setState(prevState => ({
patientInfo: prevState.patientInfo.map((patient) => {
if (patient.locationInfo!== '') {
if (patient.count <= 0) {
clearInterval(this.countDownInterval);
}
return { ...patient, count: patient.count - 1 };
}
return patient;
})
}));
}, 1000);
}
You should be able to handle it in a similar fashion to this:
function Table() {
const [tableData, setTableData] = React.useState([
{
name: "John Doe",
timestamp: ""
},
{
name: "Jane Doe",
timestamp: ""
},
{
name: "Nancy Doe",
timestamp: ""
}
]);
const updateItem = (event, index) => {
let newstate = [...tableData];
newstate[index].timestamp = (new Date(Date.now())).toString();
alert(`Do POST here: ${JSON.stringify(newstate[index],null,2)}`);
setTableData(newstate);
};
return (
<table border="5">
<tr>
<th>
<div>Patient</div>
</th>
<th>
<div>Timestamp</div>
</th>
<th>Update</th>
</tr>
{tableData.map((item, index) => {
return (
<tr>
<td>{item.name}</td>
<td style={{width:'410px'}}>{item.timestamp}</td>
<td>
<button
style={{backgroundColor:'green', color:'white'}}
onClick={event => updateItem(event, index)}>
UPDATE
</button>
</td>
</tr>
);
})}
</table>
);
}
ReactDOM.render(<Table />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>

Determining pr eliminating empty key:value from an object for multiple filtering purposes

My app has a feature where users can filter results based on "blood group" and "city", and areas. Results will be retrieved from DB using Axios for Vuejs through "URL" query strings. Example url is: http://example.com/api/results?blood=a+&city=london
It should work in a way that when a user select just blood group from select menu: the url would exclude the city parameter. But from my current code, I can't get it stripped of, as a result, the database query returns no results on the basis that cityreturns null value.
Here's what I have in my Vue component:
<script>
export default {
props: ['user'],
data() {
return {
auth_user: this.user,
results: {},
blood_groups: "",
cities: "",
districts: "",
areas: "",
donorUrl: "/api/donors",
requestedBlood: "",
requestedCity: "",
requestedDist: "",
requestedArea: "",
params: {}
};
},
created() {
this.fetchDonors();
this.fetchCities();
},
methods: {
fetchDonors() {
let url = "/api/donors";
axios.get(url).then(response => {
this.results = response.data.data;
this.blood_groups = [...new Set(response.data.data.map(x=> x.blood_group))];
});
},
fetchCities() {
let url = "/api/location_type/cities";
axios.get(url).then(response => {
this.cities = response.data.cities
})
},
selected_blood_group(event) {
this.requestedBlood = event.target.value;
this.get();
},
get_city(event) {
this.requestedCity = event.target.value;
this.get();
},
get() {
let request = {
params: {
blood: this.requestedBlood,
city: this.requestedCity,
dist: this.requestedDist,
area: this.requestedArea
}
}
axios.get('/api/donors', request).then(response => {
this.results = response.data.data
})
}
},
};
</script>
My query is how can I remove or check if any of the following properties contains empty value, so that I do not include them in axios params?
let request = {
params: {
blood: this.requestedBlood,
city: this.requestedCity,
dist: this.requestedDist,
area: this.requestedArea
}
}
You can try below code.
Create a new object(called testParams) and add that object in params.suppose requestedCity is selected(not only but any variable is selected ). Then you can do like below.
if(requestedCity.length!=0)
{
testParams["city"]=requestedCity; // OTHERWISE DON'T ADD IN testParams object
}
Finally while making request through axios add testParams in params object like below.
axios.get('/yourUrl/',{
params:{
testParams //here vue will automatically sets 'testParams':testParams
}
})
I got it working with the following approach:
let request = {
blood: this.requestedBlood,
city: this.requestedCity,
dist: this.requestedDist,
area: this.requestedArea
}
for(let k in request)
if(!request[k]) delete request[k];
axios.get('/api/donors', {
params: request
}).then(response => {
this.results = response.data.data
})

Is it possible to get values for <options> from an endpoint that sends back json response using axios?

So, I have this endpoint: http://127.0.0.1:8000/api/materials
that would return this json response:
{
"data": [
{
"uuid": "05a36470-d0a0-11e7-91b4-ff3d7d9f961a",
"title": "Apple",
"viewing_time": 15,
"description": "",
"organization_id": null,
"created_at": "2017-11-24 06:45:36",
"updated_at": "2017-11-24 06:45:36",
"deleted_at": null
},
{
"uuid": "2048f730-bfa0-11e7-95fb-6dceb95ba437",
"title": "Banana",
"viewing_time": 15,
"description": "It's a fruit",
"organization_id": null,
"created_at": "2017-11-02 15:33:31",
"updated_at": "2017-11-02 15:33:31",
"deleted_at": null
},
{
"uuid": "3b6a1020-d0a0-11e7-b6bb-d77fc76d610b",
"title": "Strawberry",
"viewing_time": 15,
"description": "",
"organization_id": null,
"created_at": "2017-11-24 06:47:06",
"updated_at": "2017-11-24 06:47:06",
"deleted_at": null,
},
I want to pick all the titles and make them options.
and this is my function where axios is called:
materialList = () => {
var token = localStorage.getItem('jwt');
var apiBaseUrl = "http://127.0.0.1:8000/api/materials";
var config = {
headers: {
'Authorization': "bearer " + token,
'Accept': 'application/json',
'Content-Type': 'application/json',
},
withCredentials: false
}
axios.get(apiBaseUrl, config)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
and this is where I want the titles (Apple, Banana and Strawberry) to appear:
<Form.Input list='material' placeholder='Material' name="material_id" id="material_id" onChange={this.onChange}/>
<datalist id='material_id'>
<option value=/** What do I put here **/ />
</datalist>
I have used axios when submitting a post request to the api, but can I trigger an axios get request as soon as the page loads, so that I can get the title I needed ?
First create a state variable as shown below in your component.
constructor(props) {
super(props);
this.state = {
options: []
}
}
Now, you can use componentDidMount() to get those values from the API as shown below.
componentDidMount() {
const token = localStorage.getItem('jwt');
const apiBaseUrl = "http://127.0.0.1:8000/api/materials";
const config = {
headers: {
'Authorization': "bearer " + token,
'Accept': 'application/json',
'Content-Type': 'application/json',
}
}
axios.get(apiBaseUrl, config)
.then((response) => {
this.setState({
options: response.data
})
})
.catch((error) => {
console.log(error);
});
}
Now you can use that state variable to show in the option.
render() {
const { options } = this.state;
return(
<Form.Input list='material' placeholder='Material' name="material_id" id="material_id" onChange={this.onChange}>
{options.map((item, index) => <option key={index} value={item.uuid}>{item.title}</option>)}
</Form.Input>
)
}
First, add an options array to your state.
Next, in your axios function:
axios.get(apiBaseUrl, config)
.then(response => {
this.setState({
options: response.data.map(item => item.title),
});
})
Finally, in your UI component (assuming you've made the options from earlier available as a variable of the same name):
const optionList = options.map(option => <option value={option} />)
render() {
return (
// Other JSX here..
<datalist id='material_id'>
{optionList}
</datalist>
)
}
I assume that the jsx code you post is what inside your component render function.
If you required data from external source and want to make a http request to get these data whenever the component is mounted. What you might want to do is to get the data inside componentDidMount, save it to your state, and use it inside your render function, an example can be found below:
class YourComponent {
// Use componentDidMount to get the data via axios
componentDidMount() {
// ... Your code to prepare the axios call
/*
* Use arrow function to keep refer to React component `this`
* and sae the response data to the component state
*/
axios.get(apiBaseUrl, config)
.then(
response => this.setState({options: response.data})
)
.catch(function (error) {
// handle the error here
});
}
render() {
// Options will have the same format as your response data
const { options } = this.state;
return (<datalist id='material_id'>
{options.map(option =>
<option value={/* can be any attribute you want from the result object, like id, title, ..etc*/}>
{option.title}
</option>)}
</datalist>);
}
}
Regarding the triggering of the API request when the page will load:
Make yourself familiar with the React lifecycle methods.
https://reactjs.org/docs/react-component.html
In this case I would go for the componentDidMount() method:
componentDidMount() {
this.materialList();
}
If you do not plan to use redux to save the state, you need will probably need to call a setState() here in order to save the result of your request in your component's state (like Nico described).

Categories