JavaScript Dropdown Box Issue - Array Map not working - javascript

I'm new to JavaScript and I am having a massive problem trying to populate a dropdown box with values that are getting queried from a SQL database and returned as a JSON file. My server code seems to work well querying the DB and if I hit the server directly it gives me the following JSON data:
[{
"key": "1AEFF22E-7A2C-4920-B72A-255119E785A8",
"value": "ExampleSSRSProject"
}, {
"key": "5A8AE6D3-4A96-4048-9207-6DDDA5B7D19E",
"value": "MyReportPackage"
}, {
"key": "EA2CD590-FA01-4094-86EE-414C860E597A",
"value": "CoverSheet"
}]
However, when I run the client page code I just get the error:
"Warning: Each child in a list should have a unique "key" prop".
Below I have listed my code from the server.js, client componentDidMount(), and the render.
app.get('/api/reportList', (req,res) => {
connection.connect(err=>{
if(err){
console.log(err);
res.statusCode = 200;
res.setHeader('content','text/plain');
res.end(err);
}
else{
var sqlrequest = new sql.Request(connection);
sqlrequest.query("select ItemId as 'key',Name as 'value' FROM
ReportServer$SQL2014.dbo.Catalog where Name <> ''",(err,result)=>{
if(err){
console.log(`SQL Error`);
res.statusCode = 200;
res.setHeader('content','text/plain');
res.end("SQL Error");
}
else{
console.log(result);
res.statusCode = 200;
res.setHeader('content','text/plain');
res.json(result.recordset);
connection.close();
}
})
}
})
});
componentDidMount() {
fetch("api/reportList")
.then((response) => {
return response.json();
})
.then(data => {
let reportsFromApi = data.map((report,index) => {
return {key: {index}, display: report}
});
this.setState({
reports: reportsFromApi
});
}).catch(error => {
console.log(error);
});
render() {
return (
<div>
<select>
{this.state.reports.map((report) => <option key={report.ItemId} value={report.Name}
>{report.Name}</option>)}
</select>
</div>
)
}
import React, {Component} from 'react';
import logo from './logo.svg';
import './App.css';
import ReportList from './components/reportList/reportList';
class App extends Component {
render() {
return (
<div className="App">
<ReportList/>
</div>
);
}
}
export default App;

I think that is because in your React component, you use the ItemId attribute as the "key" prop.
{this.state.reports.map((report) => <option key={report.ItemId} value={report.Name}>...</option>)}
The problem is that your "report" objects that are passed from your server code to your client code don't seem to have an "ItemId" property. That means all of your option elements are passed a "key" property that is equal to undefined and therefore, that is not unique.
You could use the "key" attribute of your "report" objects though, like this:
{this.state.reports.map((report) => <option key={report.key} value={report.Name}>...</option>)}

Related

React: Accessing an objects properties within an array that I have mapped

I'm trying to make my first real React app and am pulling information from a database, updating the state to set that info as an array, and then trying to access the properties of the objects in the array.
function App() {
const [students, setStudents] = useState([]);
function fetchStudentInfo() {
fetch('https://api.hatchways.io/assessment/students')
.then(response => {
return response.json();
})
.then(data => {
const transformedStudentData = data.students.map(studentData => {
return {
imgURL: studentData.pic,
fullName: `${studentData.firstName} ${studentData.lastName}`,
email: studentData.email,
company: studentData.company,
skill: studentData.skill
}
});
setStudents(transformedStudentData);
});
}
fetchStudentInfo();
return (
<div> {console.log(students[0].fullName)}
<h1>Student Assessments</h1>
</div>
)
}
export default App;
I know I shouldn't be console.logging the info I'm trying to get, but I'm not even sure how to use the console in React to find out how to access my variables. Anyway, I get "TypeError: Cannot read properties of undefined (reading 'fullName')" as an error and it fails.
I'm really trying to pass down the array as properties to be used in my components, but I've taken out code to try and simplify the problem for myself and this is where I've hit a wall.
On the first render your students state is an empty array because that's how you've initialised it. On that first render you can't access the first index (because the data hasn't been fetched, or the state updated), and you can't access a property of an element that doesn't exist yet.
So you need a condition in there that renders: 1) if students.length is zero return a loading message (for example) 2) otherwise map over the students array which you now know exists to produce a list of student names.
Here's a working example that uses useEffect to add an array to state after three seconds simulating your API fetch. You should be using useEffect like this for your fetch (with an empty dependency array) instead of calling your function directly in the component.
const { useEffect, useState } = React;
const json = '[{ "fullName": "Bob" },{ "fullName": "Karen" },{ "fullName": "Rick" }]';
function mockApi() {
return new Promise((res, rej) => {
setTimeout(() => res(json), 3000);
});
}
function Example({ data }) {
const [ students, setStudents ] = useState([]);
useEffect(() => {
function getData() {
mockApi()
.then(json => JSON.parse(json))
.then(data => setStudents(data));
}
getData();
}, []);
// Initial log will be `[]`
// Second log will be the updated state stringified
useEffect(() => {
console.log(JSON.stringify(students));
}, [students]);
// No data, so return the loading message, or spinner
if (!students.length) return <div>Loading: wait 3 seconds</div>
// Data! `map` over it to produce the list
return (
<ul>
{students.map(student => {
return <li>{student.fullName}</li>;
})}
</ul>
);
};
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

GraphQL Query Crashing On "If" Working on "Else If"

I am trying to access a query from my frontend react page. When I call the query this
import React from "react";
import { useQuery } from "#apollo/react-hooks";
import { getBookQuery } from "../queries/queries";
export const BookDetails = (props) => {
const { loading, error, data } = useQuery(getBookQuery, {
skip: !props.bookId,
variables: { id: props.bookId },
});
let content;
if (loading) content = <p>Loading Detail...</p>;
if (error) {
content = <p>Error...</p>;
}
if (!props.bookId) {
content = <p>No book selected yet</p>;
} else {
console.log(data.book);
const book = data;
}
return (
<div id="hook-details">
</div>
);
When I run this code I see the error TypeError: Cannot read property 'book' of undefined
To fix this error I changed the above code to use else if statements instead of just if statements, but I am failing to understand why this fixes the bug. Even when I throw log statements in each if block the same code seems to be called at the same time.
Working Code:
import React from "react";
import { useQuery } from "#apollo/react-hooks";
import { getBookQuery } from "../queries/queries";
export const BookDetails = (props) => {
const { loading, error, data } = useQuery(getBookQuery, {
skip: !props.bookId,
variables: { id: props.bookId },
});
let content;
if (loading) content = <p>Loading Detail...</p>;
else if (error) {
content = <p>Error...</p>;
}
else if (!props.bookId) {
content = <p>No book selected yet</p>;
} else {
console.log(data.book);
const book = data;
}
return (
<div id="hook-details">
</div>
);
};
Thank you.
1st case
... when your component gets bookId prop following code works:
if (loading) content = <p>Loading Detail...</p>;
and
} else {
console.log(data.book);
const book = data;
}
data is undefined at start then error appears.
2nd case
... chained if else excludes the last fragment (console.log(data.book)) until data is loaded - loading OR data access, no both (AND)
When loading gets false then data contains book, it can be safely logged out.
PS. It's safer to use 'early return':
if( !props.bookId ) return <NoBookSelectedMessage /> // or null to show nothing
if( loading ) return <Loading/>
if( error ) return <Error />
// 'data' must be defined here
The problem is data value arrives asynchronously meanwhile you are expecting synchronously. Try using useEffect hook to catch changes on data after useQuery returned the value.
Like the following - by passing data into the dependency array:
useEffect(() => {
if (data) {
// your actions here
console.log(data.book);
}
}, [data]);
Suggested read: Using the Effect Hook
I hope this helps!

Fetch one JSON object and use it reactjs

I'm very new to JS and ReactJS and I try to fetch an endpoint which gives me a JSON Object like this :
{"IDPRODUCT":4317892,"DESCRIPTION":"Some product of the store"}
I get this JSON Object by this endpoint :
http://localhost:3000/product/4317892
But I dont how to use it in my react application, I want to use those datas to display them on the page
My current code looks like this but it's not working and I'm sure not good too :
import React, {Component} from 'react';
class Products extends Component {
constructor(props){
super(props);
this.state = {
posts: {}
};
};
componentWillMount() {
fetch('http://localhost:3000/product/4317892')
.then(res => res.json())
.then(res => {
this.setState({
res
})
})
.catch((error => {
console.error(error);
}));
}
render() {
console.log(this.state)
const { postItems } = this.state;
return (
<div>
{postItems}
</div>
);
}
}
export default Products;
In the console.log(this.state) there is the data, but I'm so confused right now, dont know what to do
Since I'm here, I have one more question, I want to have an input in my App.js where the user will be able to type the product's id and get one, how can I manage to do that ? Passing the data from App.js to Products.js which is going to get the data and display them
Thank you all in advance
Your state doesn't have a postItems property which is considered undefined and react therefore would not render. In your situation there is no need to define a new const and use the state directly.
Also, when you setState(), you need to tell it which state property it should set the value to.
componentWillMount() {
fetch('http://localhost:3000/product/4317892')
.then(res => res.json())
.then(res => {
this.setState({
...this.state, // Not required but just a heads up on using mutation
posts: res
})
})
.catch((error => {
console.error(error);
}));
}
render() {
console.log(this.state)
return (
<div>
<p><strong>Id: {this.state.posts.IDPRODUCT}</strong></p>
<p>Description: {this.state.posts.DESCRIPTION}</p>
</div>
);
}
I have got 3 names for the same thing in your js: posts, postItems and res.
React can not determine for you that posts = postItems = res.
So make changes like this:
-
this.state = {
postItems: {}
};
-
this.setState({
postItems: res
});
-
return (
<div>
{JSON.stringify(postItems)}
<div>
<span>{postItems.IDPRODUCT}</span>
<span>{postItems.DESCRIPTION}</span>
</div>
</div>
);
{postItems["IDPRODUCT"]}
Will display the first value. You can do the same for the other value. Alternatively, you can put
{JSON.stringify(postItems)}
With respect to taking input in the App to use in this component, you can pass that input down through the props and access it in this component via this.props.myInput. In your app it'll look like this:
<Products myInput={someInput} />

I cannot get into nested JSON object, but only sometimes

I am trying to fetch data from an API (the Magic the Gathring Scryfall API) that has a nested object while using ReactJS. As soon as I try to use data from a nested object, I get a "cannot read png of undefined". I figured out this was probably an async problem, and fixed it by changing the state of my initial array to null, then adding an if statement to the render, but as soon as I changed the API url from https://api.scryfall.com/cards?page=3 to https://api.scryfall.com/cards/search?order=cmc&q=c%3Ared+pow%3D3, I can no longer access the image urls in the nested object again, despite having a JSON in the same format returned to me as the first URL. I'm just at a loss now.
I tried using axios, and I tried putting the fetch into a separate function, then putting that function into componentDidMount, but no luck. Setting 'cards' to null and then putting the "if (cards === null) { return null; } into the render worked for the first link, but not the second.
import React,{Component} from 'react';
import logo from './logo.svg';
import './App.css';
import Login from './components/Login'
class App extends Component {
constructor() {
super()
this.state = {
cards: null,
isUpdated:false
}
}
componentDidMount() {
this.populateCards()
}
populateCards() {
let url = 'https://api.scryfall.com/cards/search?order=cmc&q=c%3Ared+pow%3D3 '
fetch(url)
.then(response => response.json())
.then(json => {
console.log("setting the state.")
console.log(json.data)
this.setState({cards: json.data})
})
}
render() {
const { cards } = this.state;
if (cards === null) {
return null;
}
let cards1 = this.state.cards
let cardItems = cards1.map((card) => {
return (
<li>{card.name} - {card.id}
<p></p><img src={card.image_uris.png}/></li>
)
})
return (
<div className="App">
<h1>HOME PAGE</h1>
<Login />
<ul>
{cardItems}
</ul>
</div>
)
}
}
export default App;
Just need to figure out what is going on with this JSON before I can move on to writing up some search boxes. Greatly appreciate any help that can be offered.
The JSON coming back looks like so:
{
"object": "list",
"total_cards": 248583,
"has_more": true,
"next_page": "https://api.scryfall.com/cards?page=4",
"data": [
{
"object": "card",
"id": "18794d78-5aae-42de-a45b-3289624689f1",
"oracle_id": "a6543f71-0326-4e1f-b58f-9ce325d5d036",
"multiverse_ids": [
463813
],
"name": "Gateway Plaza",
"printed_name": "門前廣場",
"lang": "zht",
"released_at": "2019-05-03",
"uri": "https://api.scryfall.com/cards/18794d78-5aae-42de-a45b-3289624689f1",
"scryfall_uri": "https://scryfall.com/card/war/246/zht/%E9%96%80%E5%89%8D%E5%BB%A3%E5%A0%B4?utm_source=api",
"layout": "normal",
"highres_image": false,
"image_uris": {
"small": "https://img.scryfall.com/cards/small/front/1/8/18794d78-5aae-42de-a45b-3289624689f1.jpg?1556241680",
"normal": "https://img.scryfall.com/cards/normal/front/1/8/18794d78-5aae-42de-a45b-3289624689f1.jpg?1556241680",
"large": "https://img.scryfall.com/cards/large/front/1/8/18794d78-5aae-42de-a45b-3289624689f1.jpg?1556241680",
"png": "https://img.scryfall.com/cards/png/front/1/8/18794d78-5aae-42de-a45b-3289624689f1.png?1556241680",
"art_crop": "https://img.scryfall.com/cards/art_crop/front/1/8/18794d78-5aae-42de-a45b-3289624689f1.jpg?1556241680",
"border_crop": "https://img.scryfall.com/cards/border_crop/front/1/8/18794d78-5aae-42de-a45b-3289624689f1.jpg?1556241680"
},
"mana_cost": "",
"cmc": 0,
Some object on your response does not have the image_uris property so it throw error.
Add these line
let filtered = cards1.filter(card => card.image_uris);
And then map over filtered array, you will get what you need
let cardItems = filtered.map(card => {
return (
<li>
<img src={card.image_uris.png}/>
</li>
);
});

Cannot get access to nested query

I want to render on the page, simple data from manual API, but it seems that I cannot get access to the nested query.
This is what I have:
import React from 'react';
import {
gql,
graphql,
} from 'react-apollo';
//access to queries and map function
const ChannelsList = ({ data: {loading, error, items }}) => {
if (loading) {
return <p>Loading ...</p>;
}
if (error) {
return <p>{error.message}</p>;
}
return (
<div className="channelsList">
{ items.map( itm => <div key={itm.id} className="channel">{itm.sku}</div> ) }
</div>
);
};
const channelsListQuery = gql`
query ChannelsListQuery {
allLinks {
items {
id
sku
}
}
}
`;
export default graphql(channelsListQuery)(ChannelsList);
Don't mind this "channel" names, this is from example that I used for my own api. The problem is, all the queries that contain "id, names, sku" etc is in "items" type and it looks like for me it cannot get access to it. The query in graphqli server is working perfectly. So, the question is- how to get access to nested queries, so that I can use map and render it on page?
Schema structure:
const typeDefs = `
type Link {
items: [itemapi]
}
type itemapi {
id: ID!
sku: String!
name: String!
}
type Query {
allLinks: Link!
}
`;
#Solution by #OzySky
const ChannelsList = ({ data: {loading, error, allLinks }}) => {
if (loading) {
return <p>Loading ...</p>;
}
if (error) {
return <p>{error.message}</p>;
}
return (
<div className="channelsList">
{ allLinks.items.map( aLitm => <div key={aLitm.id} className="channel">{aLitm.sku}</div> ) }
</div>
);
};
In react-apollo, query data is passed through a prop with the name of the query. So in your case you would access items through the data.allLinks prop.
If you have a highly nested return value from your query one way to access the nested object is to first turn it into an array with Object.values(). Then access the field the array items and their corresponding values with dot notation.
const myQuery = gql`
query {
users {
id
name
pet {
id
name
}
}
}
`;
// Inside Render function if using React ...
return users.map(({ id, name, pet }) => (
<div key={id}>
<ul>
<li>
{id} {name}
{Object.values({ pet })[0].name}
</li>
</ul>
</div>
));

Categories

Resources