useState from API returning an empty array - javascript

I'm having some issues populating data from an API. When I console.log the state "dataFromApi", it's working just fine. Meaning, I'm getting an arr of multiple objects.
However, I then plugged in the API data from the state into the "columnsFromBackend", "items" section. When I then console.log the "columns" state at the bottom of the page which is just all the data from "columnsFromBackend", it returns me all the hardCodedData but not the one from the API.
Meaning, I'm just receiving an empty array. This is the output from the console.log(columns). Any suggestions on what might be happening here?
const [dataFromApi, setDataFromApi] = useState([]);
useEffect(() => {
getLeadsClApproved().then((resp) => {
setDataFromApi(resp.data);
});
}, []);
const hardCodedData = [
{
id: uuid(),
business_name: "Canva",
first_name: "Melanie",
last_name: "Perkins",
created_at: "15th of Nov., 2022",
},
{
id: uuid(),
business_name: "Microsoft",
first_name: "Bill",
last_name: "Gates",
created_at: "15th of Nov., 2022",
},
];
const columnsFromBackend = {
[uuid()]: {
name: "In Progress",
items: hardCodedData,
},
[uuid()]: {
name: "CL Approved",
items: dataFromApi,
},
[uuid()]: {
name: "CL Declined",
items: [],
},
[uuid()]: {
name: "Awaiting Response",
items: [],
},
[uuid()]: {
name: "Interview Scheduled",
items: [],
},
[uuid()]: {
name: "Accepted",
items: [],
},
[uuid()]: {
name: "Rejected",
items: [],
},
};
const [columns, setColumns] = useState(columnsFromBackend);
console.log(columns); // logs the columns with its content

The columns state variable is initialized with the value of columnsFromBackend on the initial render. On subsequent renders (like when the API data returns, resulting in the effect calling setDataFromApi), columnsFromBackend is updated with the new value of dataFromApi, but the state value columns is not. This is how useState works. The argument passed to useState provides an initial value, but changes to that initialization value do not automatically flow through. The only way the state value gets updated, columns in this case, is by calling setColumns. You could add another effect to synchronize columns with dataFromApi, but I'd propose something like this instead (I'm assuming you want the uuid's you're generating to be stable across renders):
const [dataFromApi, setDataFromApi] = useState([]);
const [uuids] = useState(() => ({
canvaUuid: uuid(),
microsoftUuid: uuid(),
inProgressUuid: uuid(),
clApproveUuid: uuid(),
clDeclinedUuid: uuid(),
awaitingResponseUuid: uuid(),
interviewScheduledUuid: uuid(),
acceptedUuid: uuid(),
rejectedUuid: uuid(),
}));
useEffect(() => {
getLeadsClApproved().then((resp) => {
setDataFromApi(resp.data);
});
}, []);
const columns = React.useMemo(() => {
const hardCodedData = [
{
id: uuids.canvaUuid,
business_name: "Canva",
first_name: "Melanie",
last_name: "Perkins",
created_at: "15th of Nov., 2022",
},
{
id: uuids.microsoftUuid,
business_name: "Microsoft",
first_name: "Bill",
last_name: "Gates",
created_at: "15th of Nov., 2022",
},
];
return {
[uuids.inProgressUuid]: {
name: "In Progress",
items: hardCodedData,
},
[uuids.clApproveUuid]: {
name: "CL Approved",
items: dataFromApi,
},
[uuids.clDeclinedUuid]: {
name: "CL Declined",
items: [],
},
[uuids.awaitingResponseUuid]: {
name: "Awaiting Response",
items: [],
},
[uuids.interviewScheduledUuid]: {
name: "Interview Scheduled",
items: [],
},
[uuids.acceptedUuid]: {
name: "Accepted",
items: [],
},
[uuids.rejectedUuid]: {
name: "Rejected",
items: [],
},
};
}, [uuids, dataFromApi]);
console.log(columns); // logs the columns with its content
Using useMemo here instead of useState guarantees that columns will always be in sync with dataFromApi.

Related

How to console.log data of a nested object from API with axios?

I am learning to fetch data from api right now. I have a local database written on RoR. I have game model and a nested player model inside of it. When I go to http://localhost:3000/api/v1/games, I see right json data:
[
{
id: 1,
code: "KR984",
name: "Hello world",
created_at: "2022-12-02T13:34:51.992Z",
updated_at: "2022-12-07T10:32:35.827Z",
slug: null,
fight: false,
players: [
{
id: 1,
name: "PLAYER 1",
initiative: null,
hp: 20,
languages: "sjdads",
perc: "12",
inv: "12",
ins: "12",
armor: 44,
conc: true,
created_at: "2022-12-11T17:18:33.745Z",
updated_at: "2022-12-11T17:18:33.745Z",
game_id: 1,
},
],
},
{
id: 2,
code: "BT526",
name: "Random game",
created_at: "2022-12-07T10:14:16.948Z",
updated_at: "2022-12-07T10:14:16.948Z",
slug: null,
fight: false,
players: [],
},
];
As you can see, I have one player inside the game with id: 1
When I fetch this data with axios in my React-native app I get all the info about games:
Output in console:
[
{
code: "KR984",
created_at: "2022-12-02T13:34:51.992Z",
fight: false,
id: 1,
name: "Hello world",
players: [[Object]],
slug: null,
updated_at: "2022-12-07T10:32:35.827Z",
},
{
code: "BT526",
created_at: "2022-12-07T10:14:16.948Z",
fight: false,
id: 2,
name: "Random game",
players: [],
slug: null,
updated_at: "2022-12-07T10:14:16.948Z",
},
];
However, as you can see I can't get any data related to players. It's just an [[Object]].
What am I missing. And sorry, if it's a stupid question - I am a newbie.
That is how tried to fetch data firstly:
const [isLoading, setLoading] = useState(true);
const [data, setData] = useState([]);
useEffect(() => {
axios
.get("http://localhost:3000/api/v1/games")
.then(({ data }) => {
console.log(data);
setData(data);
})
.catch((error) => console.error(error))
.finally(() => setLoading(false));
}, []);
Then I read that the problem might be solved with an asyns/await function, but got the same result:
const [isLoading, setLoading] = useState(true);
const [testData, setTestData] = useState(null);
async function fetchData() {
try {
const response = await axios.get("http://localhost:3000/api/v1/games");
setTestData(response.data);
console.log(testData);
} catch (err) {
const errorMessage = "Error: " + err.message;
setError(errorMessage);
console.log(errorMessage);
} finally {
setLoading(false);
}
}
useEffect(() => {
fetchData();
}, []);

How to add items to an array according to its category?

I have a page that is a simulation of a clothing store. The list of clothes displayed is divided into five different categories.
The user has the option to click on the Add Clothes button and add a new clothes to the list that already exists.
My question is how to add a new item to the clothing array according to the category that the user chose?
For example, if he chooses to add a hat, add the new item to the hat collection within the array.
Here''s my code I put in CodeSandBox: https://codesandbox.io/s/stupefied-flower-1ij9v?file=/src/App.js
Here's my shop data:
const shop_data = [{
id: 1,
title: "Hats",
routeName: "hats",
items: [{
id: 1,
name: "Brown Brim",
imageUrl: "https://i.ibb.co/ZYW3VTp/brown-brim.png",
price: 25
},
{
id: 2,
name: "Blue Beanie",
imageUrl: "https://i.ibb.co/ypkgK0X/blue-beanie.png",
price: 18
},
{
id: 3,
name: "Brown Cowboy",
imageUrl: "https://i.ibb.co/QdJwgmp/brown-cowboy.png",
price: 35
}
]
},
{
id: 2,
title: "Sneakers",
routeName: "sneakers",
items: [{
id: 10,
name: "Adidas NMD",
imageUrl: "https://i.ibb.co/0s3pdnc/adidas-nmd.png",
price: 220
},
{
id: 11,
name: "Adidas Yeezy",
imageUrl: "https://i.ibb.co/dJbG1cT/yeezy.png",
price: 280
},
{
id: 12,
name: "Black Converse",
imageUrl: "https://i.ibb.co/bPmVXyP/black-converse.png",
price: 110
}
]
},
{
id: 3,
title: "Jackets",
routeName: "jackets",
items: [{
id: 18,
name: "Black Jean Shearling",
imageUrl: "https://i.ibb.co/XzcwL5s/black-shearling.png",
price: 125
},
{
id: 19,
name: "Blue Jean Jacket",
imageUrl: "https://i.ibb.co/mJS6vz0/blue-jean-jacket.png",
price: 90
},
{
id: 20,
name: "Grey Jean Jacket",
imageUrl: "https://i.ibb.co/N71k1ML/grey-jean-jacket.png",
price: 90
}
]
},
{
id: 4,
title: "Womens",
routeName: "womens",
items: [{
id: 23,
name: "Blue Tanktop",
imageUrl: "https://i.ibb.co/7CQVJNm/blue-tank.png",
price: 25
},
{
id: 24,
name: "Floral Blouse",
imageUrl: "https://i.ibb.co/4W2DGKm/floral-blouse.png",
price: 20
},
{
id: 25,
name: "Floral Dress",
imageUrl: "https://i.ibb.co/KV18Ysr/floral-skirt.png",
price: 80
}
]
},
{
id: 5,
title: "Mens",
routeName: "mens",
items: [{
id: 30,
name: "Camo Down Vest",
imageUrl: "https://i.ibb.co/xJS0T3Y/camo-vest.png",
price: 325
},
{
id: 31,
name: "Floral T-shirt",
imageUrl: "https://i.ibb.co/qMQ75QZ/floral-shirt.png",
price: 20
},
{
id: 32,
name: "Black & White Longsleeve",
imageUrl: "https://i.ibb.co/55z32tw/long-sleeve.png",
price: 25
}
]
}
];
export default shop_data;
Can someone help me? Thank you in advance.
Pass a callback function to DialogSelect which will take the item and the category as parameters, then update the state.
In ShopPage create an addItem function
addItem = (item, category) => {
this.setState((prevState) => ({
collections: prevState.collections.map((collection) => {
if (collection.routeName === category) {
return {
...collection,
items: collection.items.concat({
...item,
// setting the id to the length of the array
// can cause problems, use an id generator of
// some sort
id: collection.items.length + 1
})
};
}
return collection;
})
}));
};
then pass it to DialogSelect
<DialogSelect addItem={this.addItem} />
In DialogSelect call the callback from props when the user is adding an item
const addClothes = () => {
const item = { product, price, imageUrl };
addItem(item, category);
};
sandbox
I see two solutions, the first one would be to pass the results from your DialogSelect component to the parent (ShopPage) by using a callback in the props:
<DialogSelect onAddClothes={this.onAddClothes} />
Then, in this.onAddClothes function, you can update your collections through a setState() call. Use a findIndex on collections to find the object having "hats" as a routeName and add your item in the items array with a push() call.
The second solution would be to use Redux in order to have a general store for your application (this could be interesting especially for a shopping website).
Hi i made a few changes to your code.
https://codesandbox.io/s/vigorous-babbage-pwmn1
You need to update your state when data send for this I made a function which update the state by category. If the category matches then add the item in the list
addNewClothes = (clothes) => {
const updatedCollections = this.state.collections;
updatedCollections.map((collection) => {
//if collection routerName eques to category
// then update the collection
if (collection.routeName === clothes.category) {
//add new Item in items
collection.items.push({
id: collection.items.length + 1,
name: clothes.product,
price: clothes.price,
imageUrl: clothes.imageUrl
});
//return updated collection
return collection;
} else {
// return original collection
return collection;
}
});
//update the state
this.setState({
collections: updatedCollections
});
};

How to populate a flat array in javascript

My goal is to create a tree-like structure that automatically orders the following data.
The subSkills property contains a list of id references to other skills.
[
{
name: "chess";
subSkills: [];
parentId: "games";
_id: "chess";
},
{
name: "games";
subSkills: ["chess",...];
parentId: "";
_id: "games";
},
]
export default interface ISkill {
name: string;
subSkills: string[] | ISkill[];
parentId: string;
_id: string;
}
The result should be something like this.
[
{
name: "games";
subSkills: [
{
name: "chess";
subSkills: [{}...];
parentId: "games";
_id: "chess";
}
];
parentId: "";
_id: "games";
}, ... {}
]
I should note that the function has to be able to handle any level of depth. As I have no experience with
stuff like this, I would appreciate it if anyone could describe their way of thinking.
Thanks in advance.
EDIT: I have multiple trees/roots in the database. So multiple skills have a "" as parentId.
Based to my understanding, each object of the given array will be either a root or a child of another root object. I don't know if you mistakenly filled the subSkills array of an object or is it something to be considered so should be replaced with the whole object if found? per my assumption I just don't have subSkills as strings for now, I set all subSkills to empty array to start with, let me know if this something that should be considered please. otherwise you can actually just see if the string is met then you can remove it from the array and replace it with the child object itself.
Here's my solution:
const givenArray = [
{
name: "chess",
subSkills: [],
parentId: "games",
_id: "chess",
},
{
name: "games",
subSkills: [],
parentId: "",
_id: "games",
},
{
name: "programming dev",
subSkills: [],
parentId: "chess",
_id: "programming",
},
{
name: "basketball 01",
subSkills: [],
parentId: "chess",
_id: "basketball",
},
];
const skillsAggregator = (skills) => {
const newSkills = [...skills];
newSkills.forEach((skill) => {
if (!!skill.parentId.length) {
addSubSkills(newSkills, skill);
}
});
return newSkills;
};
const addSubSkills = (skills, currentSkill) => {
for (let i = 0; i < skills.length; i++) {
const skill = skills[i];
if (currentSkill.parentId === skill._id) {
skill.subSkills.push(currentSkill);
break;
}
}
};
console.log(JSON.stringify(skillsAggregator(givenArray), null, 2));
NOTE
If you can update your data structure (and you can) to a Map or a literal object, the algorithm will be faster than with array, here's an example with extra deep nesting level:
const givenArray = {
chess: {
name: "chess",
subSkills: [],
parentId: "games",
_id: "chess",
},
games: {
name: "games",
subSkills: [],
parentId: "",
_id: "games",
},
programming: {
name: "programming dev",
subSkills: [],
parentId: "chess",
_id: "programming",
},
basketball: {
name: "basketball 01",
subSkills: [],
parentId: "chess",
_id: "basketball",
},
football: {
name: "football",
subSkills: [],
parentId: "basketball",
_id: "football",
},
};
const skillsAggregator = (skills) => {
const newSkills = { ...skills };
Object.entries(newSkills).forEach(([id, skill]) => {
if (!!skill.parentId.length) {
newSkills[skill.parentId].subSkills.push(skill);
}
});
return newSkills;
};
console.log(JSON.stringify(skillsAggregator(givenArray), null, 2));

Delete multiple items in one array in react state from another array

I have this array
let deleted = [
{id: '123', name: 'Something'},
{id: '321', name: 'Something1'}
];
and I have this
this.setState({
config: {
...this.state.config,
categories: this.state.config.categories.map(cat => ({
...cat,
movies: [...cat.movies, ...currentMovies]
}))
}
});
Every movies array for every category contains all items from deleted array but are not the same arrays because some contains selected property and some not, but movie id's are the same.
How can I delete every item from delete array from every categories.movies array?
I am thinking to iterate through deleted and then for every item in that array do
movies: this.state.config.categories.filter(item => item.id !== deleted.id)
But i do not know if that's a best solution, can someone help?
Thanks in advance
This should work. filter the movies as well with map.
const filt = (item) => item.id !== deleted.id;
this.state.config.categories.filter(filt).map(({ movies, ...rest }) => ({
...rest,
movies: movies.filter(filt),
}));
It isn't very clear what the state structure is, but to filter the movies array by checking against an array of movies to delete can be done a couple ways using an array::filter function.
Search the deleted array each iteration and check if the movie id's match (O(n) linear search). If no match found then include in the result.
movies.filter(movie => !deleted.some(({ id }) => movie.id === id))
let deleted = [
{ id: "123", name: "Something" },
{ id: "321", name: "Something1" }
];
const currentState = {
movies: [
{ id: "120", name: "Something 120" },
{ id: "121", name: "Something 121" },
{ id: "122", name: "Something 122" },
{ id: "123", name: "Something" },
{ id: "124", name: "Something 124" },
{ id: "125", name: "Something 125" },
{ id: "321", name: "Something1" }
]
};
const nextState = currentState.movies.filter(
movie => !deleted.some(({ id }) => movie.id === id)
);
console.log(nextState)
Create a map of deleted movie ids so you don't have to search each time (O(1) constant-time search).
const deletedMovieIdSet = deleted.reduce((ids, { id }) => {
ids.add(id);
return ids;
}, new Set());
...
movies.filter(movie => !deletedMovieIdSet.has(movie.id))
let deleted = [
{ id: "123", name: "Something" },
{ id: "321", name: "Something1" }
];
const deletedMovieIdSet = deleted.reduce((ids, { id }) => {
ids.add(id);
return ids;
}, new Set());
const currentState = {
movies: [
{ id: "120", name: "Something 120" },
{ id: "121", name: "Something 121" },
{ id: "122", name: "Something 122" },
{ id: "123", name: "Something" },
{ id: "124", name: "Something 124" },
{ id: "125", name: "Something 125" },
{ id: "321", name: "Something1" }
]
};
const nextState = currentState.movies.filter(
movie => !deletedMovieIdSet.has(movie.id)
);
console.log(nextState)

Multidimensional state update in reactjs

I am working with a complex state object in reactjs and I am unable to update a particular item of the state.
state = {
order: {
jobs: [
{
id: "1",
jobId: "31147-02",
services: [
{
id: 18,
price: "100",
},
{
id: 19,
price: "100",
},
{
id: 65,
price: "3200",
},
{
id: 87,
price: "1800",
},
{
id: 88,
price: "1350",
},
{
id: 89,
price: "1350",
},
],
},
{
id: "2",
jobId: "31147-02",
services: [
{
id: 45,
price: ""
},
{
id: 41,
price: "",
},
{
id: 28,
price: "",
},
],
},
],
},
};
And trying to handle delete functionality
Following code of is receiving the index of JOB and the object of service which needs to be removed from the state to perform delete functionality.
handleJobServiceDelete = (jobId, service) => {
let jobs = [...this.state.order.jobs[jobId].services];
jobs = jobs.filter((s) => s.id !== service.id);
console.log(jobs);
this.setState({ jobs });
};
But I am unable to setState with updated service array after removing object from it.
You'd need to use this.setState({ order: { jobs } });, but that wouldn't work because React won't set the state fully, so it takes a bit more.
this.setState((prevState)=>({ order: {...prevState.order, jobs } }));
The way I would ordinarily manage deeply nested state like this is using https://github.com/kolodny/immutability-helper as it makes it much easier to manage.
Edit: Fixed the full issue using immutability helper. There were different issues than I originally noticed. I didn't realize due to the variable names that the data being altered was the job's services and so had to go back into the job's services rather than replacing the jobs.
import update from 'immutability-helper';
this.setState((prevState) => {
const idx = prevState.order.jobs[jobId].services.findIndex(
(s) => service.id === s.id
);
return {
order: update(prevState.order, {
jobs: { [jobId]: { services: { $splice: [[idx, 1]] } } },
}),
};
});

Categories