Optimize code for two similar states in react - reactjs

I have two similar states in React:
const [bareLong, setBareLong] = useState([
["2", "14"],
["1", "16"],
]);
const [bareTrans, setBareTrans] = useState([
["2", "14"],
["1", "16"],
]);
function addBareLong() {
setBareLong(function (prevBareLong) {
const newArr = [...prevBareLong, ["2", "14"]];
return newArr;
});
}
function addBareTrans() {
setBareTrans(function (prevBareLong) {
const newArr = [...prevBareLong, ["2", "14"]];
return newArr;
});
}
Functions addBareLong() and addBareTrans() will be added on two buttons.
How can I simplify this code? I think I copy-paste too much.

you can do it in one state
the state is object like
const [bareState, setBareState] = useState({
bareLong: [
["2", "14"],
["1", "16"]
],
bareTrans: [
["2", "14"],
["1", "16"]
]
});
function addBareLong() {
setBareState(prevState => {
return {
...prevState,
bareLong: [...prevState.bareLong, ["2", "14"]]
};
});
}
function addBareTrans() {
setBareState(prevState => {
return {
...prevState,
bareTrans: [...prevState.bareTrans, ["2", "14"]]
};
});
}
```
this is the first solution what i prefer or you can use this:
```
function addNewItem(prevState, setState) {
setState(prevState => [...prevState, ["2", "14"]]);
}
function addBareLong() {
addNewItem(bareLong, setBareLong);
}
function addBareTrans() {
addNewItem(bareTrans, setBareTrans);
}
```

Related

Can't use the data from API when app just starts

My data is undefined when the app is started but after the refresh, the data comes perfectly.
For startup
It gives me [Unhandled promise rejection: TypeError: Object.entries requires that input parameter not be null or undefined]
But after the refresh, the data comes perfectly and everything working.
This is part of my data
Object {
"attributes": Object {
"htmlName": null,
"id": 0,
"items": Array [
Object {
"htmlName": "r_1",
"name": "m2 (Brüt)",
"numeric": true,
"options": Object {},
"order": 0,
"required": true,
},
Object {
"htmlName": "r_2",
"name": "m2 (Net)",
"numeric": true,
"options": Object {},
"order": 0,
"required": true,
},
Object {
"htmlName": "r_164",
"name": "Arsa Alanı (m2)",
"numeric": true,
"options": Object {},
"order": 0,
"required": true,
},
Object {
"htmlName": "a_137",
"name": "Oda Sayısı",
"numeric": false,
"options": Object {
"12": "1+0",
"13": "1+1",
"14": "1.5+1",
"15": "2+0",
"16": "2+1",
"17": "2.5+1",
"18": "2+2",
"19": "3+1",
"20": "3.5+1",
"21": "3+2",
"22": "4+1",
"226": "0+1",
"23": "4.5+1",
"24": "4+2",
"25": "4+3",
"26": "4+4",
"27": "5+1",
"28": "5+2",
"29": "5+3",
"30": "5+4",
"31": "6+1",
"32": "6+2",
"33": "6+3",
"34": "7+1",
"35": "7+2",
"36": "7+3",
"37": "8+1",
"38": "8+2",
"39": "8+3",
"40": "8+4",
"41": "9+1",
"42": "9+2",
"43": "9+3",
"44": "9+4",
"45": "9+5",
"46": "9+6",
"47": "10+1",
"48": "10+2",
"49": "10 Üzeri",
},
"order": 0,
"required": true,
},
api.js
export const getData = function () {
return axios
.get(
"blabla",
{
headers: {
Authorization: `blabla`,
},
}
)
.then((json) => {
if (json && json.status === 200) {
//console.log(json);
return json.data;
}
})
.catch((e) => {
console.log(e);
});
};
App.js
const [data, setData] = useState({});
const [roomValue, setRoomValue] = useState(null);
const [roomCount, setRoomCount] = useState([]);
const [isFocus, setIsFocus] = useState(false);
useEffect(() => {
getDataFunc();
//setDropdown(data.attributes.items[3].options);
}, []);
const getDataFunc = async () => {
const res = await getData();
//console.log(res);
setData(res);
console.log(data);
};
function setDropdown(query) {
const response = query;
try {
const entries = Object.entries(response);
const tempArray = [];
for (let i = 0; i < entries.length; i++) {
var key;
var value;
(key = entries[i][0]), (value = entries[i][1]);
tempArray.push({ key: value, value: key });
}
setRoomCount(tempArray);
//console.log(roomCount);
} catch (error) {
//console.log(error);
}
}
How can I fix that ?
Add a seperate useEffect to check wheather the data has been set and then only set the dropdown values
useEffect(() => {
getDataFunc();
}, []);
useEffect(() => {
if(data && data.attributes?.items[3]){
setDropdown(data.attributes.items[3].options);
}
}, [data]);
const getDataFunc = async () => {
const res = await getData();
//console.log(res);
setData(res);
console.log(data);
};
It seems like the error is caused by the attributes property being empty when you try to access it. But when you assign them one by one then it loads because the data is loaded per nested property before assigning it to the variable. Means it hasn't fully loaded yet
const response = data.attributes.items[3].options;
It outputs an error because attributes is undefined. So it's not an object, therefore, attributes.items is considered invalid
// sample
const data = {
/* attributes: {
items: {
1: {
options: 'option1'
},
2: {
options: 'option2'
},
3: {
options: 'option3'
}
}
} */
}
const specificData = data.attributes.items[3].options
console.log(specificData) //
So one solution would be using the optional chaining operator to avoid the error, it's just basically a question mark (?) after the object you are trying to access.
The response would be then 'undefined'. That way even if the attributes is empty or not, data will be assigned to the response constant then you can just add some more checking outside of that.
// sample
const data = {
/* attributes: {
items: {
1: {
options: 'option1'
},
2: {
options: 'option2'
},
3: {
options: 'option3'
}
}
} */
}
const specificData = data.attributes?.items[3].options
console.log(specificData) // outputs undefined instead of an error
Let me know if this works btw. maybe you could provide the actual api or maybe a sample api endpoint so we could test it directly. Or maybe the full code?
I've encoutered this before though I'm not 100% sure this is all I've done. But for the error I'm sure the optional chaining operator will prevent it
Try calling getData inside an async function and wait for the process to complete like this in your App.js
const [data, setData] = useState([]);
const [roomCount, setRoomCount] = useState([]);
useEffect(() => {
getDataFunc()
}, []);
const getDataFunc = async() => {
await getData(setData);
const response = data;
console.log(response);
const entries = Object.entries(response);
const tempArray = [];
for (let i = 0; i < entries.length; i++) {
var key;
var value;
(key = entries[i][0]), (value = entries[i][1]);
tempArray.push({ key: value, value: key });
}
setRoomCount(tempArray);
console.log(roomCount);
}
note: The best practice is not to directly pass the setData function to getData api call instead return the response from api and assign the response in main code like below
const response = await getData();
setData(response)
From what I see, your data.attributes has undefined value.
Please double-check everything, it is technically impossible to get data directly if data.attributes is undefined

Push value of arrivalDate in array

I would like to store every arrivalDate in my array list.
Someone could tell me how can I do it?
But my array is still empty.
JSON returned by the API:
{
"reservations": {
"reservationInfo": [
{
"roomStay": {
"arrivalDate": "11am"
},
"WeatherR": {
"sound": "cloudy"
},
},
{
"roomStay": {
"arrivalDate": "7pm"
},
"WeatherR": {
"sound": "cloudy"
},
}
]
}
}
component.ts
searchForReservation() {
alert('hello');
this.http.get('/api/searchForReservation')
.subscribe((data) => {
this.ddataIno = data;
this.ddataIno = this.ddataIno.result.reservations.reservationInfo;
console.log('number of value', this.ddataIno.length);
console.log('content', this.ddataIno);
for (let i = 0; i <= this.ddataIno[i].length; i++) {
this.list = this.ddataIno.roomStay.arrivalDate;
}
console.log('store array', this.list)
})
}
searchForReservation() {
alert('hello');
this.http.get('/api/searchForReservation')
.subscribe((data) => {
const reservationInfo = this.data.result.reservations.reservationInfo;
this.list = reservationInfo.map(e => e.roomStay.arrivalDate);
})
}
Here's a working example in vanilla JS. You would need to make some small adjustments for angular, like this.list = ... instead of let list = ...
Using Array#map, you can create a new array from the JSON object
data.reservations.reservationInfo.map(r => r.roomStay.arrivalDate)
let data = {
"reservations": {
"reservationInfo": [{
"roomStay": {
"arrivalDate": "11am"
},
"WeatherR": {
"sound": "cloudy"
},
},
{
"roomStay": {
"arrivalDate": "7pm"
},
"WeatherR": {
"sound": "cloudy"
},
}
]
}
}
// declare your list as an array at the top
// list: []
// below would start off as 'this.list'
let list = data.reservations.reservationInfo.map(r => r.roomStay.arrivalDate);
console.log(list);
Your for loop is just reassigning the value of this.list
I suggest reading up on Array methods
I would use a map method, e.g.
this.list = this.ddataIno.result.reservations.reservationInfo.map(i => i.roomStay.arrivaldate);

how to post an array with some object when I have an array with id, using angular

I need your opinion how to post an array with some object.
I have this code:
const selectedJobs = this.ms.selectedItems;
if (!selectedJobs) {
return;
}
const selectedJobsId = selectedJobs.map((jobsId) =>
jobsId.id
);
in this case I get all jobId like an array ['618e2ee9', '3ee199b7']
const payload = [
{
jobId: selectedJobsId,
state: 2,
}
];
from payload I get an array with one object that have inside an array with JobsId and state. As below
[
{
"jobId": [
"618e2ee9",
"3ee199b7"
],
"state": 2
}
]
I should get this response, one array with all objects. all jobs id to be objects:
[
{
"jobId": "618e2ee9",
"state": 2
},
{
"jobId": "3ee199b7",
"state": 2
}
]
Have you any idea please?
Try this
const payload = selectedJobsId.map(job =>
{
return {jobId: job, state: 2};
});
const desiredArray = selectedJobs.map(job =>
{jobId:job.id, state:job.state}
);

When I try to change one state of an object the other also changes (React)

I'm using an object in two different states, in on of the states I just set the object in the state, and the other state I change one of the values in the object before setting state, but when I make the change both of the states change.
const fetchCategories = () => {
setAllCategory(categoryList1["movies"]);
categoryList1["movies"].forEach((i) => {
setMovies((prev) => {
i["favmovies"] = [];
return [...prev, i];
});
});
};
both states return an empty favmovies array
[
{
"id": "1",
"name": "development",
"image": "img.png",
"favmovies": []
},
{
"id": "2",
"name": "socialmedia",
"image": "img2.png",
"favmovies": []
},
{
"id": "3",
"name": "writing",
"image": "img2.png",
"favmovies": []
}
]
Issue
You are still mutating an object reference:
const fetchCategories = () => {
setAllCategory(categoryList1["movies"]);
categoryList1["movies"].forEach((i) => {
setMovies((prev) => {
i["favmovies"] = []; // <-- mutation is here
return [...prev, i];
});
});
};
i["favmovies"] = []; mutates the i object that is still a reference in the categoryList1.movies array.
Solution
From what I can tell, you want to store categoryList1.movies array into the allCategory state, and then a copy of categoryList1.movies array with the favmovies array emptied/reset to an empty array.
Instead of for-each iterating over the categoryList1.movies array and enqueueing multiple state updates, just map categoryList1.movies array to a new array reference for the movies state.
const fetchCategories = () => {
setAllCategory(categoryList1["movies"]);
setMovies(movies => movies.concat( // <-- add to existing state
categoryList1.movies.map(movie => ({ // <-- map to new array
...movie, // <-- shallow copy movie object
favmovies: [], // <-- update property
}))
));
};

Is this the correct way to update state?

My Component looks like this:
import cloneDeep from "clone-deep";
import { Context } from "../../context";
const Component = () => {
const context = useContext(Context);
const [state, setState] = useState(
{
_id: "123",
users: [
{
_id: "1",
points: 5
},
{
_id: "2",
points: 8
}
]
}
);
useEffect(() => {
context.socket.emit("points");
context.socket.on("points", (socketData) => {
setState(prevState => {
const newState = {...prevState};
const index = newState.users
.findIndex(user => user._id == socketData.content._id);
newState.users[index].points = socketData.content.points;
return newState;
})
});
return () => context.socket.off("points");
}, []);
return <div>(There is table with identificators and points)</div>
};
I wonder if this is the right approach. I just want to write the code in the right way.
Or maybe it's better with the use of deep cloning? Does it matter?
setState(prevState => {
const newState = cloneDeep(prevState);
const index = newState.users
.findIndex(user => user._id == "2");
newState.users[index].points++;
return newState;
})
EDIT: I added the rest of the code to make it easier to understand.
In your current code:
useEffect(() => {
setState((prevState) => {
const newState = { ...prevState };
const index = newState.users.findIndex((user) => user._id == "2");
newState.users[index].points++;
console.log({ prevState, newState });
return newState;
});
}, []);
You can see that prevState is being mutated (points is 9):
{
_id: "123",
users: [
{
_id: "1",
points: 5
},
{
_id: "2",
points: 9 // Mutated!
}
]
}
To avoid mutating the state, you have to use not mutating methods such as spread operator or map function:
useEffect(() => {
setState((prevState) => {
const newState = ({
...prevState,
users: prevState.users.map((user) =>
user._id === "2"
? {
...user,
points: user.points + 1
}
: user
)
})
console.log({ prevState, newState });
return newState
}
);
}, []);
Now you can see that the prevState is not mutated:
{
_id: "123",
users: [
{
_id: "1",
points: 5
},
{
_id: "2",
points: 8 // Not mutated :)
}
]
}
Your code will work, but the problem will start when your state becomes bigger.
You currently have only two properties on the state, the _id and the users. If in the future will add more and more properties like loggedUser and settings and favorites, and more... your application will render everything on every state change.
At this point you will have to start thinking about other solutions to state management, like redux, mobx, or just split the state to smaller useState, also you can look into useReducer in complex structures.

Resources