I'm tying to find a way to correct the Hook rendering error. I have a total of 3 useQuery hooks being rendered :
const {
data: OSData,
error: OSError,
loading: OSLoading,
} = useQuery(OSData, {
variables: {
NUMBER: UniqueList,
},
})
const {
data: RamData,
error: RamERROR,
loading: RamLOADING,
} = useQuery(GET_Ram)
const {
data: Hardware,
error: HardwareERROR,
loading: HardwareLOADING,
} = useQuery(GET_Hardware)
The variable 'NUMBER' is based on a list 'UniqueList' that is made from the GET_Ram and GET_Hardware queries so the OSData query needs to be called later or there's an undefined variable. However, calling the OSData Query later in the code gives me a render error.
Any idea on how I could accomplish this?
Thank you!
an answer I found is using lazy query.
const SomeData [{
called, loading, data
}] = useLazyQuery(OSData)
})
if (called && loading) return <p>Loading ...</p>
if (HardwareLOADING || RamLOADING) return <p> loading</p>
if (HardwareERROR || RamERROR) return <p>error</p>
//perform all the needed calculations for the variable here
and in the return statement you can call the query and provide the variable. Here I use a button.
<div>
<button onClick={() => SomeData({ variables: { NUMBER: uniqueList } })}>
Load{' '}
</button>
</div>
Hope this helps someone
Related
I have two react queries in the same component
const { data: sdrData, status: sdrDataLoading } = useQuery(
[queryInfo[0]?.dcsSysId, data[0]?.dcsStructSysId, data[cardIndex]?.dcsStructNodeId],
() => getSDR(queryInfo[0]?.dcsSysId, data[0]?.dcsStructSysId, data[cardIndex]?.dcsStructNodeId),
);
const { isIdle, data: sdrTemplateData, status: sdrTemplateDataLoading } = useQuery(
[sdrData[0]?.dcsSdrSysId, queryInfo[0]?.fldTemplateSysId],
() =>
SDRTemplateValues(sdrData[0]?.dcsSdrSysId, queryInfo[0]?.fldTemplateSysId, {
// The query will not execute until the userId exists
enabled: !!sdrData[0]?.dcsSdrSysId,
retry: true,
}),
);
My second query is depended on the first I need to access sdrData[0] for the first arg in my query however when I do this the query is undefined initially and it fails. Is there a good way to handle this. I saw you can set it equal to a variable, but I'm still faced with the same problem.
I need a way to tell the first query to wait until the second query is finished before it tried to access the arguments. I thought you could set enabled like I did, but that didn't work either.
I was able to solve this by adding the data parameter I need to the beginning of the argument array like this.
const { data: sdrData, status: sdrDataLoading } = useQuery(
queryInfo[0]?.dcsSysId && [
queryInfo[0]?.dcsSysId,
data[0]?.dcsStructSysId,
data[cardIndex]?.dcsStructNodeId,
],
() => getSDR(queryInfo[0]?.dcsSysId, data[0]?.dcsStructSysId, data[cardIndex]?.dcsStructNodeId),
);
const { data: sdrTemplateData, status: sdrTemplateDataLoading } = useQuery(
sdrData?.[0]?.dcsSdrSysId && [sdrData?.[0]?.dcsSdrSysId, queryInfo[0]?.fldTemplateSysId],
() => SDRTemplateValues(sdrData?.[0]?.dcsSdrSysId, queryInfo[0]?.fldTemplateSysId),
);
You are on the right path here.
You just need to check if sdrData is undefined. You are trying to access the first element (sdrData[0]) but initially it's undefined.
...
{
enabled: !!(sdrData && sdrData[0]?.dcsSdrSysId
}
...
I am using vercel for NextJS and this is my setup in getStaticPaths
const paths = posts.map((post) => ({
params: { player: post.player, id: post.id },
}))
return { paths, fallback: true }
When I set the fallback to true, I have got this error in vercel:
21:55:01.736 info - Generating static pages (1752/1752)
21:55:01.736 > Build error occurred 21:55:01.739 Error: Export
encountered errors on following paths: 21:55:01.739
/clip/[player]/[id]
It is ok when fallback is set to false but I really like to set fallback set to true so that pages can be updated frequently. Any help will be greatly appreciated...
Inside your /clip/[player]/[id].js file, you need to handle the fallback state when that page is being requested on-demand.
// pages/posts/[id].js
import { useRouter } from 'next/router'
function Post({ post }) {
const router = useRouter()
// If the page is not yet generated, this will be displayed
// initially until getStaticProps() finishes running
if (router.isFallback) {
return <div>Loading...</div>
}
// Render post...
}
// This function gets called at build time
export async function getStaticPaths() {
return {
// Only `/posts/1` and `/posts/2` are generated at build time
paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
// Enable statically generating additional pages
// For example: `/posts/3`
fallback: true,
}
}
// This also gets called at build time
export async function getStaticProps({ params }) {
// params contains the post `id`.
// If the route is like /posts/1, then params.id is 1
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
// Pass post data to the page via props
return {
props: { post },
// Re-generate the post at most once per second
// if a request comes in
revalidate: 1,
}
}
export default Post
https://nextjs.org/docs/basic-features/data-fetching#fallback-true
What I did was conditionally render my component. So, my component receives the object data and if I need to use a value from data, such as "title", I will do...
data?.title
Also, for my entire return component I will conditionally render it. For example...
{data !== undefined ? (
<div className ='main-content'>
<p> This is the content that I want rendered if data is defined </p>
</div>
) : (
<div className = 'fallback-content'>
<p> This shows if data == undefined </p>
</div>
)
I just got started to react so please bear with me. I don't know exactly what I am doing, I'm just picking those things as I go so I'll do my best to walk you through my mental process when building this.
My intentions are to create a registration component, where the backend returns the validation errors in case there are any in form of an object which has following structure.
{
"username": [
"A user with that username already exists."
],
"email": [
"A user is already registered with this e-mail address."
]
}
The state manager that I chose to be using is redux, so this comes back every time when the register function is dispatched.
Since it has this structure I wrote a function to help me decompose it and pick up only on the actual errors (the strings).
const walkNestedObject = (obj, fn) => {
const values = Object.values(obj)
values.forEach(val =>
val && typeof val === "object" ? walkNestedObject(val, fn) : fn(val))
}
now I want to display them in the view, so I wrote another function which is supposed to do that
const writeError = (value) => {
return <Alert message={value} type="error" showIcon />
}
Down in the actual component I am calling it as this:
{(props.error) ? walkNestedObject(props.error, writeError) : null}
To my surprise if I console.log the value above return in writeError it works flawlessly, every single error gets printed, but none of them gets rendered.
To debug this I've tried multiple variations and none of them seemed to work, I even called the writeError function in the component as
{writeError('test')}
and it worked for some reason.
At this stage I'm just assuming there's some react knowledge required to fulfil this task that Im just now aware of.
EDIT:
A mock example can be found over here
Also, I've tried using the first two answers and when mapping through the errors I get this
Unhandled Rejection (TypeError): props.error.map is not a function
with other variations, it mentions the promise from so I'd include how I manage the API request
export const authSignup = (username, email, password1, password2) => dispatch => {
dispatch(authStart());
axios.post('http://127.0.0.1:8000/rest-auth/registration/', {
username: username,
email: email,
password1: password1,
password2: password2
})
.then(res => {
const token = res.data.key;
const expirationDate = new Date(new Date().getTime() + 3600 * 1000);
localStorage.setItem('token', token);
localStorage.setItem('expirationDate', expirationDate);
dispatch(authSuccess(token));
dispatch(checkAuthTimeout(3600));
})
.catch(err => {
dispatch(authFail(err.response.data))
})
}
Consider changing the topology of your error messages:
"errors": [
{ "type": "username", "message": "Username already in use." },
{ "type": "email", "message": "Email address already in use."}
]
That makes your implementation a bit easier:
// MyLogin.jsx
import React from 'react'
const MyLogin = () => {
/**
* Here we're using state hooks, since it's much simpler than using Redux.
* Since we don't need this data to be made globally available in our
* application, it doesn't make sense to use Redux anyway.
*/
const [errors, setErrors] = React.useState([])
const handleLogin = (event) => {
event.preventDefault()
axios.post('/api/login', formData).then(() => successAction(), (error: any) => {
setErrors(error) // Update our local state with the server errors
})
}
return (
<>
{errors.length && ( // Conditionally render our errors
errors.map((error) => (
<Alert type={error.type} message={error.message} />
)
)}
<form onSubmit={handleLogin}>
<input type='text' name='email' />
<input type='text' name='username' />
<input type='password' name='password' />
</form>
<>
)
}
export default MyLogin
Your walkNestedFunction function checks each layer of an object, and if a given layer of the object is an object itself, it then uses that object to run your function - which in this case is writeError. writeError returns an error <Alert /> as soon as an error arises. But when you stick writeError inside the circular logic of walkNestedFunction, it will hit the first return statement, render it to the page, and then stop rendering. I think this is why you're getting the complete list of errors logged to the console. Your walkNestedFunction continues cycling down through object layers until its done. But in React, only the first return statement will actually render.
A better tactic would be to modify your writeError function to record the erors to a state variable. Then you can render this state variable. Every time the state is updated, the component will rerender with the updated state.
// Define state in your component to contain an array of errors:
state = {
errors: []
}
// Add an error into state with each iteration of writeError
const writeError = (error) => {
this.setState({
errors: [
...this.state.errors,
error
]
})
}
// inside render(), render the state variable containing your errors
<div>
{ this.state.errors.map(error => <p>error</p>) }
</div>
`
I am testing the React-table server side data to render a huge amount of data fetched from an web api without crashing the browser. With the base react-table settings the browser is unable to handle such amount of records (500000) and crash (it gets stuck in the pending state of the request).
So I find the server side data that maybe can help me.
I followed the instructions from the documentation but typescript is complaining about the data that I am trying to use when I update the state.
This is what I have until now:
The method that fetch the data from web api:
private fetchSales() {
fetch(`http://localhost:50335/api/RK`)
.then(response => response.json())
.then(data =>
this.setState({
sales: data // here I get 500000 items
})
)
}
This fetchSales gets called in the componentDidMount().
Then I have the ReactTable component inside the render():
render() {
const {
sales,
pages
} = this.state;
return (
<div className = "App" >
<ReactTable
data = {sales}
manual
pages = {pages}
defaultPageSize = {10}
onFetchData = {this._fetchData}
columns = {
[{
Header: "Region",
accessor: "Region"
},
{
Header: "Country",
accessor: "Country"
}]
}
/>
</div>
);
}
In the ReactTable there is call to a function called _fetchData and that function looks like this:
private _fetchData(state: any) {
requestData(
state.sales,
state.pageSize,
state.page
).
then(res => {
this.setState({
sales: res.rows, // here typescript complain: "res is of type 'unknown'"
pages: res.pages // here typescript complain: "res is of type 'unknown'"
});
})
}
Inside the setState the res object is type 'unknown' and typescript doesn't like it.
requestData is a function that lives outside the class and get the sales, pageSize and page states:
const requestData = (sales: any, page: number, pageSize: number) => {
return new Promise((resolve, reject) => {
const res = {
rows: sales.slice(pageSize * page, pageSize * page * pageSize),
pages: Math.ceil(sales.length / pageSize)
}
resolve(res);
})
}
The function is almost identical the in the documentation I only removed the filtering and sorting because I don't need them. I only need the res object that return the function.
And I almost forget it, inside the constructor I am attaching the this to the _fetchData method: this._fetchData = this._fetchData.bind(this);
Why is typescript complaining about the res object that I am trying to use to set the state?
Best regards!
Americo
EDIT
I noticed a few mistakes.
private _fetchData(state: any) {
requestData(
state.sales, // that should be state.data (state is the state of ReactTable)
state.pageSize,
state.page
...
Next, I've noticed that you do pagination after fetching the data. But using manual with onFetchData is for handling pagination on the server. For example, with page and pageSize you could pass these parameters to your API. That's the whole point of pagination!
An example from the documentation:
onFetchData={(state, instance) => { //onFetchData is called on load and also when you click on 'next', when you change page, etc.
// show the loading overlay
this.setState({loading: true})
// fetch your data
Axios.post('mysite.com/data', {
//These are all properties provided by ReactTable
page: state.page,
pageSize: state.pageSize,
sorted: state.sorted,
filtered: state.filtered
})
Since you're fetching all of them at once (why though? can't your API on the server handle pagination? this way, you won't have to wait ages before the results are returned), then I suggest you let ReactTable do the work.
That is, you just do:
<ReactTable
columns={columns}
data={data}
/>
ReactTable will take care of pagination. And now, you may use your query to the API in ComponentDidMount.
Could you please instead try to put all the logic in the onFetchData. I could be wrong but it seems to me you misunderstood the instructions in the documentation. OnFetchData is called at ComponentDidMount, it's not telling you that you have to put your function there.
private _fetchData(state, instance) {
const { page, pageSize } = state
fetch(`http://localhost:50335/api/RK`)
.then(response => response.json())
.then(data =>
let sales = data
this.setState({
sales: sales.slice(pageSize * page, pageSize * page * pageSize),
pages: Math.ceil(sales.length / pageSize)
});
)
}
As for Typescript, from what I gather, Typescript doesn't have enough information to infer the type of what your Promise returns.
So you have to explicitly annotate Promises generic type parameter:
return new Promise<{ sales: object; pages: number; }>((resolve, reject) => { ... }
I'm trying to render my Guild component with data from Firestore. I put the data from Firestore into my state as an array, then when I call the component and try to render it, nothing shows. I want to believe I'm doing something very wrong here (haven't been working with React for very long), but I'm not getting any errors or warnings, so I'm not sure exactly what's happening.
Guilds.js
<Col>
<Card>
<CardBody>
<CardTitle className={this.props.guildFaction}>{this.props.guildName}</CardTitle>
<CardSubtitle>{this.props.guildServer}</CardSubtitle>
<CardText>{this.props.guildDesc}</CardText>
</CardBody>
</Card>
</Col>
Render function
renderCards() {
var guildComp = this.state.guilds.map(guild => {
console.log(guild)
return <Guilds
key={guild.id}
guildFaction={guild.guildFaction}
guildServer={guild.guildServer}
guildName={guild.guildName}
guildDesc={guild.guildDesc} />
})
return <CardDeck>{guildComp}</CardDeck>
}
Fetching Firestore Data
guildInfo() {
Fire.firestore().collection('guilds')
.get().then(snapshot => {
snapshot.forEach(doc => {
this.setState({
guilds: [{
id: doc.id,
guildDesc: doc.data().guildDesc,
guildFaction: doc.data().guildFaction,
guildName: doc.data().guildName,
guildRegion: doc.data().guildRegion,
guildServer: doc.data().guildServer
}]
})
console.log(doc.data().guildName)
})
})
}
UPDATE: solved, fix is in the render function.
Well, you using state "guilds" but you update state "posts" or I miss something?
I see few things here:
your component is Guild.js, but you are rendering <Guilds />
You are setting state to posts, but using this.state.guilds to render the components
You are overriding that piece of state each time to the last object in the snapshot, with the way you are mapping the Firestore data
you are setting the ids in the list wrong using doc.id instead of doc.data().id
You aren't mapping guilds to render. guilds is an array of guild objects, so you should do something like guilds.map(guild => { return <Guild /> }
These are few things to fix, and then try to console.log(this.state.guilds) before rendering and see if you get the right data
I think your issue is that because setState is async, by the time it actually sets the state doc is no longer defined. Try creating the array first, then call setState outside of the loop ie:
guildInfo() {
Fire.firestore().collection('guilds')
.get().then(snapshot => {
let guilds = []
snapshot.forEach(doc => {
guilds.push({
id: doc.id,
guildDesc: doc.data().guildDesc,
guildFaction: doc.data().guildFaction,
guildName: doc.data().guildName,
guildRegion: doc.data().guildRegion,
guildServer: doc.data().guildServer
});
})
this.setState({guilds});
})
}
Try to use a map function, and in the callback function of the setState, try to console log your state after the update:
guildInfo() {
Fire.firestore().collection('guilds')
.get()
.then(snapshot => {
const guilds = snapshot.map(doc => {
return {
id: doc.id,
guildDesc: doc.data().guildDesc,
guildFaction: doc.data().guildFaction,
guildName: doc.data().guildName,
guildRegion: doc.data().guildRegion,
guildServer: doc.data().guildServer
};
this.setState({guilds}, () => console.log(this.state))
})
})
})
}
If in the console log there's a little [i] symbol near your state, it means that the state is not ready, and therefore it's am async issue. Replacing the forEach with the map function may already help though.