Map object with URL to return object - javascript

I loop through this array like this:
{props.choosenMovie.characters.map((characters) => (
<p>{characters}</p> /* This displays the URL of course */
))}
These URL's include a name object which is what i want to display,
what is the best practice to do this?
This is how it is displayed on my application, but the desire is to display the name object from the URL's.

In useEffect, map thru your array of urls and make the api call and store the promises in an array. Use promise.all and update the state which will cause re-render.
In render method map thru the updated state and display the names.
see working demo
Code snippet
export default function App() {
const [char, setChar] = useState([
"https://swapi.dev/api/people/1/",
"https://swapi.dev/api/people/2/"
]);
const [people, setPeople] = useState([]);
useEffect(() => {
const promiseArray = [];
char.forEach(c => {
promiseArray.push(fetch(c).then(res => res.json()));
Promise.all(promiseArray).then(res => {
console.log("res", res);
setPeople(res);
});
});
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{people.map((p, i) => {
return <p key={i}>{p.name}</p>;
})}
</div>
);
}

I was working with that API some time ago, and the way I approached it (to display the names etc) was with Promise.all
so the snipped looked like
axios.get(`https://swapi.dev/api/${this.props.match.path.split('/')[1]}/${this.props.match.params.id}/`).then((res) => {
let characters = []
// get all characters in the movie data
let characterPromises = []
res.data.characters.forEach((character) => {
characterPromises.push(axios.get(character))
})
// Create list with all characters names and link to page
Promise.all(characterPromises).then((res) => {
res.forEach((character, i) => {
characters.push(<li key={i}><Link to={`/${character.data.url.split('api/')[1]}`}>{character.data.name}</Link></li>)
})
this.setState({
characters
})
})
})
}
then I just used the characters lists (from state) in the render method

Related

Can't update array state using information from another state

I have two state objects. One is personnel, an array of 1-object arrays like this: [[{}],[{}],[{}],[{}],...]. Another is rowItems which I am trying to fill by pulling out all the objects from the inner arrays of the big personnelarray.
My end goal is to use the rowItems to create a material-ui data grid. Right now, the data grid is empty and not rendering any data, but shows the correct number of personnel items I expect (253) in the pagination display, which is weird.
Here's my code:
const [personnel, setPersonnel] = useState([]);
const [rowItems, setRowItems] = useState([]);
const handleCallback = (data) => {
setPersonnel((prevData) => [...prevData, data]);
};
useEffect (() => {
console.log("personnel:", personnel) // I see all 253 arrays printed
setRowItems((rowItems => [...rowItems, {id: '59686', first_name: 'vbn',}])) // This was for testing only, somehow hardcoding this works
personnel?.map((row) => {
console.log("row", row[0]); // I see the item being printed
setRowItems(rowItems => [...rowItems, row[0]]);
console.log("row items", rowItems) // this is empty. WHYYYY
})
}, [personnel])
return (
<div> // This is where I get personnel items and pass to callback
{props.personnel.edges.map(({ node }) => {
return (
<Personnel
key={node.__id}
personnel={node}
parentCallback={handleCallback}
/>
);
})}
</div>
<DataGrid
columns={cols}
rows={rowItems}
pageSize={12}
/>
)
I took jsN00b's suggestion and tried to move the setRowItems() outside of the map function like so:
useEffect(() => setRowItems(prev => ([ ...prev, ...personnel?.map(row => ({...row[0]}))])), [personnel]);
and it worked! Thanks a million!

Javascript how to return data from object with matching key

I have an array of objects each with name, height and mass. I have a component that gets the names and displays them to the browser. What I'm trying to do is, in a separate component, get the height and mass that correlates to each name.
For example I have:
[
{name: 'Luke Skywalker', height: '172', mass: '77'},
{name: 'C-3PO', height: '167', mass: '75'}
]
I should mention I'm using react for this project. My Component is below:
export default function Character(props) {
const [charStats, setCharStats] = useState("");
const [currentName, setCurrentName] = useState("");
const { name } = props;
useEffect(() => {
axios.get(`${BASE_URL}`)
.then(res => {
setCharStats(res.data);
setCurrentName(name);
})
.catch(err => console.error(err))
}, [])
return (
<div>
<div>{ name }</div>
<button>Stats</button>
{ name === currentName ? charStats.map(char => {
return <Stats height={char.height} mass={char.mass} key={char.name} />;
}) : <h3>Loading...</h3>
}
</div>
)
}
The name prop I am getting from another component, I can console.log it and get each individual name so I know that works. But with the code above, I am getting the height and mass of every object returned instead of just the ones that match the name. How can I get specifically the height and mass of each object?
Looks like you might want to call filter before using map, like for example: data.filter(x => x.name === name).map(char => {.... which returns a collection that only contains the elements that match the condition). Or if you only want to find one element, its better to use .find(x => x.name === name) instead

Saving api response to State using useState and Axios (React JS)

I'm having an issue when trying to save to State an axios API call. I've tried
useState set method not reflecting change immediately 's answer and many other and I can't get the state saved. This is not a duplicate, because I've tried what the accepted answer is and the one below and it still doesn't work.
Here's the (rather simple) component. Any help will be appreciated
export const Home = () => {
const [widgets, setWidgets] = useState([]);
useEffect(() => {
axios
.get('/call-to-api')
.then((response) => {
const data = response.data;
console.log(data); // returns correctly filled array
setWidgets(widgets, data);
console.log(widgets); // returns '[]'
});
}, []); // If I set 'widgets' here, my endpoint gets spammed
return (
<Fragment>
{/* {widgets.map((widget) => { // commented because it fails
<div>{widget.name}</div>;
})} */}
</Fragment>
);
};
Welcome to stackoverflow, first thing first the setting call is incorrect you must use spread operator to combine to array into one so change it to setWidgets([...widgets, ...data]); would be correct (I assume both widgets and data are Array)
second, react state won't change synchronously
.then((response) => {
const data = response.data;
console.log(data); // returns correctly filled array
setWidgets(widgets, data);
console.log(widgets); // <--- this will output the old state since the setWidgets above won't do it's work till the next re-render
so in order to listen to the state change you must use useEffect hook
useEffect(() => {
console.log("Changed Widgets: ", widgets)
}, [widgets])
this will console log anytime widget changes
the complete code will look like this
export const Home = () => {
const [widgets, setWidgets] = useState([]);
useEffect(() => {
axios
.get('/call-to-api')
.then((response) => {
const data = response.data;
setWidgets([...widgets, ...data])
});
}, []);
useEffect(() => {
console.log("Changed Widgets: ", widgets)
}, [widgets])
return (
<Fragment>
{/* {widgets.map((widget) => { // commented because it fails
<div>{widget.name}</div>;
})} */}
</Fragment>
);
};
Try:
setWidgets(data);
istead of
setWidgets(widgets, data);
Your widgets.map() probably fails because there isn't much to map over when the component is being rendered.
You should update it with a conditional like so, just for clarity:
widgets.length>0 ? widgets.map(...) : <div>No results</div>
And your call to setWidgets() should only take one argument, the data:
setWidgets(data)
or if you want to merge the arrays use a spread operator (but then you need to add widgets as the dependency to the useEffect dependency array.
setWidgets(...widgets, ...data)
You might also have to supply the setWidgets hook function to the useEffect dependency array.
Let me know if this helps..

How to limit access for User and show specific menu parts?

I have a component where through map I show all menu parts. How can I limit access(make it hidden) if user right for that part of menu is equal to 0?
const Aside: React.FunctionComponent = () => {
const[hasRight, setHasRight] = useState(false);
useEffect( () => {
getUserContext()
.then((response) => {
Object.keys(response.data.acl.rights).forEach(role => {
const right = response.data.acl.rights[role];
Object.keys(right).forEach(val => {
right[val] > 0 ?
setHasRight(true) :
setHasRight(false);
});
});
return response.data.acl;
})
}, [hasRight]);
return (
<div className="Aside">
{hasRight?
pages.map((page, index) => {
return (
<Link className="Aside__link" key={index} to={page.link}>
<img className="Aside__link-image" src={page.img} alt="page__img" />
<p>{page.title}</p>
</Link>
);
}): null}
</div>
);
};
In this part I hide all menu if it does not have access. I need to check each menu item to see if there is access to it.
Each user has many different rights for many different pages, so instead of creating hasRight as a boolean state, you need to store a keyed object showing whether they have rights for each page. I'm assuming that if the user has multiple roles, they would have rights if any of their roles have rights.
type UserRights = Record<string, boolean>;
const [userRights, setUserRights] = useState<UserRights>({});
On the rendering side of things, we need to check whether they have the right for each page separately. Instead of a ternary with null, we can use array.filter() to filter the pages before mapping them to JSX.
const canView = (page: PageConfig): boolean => {
return userRights[page.acl];
}
<div className="Aside">
{pages.filter(canView).map((page, index) => {
Prior to the response being loaded, there should be no viewable pages.
Inside our useEffect, we need to change the setState callbacks to support our keyed object format. Instead of using Object.keys to iterate, we can use Object.values and Object.entries (provided that your tsconfig has lib: es2017 or newer). I think this is correct, but you may need to tweak based on your response structure.
useEffect(() => {
getUserContext()
.then((response: Response) => {
Object.values(response.data.acl.rights).forEach(roleRights => {
Object.entries(roleRights).forEach(([name, number]) => {
setUserRights(existing => ({
...existing,
[name]: existing[name] || number > 0 // true if true true for any role
}))
});
});
})
}, []);
The dependency for the useEffect should be the user id or something like that, otherwise use an empty array [] to run it once.
Typescript Playground Link

How do I set an array of objects to state in React?

So I am doing an SQL query in my node/express backend and sending the result to my React front end through JSON. I am console.log to see what info I am sending and that seems to be in order. However, I can't seem to figure out how to set this array of objects to my state. I am getting this error:
"Objects are not valid as a React child (found: object with keys {Title}). If you meant to render a collection of children, use an array instead."
Here is the component I am working with:
import React, { Component } from 'react'
export class ShoppingCart extends Component {
state = {
cartItems: ['Title 1']
};
displayCart = () => {
console.log('call it')
console.log('hello from displaycart');
fetch('http://localhost:5000/fillCart')
.then((res) => res.json())
.then((json) => {
this.setState(state => {
const cartItems = state.cartItems.concat(json.Title);
return {
cartItems
};
});
})
}
componentDidMount() {
this.displayCart();
}
render() {
return (
<div className="ShoppingCart">
<h3>This is your shopping cart</h3>
<ul>
{this.state.cartItems.map(item => (
<li key={item}>{item}</li>
))}
</ul>
</div>
)
}
}
export default ShoppingCart
My goal is to set the json.Title (right now holding multiple titles) into the cartItems State array I have in my state, then display each title through render() in a list. However, I am stuck as to how to accomplish this.
Also, here is what the json data looks like that I send from the Node/express backend:
[
RowDataPacket { Title: 'The Tragedy of The Korosko' },
RowDataPacket { Title: 'Two in the Far North' }
]
To clarify, at this point my problem is not displaying the information, but rather setting it to the state in the first place.
Any suggestions sure would be appreciated! I've been stuck on this for several days. Thanks in advance!!!
You need to extract all titles from the json response.
This is what the response format looks like:
{
Title: [{Title: ''}, {Title: ''}]
}
Your top level json.Title is an array with objects.
So the .Title property does not exist on json.Title, but on each of the objects inside of the array.
We can use map to pull out what we need.
Your setState could look something like this:
this.setState(state => {
// All titles in an array
const titles = json.Title.map(row => row.Title)
// Add to existing ones
const cartItems = state.cartItems.concat(titles);
return {
cartItems
};
});
Oskar Hane had an answer that was pretty close. Here is his answer with a slight modification:
.then((json) => {
this.setState(state => {
const titles = json.Title.map(row => row.Title)
const cartItems = state.cartItems.concat(titles);
return {
cartItems
};
});
})
}
The change is in json.Title.map. It is necessary to map through an array; however, json.map is not mapping through an array because json is not an array. json.Title is the array.
Use Array Spreading with current state and the JSON in response. Code will be like bellow:
displayCart = () => {
fetch('http://localhost:5000/fillCart')
.then((res) => res.json())
.then(json => {
const newItemsArry = json.map(itm=> itm.Titles)
this.setState({cartItems: [...this.state.cartItems, newItemsArry]}))
}
}

Categories

Resources