Call useQuery after retrieving router query object (NextJS with GraphQL) - react-router

I am retrieving the query param in NextJS using:
const qry_flightNumber = Number(router.query.flightNumber);
Then i want to send a request to my GraphQL server by doing this:
PAGE: LaunchDetails.js
import React, { useEffect, useState } from "react";
import classNames from "classnames";
import gql from "graphql-tag";
import { useQuery } from "#apollo/react-hooks";
import PropTypes from "prop-types";
const LAUNCH_QUERY = gql`
query LaunchQuery($flight_number: Int!) {
launch(flight_number: $flight_number) {
flight_number
mission_name
launch_year
launch_success
launch_date_local
rocket {
rocket_id
rocket_name
rocket_type
}
}
}
`;
export default function LaunchDetails({ flightNum }) {
const { loading, error, data } = useQuery(LAUNCH_QUERY, {
variables: { flight_number: flightNum },
});
if (loading) return <p>Loading...</p>;
if (error) return <p>Error</p>;
console.log("data.launch: ", data.launch);
const flightNumber = data.launch.flight_number;
const launchDate = data.launch.launch_date_local;
const launchSuccess = data.launch.launch_success;
const launchYear = data.launch.launch_year;
const missionName = data.launch.mission_name;
const { rocket } = data.launch;
return (
<div>
<h1 className="display-4 my-3">
<span className="text-dark">Mission: {missionName}</span>
</h1>
<h2 className="mb-3">Launch details</h2>
<ul className="list-group">
<li className="list-group-item">Flight number: {flightNumber}</li>
<li className="list-group-item">Launch year: {launchYear}</li>
<li className="list-group-item">
Launch successful:{" "}
<span
className={classNames({
"text-success": launchSuccess,
"text-danger": launchSuccess === false,
})}
>
{launchSuccess ? "yes" : "no"}
</span>
</li>
</ul>
<h4 className="my-3">Rocket details:</h4>
<ul className="list-group">
<li className="list-group-item">Rocket ID: {rocket.rocket_id}</li>
<li className="list-group-item">Rocket name: {rocket.rocket_name}</li>
<li className="list-group-item">Rocket type: {rocket.rocket_type}</li>
</ul>
<hr />
</div>
);
}
LaunchDetails.propTypes = {
flightNum: PropTypes.number.isRequired,
};
Dynamic PAGE containing the component ([flight_number].js):
import { ApolloProvider } from "#apollo/react-hooks";
import React from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import APOLLO_CLIENT from "../../graphql/config";
import LaunchDetails from "../../components/launches/LaunchDetails";
export default function Home() {
const router = useRouter();
const flightNum = Number(router.query.flightNumber);
return (
<>
{/* <Head>
<title>SpaceX launch</title>
</Head> */}
<ApolloProvider client={APOLLO_CLIENT}>
<div className="container">
<h1 className="title">Apollo launch details</h1>
<LaunchDetails flightNum={flightNum} />
<Link href="/">
<a className="btn btn-secondary">Back</a>
</Link>
</div>
</ApolloProvider>
</>
);
}
The issue is have is that on first page load, the GraphQL query executes with flight_number being null. So i end up with this warning in console:
[GraphQL error]: Message: Variable "$flight_number" of non-null type "Int!" must not be null., Location: [object Object], Path: undefined
I have tried using an IF statement to make sure that flight_number is !NaN before running the query however react complains about the order of the hooks being called.
Obviously after the component has fully loaded, the GraphQl variable becomes defined and then executes correctly however I will still be getting the console warning.
Console warning screenshot:
What are my options to stop the console warning?
Thank you.

I have got an answer to this now. You can pass a skip parameter to the useQuery to only exec the query if the variable is defined or not NaN:
const { loading, error, data } = useQuery(LAUNCH_QUERY, {
variables: { flight_number: flightNum },
skip: Number.isNaN(flightNum),
});

Related

Data passing probelm between files in next js

So i have three files here :-
project.js
import Image from 'next/image';
import Link from 'next/link'
import { DATA } from '../../components/Data'
const WEB_RELATED = []
const PC_EXES = []
for(let i=0;i<DATA.length; i++){
if(DATA[i]['loc'] == 'WEB'){
WEB_RELATED.push(DATA[i])
}
else if(DATA[i]['loc'] == 'EXE'){
PC_EXES.push(DATA[i])
}
}
const WEB = WEB_RELATED.map(item =>
<div className="PROJECTS_Projects">
<div className="PROJECTS_Projects_Image">
<Image
className='PROJECTS_Projects_image'
src={item['img']}
layout='fill'
// objectFit='contain'
/>
</div>
{/* if someone clicks on this link i want them to go to [project].js and send This item to [projcet].js */}
<Link href={'/projects/' + WEB_RELATED.indexOf(item)}>
<a>{item['title']}</a>
</Link>
<p>{item['desc']}</p>
</div>
);
const PC = PC_EXES.map(item =>
<div className="PROJECTS_Projects">
<div className="PROJECTS_Projects_Image">
<Image
className='PROJECTS_Projects_image'
src={item['img']}
layout='fill'
// objectFit='contain'
/>
</div>
{/* if someone clicks on this link i want them to go to [project].js and send This item to [projcet].js */}
<Link href={'/projects/' + PC_EXES.indexOf(item)}>
<a>{item['title']}</a>
</Link>
<p>{item['desc']}</p>
</div>
);
export default function index() {
return (
<div className="PROJECTS_Container">
<div className="PROJECTS_Sub_Container">
<div className="PROJECTS_New">
<h1>Web-Related</h1>
<div className="PROJECTS_Present">
{WEB}
</div>
</div>
<div className="PROJECTS_New">
<h1>Pc apllications</h1>
<div className="PROJECTS_Present">
{PC}
</div>
</div>
</div>
</div>
)
}
Project Display page [project.js]
import { useRouter } from 'next/router'
import { useState } from 'react';
import Image from 'next/image';
import { DATA } from '../../components/Data';
import React, {useEffect} from 'react';
export default function title() {
const router = useRouter();
const { project } = router.query
const [state, setState] = useState()
// when user comes here i want the item sent to be read and injected into the html
useEffect(() => {
// project && console.log('value', project, 'length : ', project.length);
// setState(project)
console.log(DATA[project]['loc'])
}, [router])
return (
<div className='DISPLAY_Container'>
<div className="DISPLAY_Sub_Container">
<div className="DISPLAY_Image">
<Image
src={'/img/Projects/Cluq.PNG'}
layout='fill'
/>
</div>
<div className="DISPLAY_Info">
<h3>{state}</h3>
SUS
<p>lorem300</p>
</div>
</div>
</div>
)
}
DATA.JS file where i've kept all of the objects i want to be shown
export const DATA = [
{
'loc':'WEB',
'title':"Bukkit List",
'img':'/img/Projects/Bukkit-List.PNG',
'desc':"Bukkit-List is a easy to use and free task managing webapp, with password protection now!",
'fullDesc':""
},
{
'loc':'WEB',
'title':"SilenxIka-Github",
'img':'/img/Projects/SilenxIkaGithub.PNG',
'desc':"First github website for Project SilenxIka, which is completly made from vanilla HTML/CSS/JS",
'fullDesc':""
}
]
when someone presses a link in the first file (check there are coments above the link) the item in the link shall be passed to the second file and i can use the item in [project].js file to inject it into the html
You can append query parameters to your url and use the useRouter hook from "next/router" for getting the content of those query parameters in your other file.
Let's imagine your item object has an id and we want to append that id as a query parameter to your route:
In project.js:
export const arrayOfItems = [
{id: 'A', title: 'A title'},
{id: 'B', title: 'B title'},
{id: 'C', title: 'C title'},
{id: 'D', title: 'D title'},
];
// Add query param "item_id" plus the id of your item to your url (note the '?')
{arrayOfItems.map((item)=>
<Link href={'/projects?item_id= + item.id }>
<a>{item.title}</a>
</Link>
)
In your product display page:
import { useRouter } from "next/router";
import { arrayOfItems } from "./project.js"; // path to where array is exported from
const ProductPage = () => {
const router = useRouter();
useEffect(() => {
// here you have access to the query parameters
console.log(router.query?.item_id);
// get item_id from route
const routeItemId = router.query?.item_id;
// search through array and find item with matching id
const matchingItem = arrayOfItems.find((item)=> item.id === routeItemId && item);
console.log(matchedItem);
}, [router]);
return(<></>)
}

Uncaught TypeError: navigate.push is not a function

i tried to build my webapp but i have a little problem the reason is because the verson the instructor is old verson but i updated to the latest verson i have this problem.
What I am trying to do is generate clicks into the post. But I can't click on it. When I open it, this error is what I'm getting.
Home.js:23 Uncaught TypeError: navigate.push is not a function
at onClick (Home.js:23:1)
at HTMLUnknownElement.callCallback (react-dom.development.js:3945:1)
at Object.invokeGuardedCallbackDev (react-dom.development.js:3994:1)
at invokeGuardedCallback (react-dom.development.js:4056:1)
at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:4070:1)
at executeDispatch (react-dom.development.js:8243:1)
at processDispatchQueueItemsInOrder (react-dom.development.js:8275:1)
at processDispatchQueue (react-dom.development.js:8288:1)
at dispatchEventsForPlugins (react-dom.development.js:8299:1)
at react-dom.development.js:8508:1
this is the code
import React from "react";
import axios from "axios";
import { useEffect, useState } from "react";
import { useNavigate } from 'react-router-dom';
function Home() {
const [listOfPosts, setListOfPosts] = useState([]);
const navigate = useNavigate();
useEffect(() => {
axios.get("https://my-project-ienpf.run-us-west2.goorm.io/posts").then((response) => {
setListOfPosts(response.data);
});
}, []);
return (
<div>
{listOfPosts.map((value, key) => {
return (
<div
className="post"
onClick={() => {
navigate.push(`/post/${value.id}`);
}}
>
<div className="title"> {value.title} </div>
<div className="body">{value.postText}</div>
<div className="footer">{value.username}</div>
</div>
);
})}
</div>
);
}
export default Home;
I hope you can help me
You can change
navigate.push(`/post/${value.id}`);
to
navigate(`/post/${value.id}`);
don`t do this, navigate = useNavigate();
navigate.push("/about")
instead, navigate("/about")

Fetching data from local .json file in react.js returns multiple errors

I have a json file named autofill.json and it's created to autofill a search bar when pressed on.
the autofill.json is a test file that's why it looks like this.
[
{
"a": {
"apple": {
"name": "apple",
"href": "https://www.apple.com/"
},
"armadillo": {
"name": "armadillo",
"href": "https://www.armadillo.com/"
}
},
"b": {
"box": {
"name": "apple",
"href": "https://www.berserk.com/"
},
"berserk": {
"name": "berserk",
"href": "https://www.berserk.com/"
}
}
}
]
The .json file is then fetched in the file named FetchAndParseResults.js
import fetch from 'isomorphic-fetch'
const FetchAndParseResults = (url) => {
return fetch(url).then(response => {
const parsedJson = response.json()
return parsedJson
})
}
export default FetchAndParseResults
The data that gets fetched is used in searchcontainer.js where everything gets placed in, the search etc.
import React from 'react'
import Searchbar from './index.js'
import FetchAndParseResults from './FetchAndParseResults.js'
class SearchContainer extends React.Component {
state = {
results: []
}
performSearch = event => {
return FetchAndParseResults('static/autofill.json').then(data => {
this.setState({ results: data })
})
}
render () {
console.log('performSearch event', this.performSearch)
console.log('data inside performSearch', this.state.results)
return (
<Searchbar
performSearch={this.performSearch}
results={this.state.results}
/>
)
}
}
export default SearchContainer
Then to map through the data that is in autofill.json there is a file named autofill.js
import React from 'react'
import PropTypes from 'prop-types'
import Styles from './searchbar.scss'
const AutoFill = (props) => {
console.log('proppppppsss', props)
const results = props.results || []
return (
<ul className={Styles.searchUl}>
{results.map(({ name, href }) => (
<li className={Styles.searchLi} key={href}>
<a className={Styles.searchA} href={href} target='_blank' rel='noopener noreferrer' key={href}>
{name}
</a>
</li>
))}
</ul>
)
}
AutoFill.propTypes = {
results: PropTypes.array
}
export default AutoFill
the Searchbar component in (index.js) that is being used in searchcontainer.js
import React from 'react'
import Styles from './searchbar.scss'
import Icon from '../../components/icon/icon'
import Search from '../../components/form-input/search'
import AutoFill from './autofill'
import PropTypes from 'prop-types'
export default class Searchbar extends React.Component {
constructor (props) {
super(props)
this.state = {
className: Styles.input,
icon: Styles.icon__wrapper,
value: []
}
this.input = React.createRef()
}
openInput = () => {
this.setState({
className: Styles.input__active,
icon: Styles.iconWidth
}, () => {
this.input.focus()
})
this.props.onOpen && this.props.onOpen()
}
closeInput = () => {
this.setState({
className: Styles.input,
icon: Styles.icon__wrapper
})
this.props.onClose && this.props.onClose()
}
handleChange = event => {
let value = event.target.value
this.setState({ value })
this.props.performSearch(value)
}
handleSubmit = event => {
event.preventDefault()
}
render () {
console.log('results', this.props.results)
console.log('state.value', this.state.value)
return (
<div>
<form onSubmit={this.handleSubmit} className={Styles.search}>
<div className={this.state.icon}>
<Icon className={Styles.icon__wrapper} iconName='faSearch' onClick={this.openInput} />
</div>
<Search autoComplete='off' value={this.state.value} onChange={this.handleChange} id='search' tabIndex='0' myref={input => { this.input = input }} className={this.state.className} onBlur={this.closeInput} placeholder='Search' />
</form>
<div>
<AutoFill results={this.props.results} />
</div>
</div>
)
}
}
Search.propTypes = {
performSearch: PropTypes.func,
results: PropTypes.array
}
When i try to refer to a what is in the json file from the search i receive the error,
GET http://localhost:3000/[object%20Object] 404 (Not Found)
And
about:1 Uncaught (in promise) SyntaxError: Unexpected token < in JSON
at position 0
The second error is fixed by doing
const parsedJson = response.text(
instead of
const parsedJson = response.json()
to get more information where/what the error takes place. But by doing this i receive the error,
searchcontainer.js:12 Uncaught (in promise) TypeError: Cannot read property 'results' of undefined
I've tried to run it from npm build instead of running it in a dev environment which didn't fix it.
I read that a mock url should work but then again i want to acces it from a file and not from a url?
Any help would be highly appreciated and looked into.
The problem is most likely in the fetch call. If you look at the error message GET http://localhost:3000/[object%20Object] 404 (Not Found)
You can see that it is trying to append an object to the URL localhost:3000/.
You are getting the Unexpected token < in JSON at position 0 error because the response of your fetch request is probably a 404 page. The < is most likely the first char of <html>
To access the JSON object in your React files, you can simply do an importation like so;
import * as autofillData from 'autofill.json';
It will be returned as a JSON object.
I believe you are using the isomorphic-fetch package wrongly, if you look at their source code, https://github.com/matthew-andrews/isomorphic-fetch/blob/master/fetch-npm-node.js#L5 , they are accepting a URL to make a call to the API URL which will return a promise or a JSON object depending on the implementation of the API that you are calling.
If you were to dive deeper into the open-source code here (https://github.com/matthew-andrews/isomorphic-fetch/blob/master/fetch-npm-node.js#L8) , you will notice that isomorphic-fetch package is using another package node-fetch to do their fetch call, which accepts the API URL and the method request options to call the API with. (As stated here; https://github.com/bitinn/node-fetch/blob/master/src/index.js#L34)
To continue with your test, perhaps this might be the solution you'd prefer?
import fetch from 'isomorphic-fetch';
import * as autofillData from 'autofill.json'; //test data
const FetchResults = event => {
return fetch('/https://jsonplaceholder.typicode.com/todos/1'') //mockURL, to be replaced with real API
.then(response => {
// const parsedJson = response.json(); // TODO: un-comment this line when the real API url is usable
const parsedJson = autofillData; // TODO: remove this line when mocking is done and the real API URL is ready
return parsedJson;
})
}
export default FetchResults;
To have a mock URL placeholder, I would suggest https://jsonplaceholder.typicode.com/ to prevent your fetch result to return an unexpected error during test mocking.
Hope this is helpful.
The question has been solved, The main issue was with defining const names such as const results = [] which should've been const results = props.results || [].
The code has been updated incase you have problems aswell.

Understanding React.JS JSON feed and how to parse

I've got the following code which is looping through an JSON file from an API and loops through some posts.
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
//https://alligator.io/react/axios-react/
import axios from 'axios';
export default class PostList extends React.Component {
state = {
posts: []
}
componentDidMount() {
axios.get(`https://jsonplaceholder.typicode.com/users`)
.then(res => {
const posts = res.data;
this.setState({ posts });
})
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
Pulls in post slugs from Domain
</p>
<ul>
{ this.state.posts.map(post => <li>{post.name} - {post.username} </li>)}
</ul>
</div>
)
}
}
This works fine, and gets the information which was needed.
Now, in my test JSON file, the format is as follows:
https://jsonplaceholder.typicode.com/users
But in my actual JSON file from WordPress Rest API, we have another item, named core_layout:
JSON image
My issue is, trying to use the same code such as {post.name}does not get the information needed such as core_layout->image->name.
Is there an easy way around this?
Thanks all!
EDIT:
Tried the answers below, but still no luck, get the error TypeError: Cannot read property 'map' of undefined:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
//https://alligator.io/react/axios-react/
import axios from 'axios';
export default class PostList extends React.Component {
state = {
posts: [],
coreLayout: {}
}
componentDidMount() {
axios.get(`https://jsonplaceholder.typicode.com/users`)
.then(res => {
// const posts = res.data;
//this.setState({ posts });
const { posts, core_layout: coreLayout } = res.data;
this.setState({ posts, coreLayout });
})
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
Pulls in post slugs from domain
</p>
<ul>
{ this.state.posts.map(post => <li>{post.name} - {post.core_layout.image.name}</li>)}
</ul>
</div>
)
}
}
EDIT 2:
Tried the below: This gets the title, but again, not the actual corelayout I need.
import React, { Component } from 'react';
class App extends Component {
constructor() {
super();
this.state = {
movies: []
}
}
componentDidMount() {
let dataURL = "http://zinsseruk.com/wp-json/wp/v2/posts?per_page=1";
fetch(dataURL)
.then(res => res.json())
.then(res => {
this.setState({
movies: res
})
})
}
render() {
let movies = this.state.movies.map((movie, index) => {
return <div key={index}>
<p><strong>Title:</strong> {movie.title.rendered}</p>
<p><strong>Title:</strong> {movie.core_layout.acf_fc_layout}</p>
</div>
});
return (
<div>
<h2>Star Wars Movies</h2>
{movies}
</div>
)
}
}
export default App;
Replace const posts = res.data; with const posts = res.data.core_layout;. Then you'll get an array similar to what you have in your test file.
I think you need to understand the JSON structure you receive from the API. Where is located core_layout property? Inside each post property as a children?
So in the posts loop you can use post.core_layout.image.name for image name, for example (and so on with other properties).
If core_property is at the root of the data you receive, you can load it inside your state like so:
state = {
posts: [],
coreLayout: {}
}
componentDidMount() {
axios.get(`https://jsonplaceholder.typicode.com/users`)
.then(res => {
// This is equivalent of doing
// const posts = res.data.posts
// const coreLayout = res.data.core_layout
const { posts, core_layout: coreLayout } = res.data;
this.setState({ posts, coreLayout });
})
}
Then use it in your code by using local component state:
render() {
...
// For example image name:
console.log('image name', this.state.coreLayout.image.name)
...
}

Why I am getting `this.props.data is undefined` error?

I was egzecuting tutorial:
CLICK
And I am getting error: this.props.data is undefined.
I was implementing the tutorial in my test application, where I was testing also various React tools, so I have not copy-pasted it in 100%. I am using ASP.NET Core MVC and React, own architecture (for test application) and I did not installed all npm's from the tutorial. But I belive, that it is syntax or architecture problem. I am guessing, that calling server's data is corrupted somehow in app.js or CommentBox.js.
Error from console:
TypeError: this.props.data is undefined[Więcej informacji] bundle.js line 541 > eval:45:17
The above error occurred in the <CommentList> component:
in CommentList (created by CommentBox)
in div (created by CommentBox)
in CommentBox (created by App)
in div (created by App)
in div (created by App)
in App
Consider adding an error boundary to your tree to customize error handling behavior.
react-dom.development.js:14226
[Przełącz szczegóły wiadomości] TypeError: this.props.data is undefined[Więcej informacji]
Main app.js file that returns to index.js:
(...)
return (
<div className="App">
<div className="App-header">
Welcome to React!
<AddProject addProject={this.handleAddProject.bind(this)}/>
<Projects onDelete={this.handleDeleteProject.bind(this)} projects={this.state.projects} />
<CommentBox url="/comments" pollInterval={2000}/>
</div>
</div>
);
(...)
In my component folder all parent and children files:
CommentBox.js:
import React, { Component } from 'react';
import $ from 'jquery';
import uuid from 'uuid';
import CommentList from '../components/CommentList';
import CommentForm from '../components/CommentForm';
class CommentBox extends React.Component {
constructor(props) {
super(props);
this.state = { data: this.props.initialData };
}
loadCommentsFromServer() {
const xhr = new XMLHttpRequest();
xhr.open('get', this.props.url, true);
xhr.onload = () => {
const data = JSON.parse(xhr.responseText);
this.setState({ data: data });
};
xhr.send();
}
componentDidMount() {
this.loadCommentsFromServer();
window.setInterval(() => this.loadCommentsFromServer(), this.props.pollInterval);
}
render() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm/>
</div>
);
}
}
export default CommentBox;
CommentList.js:
import React, { Component } from 'react';
import $ from 'jquery';
import uuid from 'uuid';
import Comment from '../components/Comment';
class CommentList extends React.Component {
render() {
var commentNodes = this.props.data.map(function (comment) {
return (
<Comment name={comment.name} key={comment.productID}>
</Comment>
);
});
return (
<div className="commentList">
{commentNodes}
</div>
);
}
}
export default CommentList;
Comment.js:
import React, { Component } from 'react';
import $ from 'jquery';
import uuid from 'uuid';
class Comment extends React.Component {
rawMarkup() {
const md = new (global.Remarkable || window.Remarkable)();
const rawMarkup = md.render(this.props.children.toString());
return { __html: rawMarkup };
}
render() {
return (
<div className="comment">
<h2 className="commentName">
{this.props.name}
</h2>
{this.props.children}
</div>
);
}
}
export default Comment;
First, way too much code. Try to be as concise as possible.
Your issue is that this.state.data in CommentBox is undefined / null initially. Make sure that you're passing the initialData prop into CommentBox or handling the null case in CommentList
var commentNodes = (this.props.data || []).map(function (comment) {
return (
<Comment name={comment.name} key={comment.productID}>
</Comment>
);
});