I'm really stuck in trying to render some data being passed down as props. I'll include some code and definitions below, but if you feel that I need to include some further code snippets, please let me know (I'm really struggling to find what's causing the error, so I may have missed out the causal issue!).
I first take data from an API which is then used to populate a UserList component via useState (setUsers(data):
useEffect(() => {
async function getUserList() {
setLoading(true);
try {
const url =
"API URL";
const response = await fetch(url);
const data = await response.json();
setUsers(data);
} catch (error) {
throw new Error("User list unavailable");
}
setLoading(false);
}
getUserList();
}, []);
If a user is clicked in the UserList, this changes the selectedUser state of the parent Home component to be the specific user's unique_ID via:
onClick={() => setSelectedUser(unique_ID)}
If the selectedUser changes, the Home component also does a more updated data fetch from the API to get all information relevant to the specific user via their unique_ID:
useEffect(() => {
async function getSelectedUserData() {
try {
const url = `API URL/${selectedUser}`;
const response = await fetch(url);
const data = await response.json();
setSelectedUserData(data);
} catch (error) {
throw new Error("User data unavailable");
}
}
getSelectedUserData();
}, [selectedUser]);
The specific user data is then passed down as props to a child UserInformation component:
<UserInformation selectedUser={selectedUser} selectedUserData={selectedUserData} />
At this point, I can see all the data being passed around correctly in the browser React Developer Tools.
The UserInformation component then gets the data passed via props:
import React, { useEffect, useState } from "react";
function UserInformation({ selectedUser, selectedUserData }) {
const [currentUser, setCurrentUser] = useState({ selectedUserData });
useEffect(() => {
setCurrentUser({ selectedUserData });
}, [selectedUser, selectedUserData]);
return (
<div>
<p>{selectedUserData.User_Firstname}</p>
<p>{currentUser.User_Firstname}</p>
</div>
);
}
export default UserInformation;
And here is where I get stuck - I can't seem to render any of the data I pass down as props to the UserInformation component, even though I've tried a few different methods (hence the <p>{selectedUserData.User_Firstname}</p> and <p>{currentUser.User_Firstname}</p> to demonstrate).
I'd really appreciate any help you can give me with this - I must be making an error somewhere!
Thanks so much, and sorry for the super long post!
I managed to solve this (thanks to the help of Mohamed and Antonio above, as well as the reactiflux community).
import React from "react";
function UserInformation({ selectedUserData }) {
const currentUserRender = selectedUserData.map(
({ User_Firstname, User_Lastname }) => (
<div key={unique_ID}>
<p>{User_Firstname}</p>
</div>
)
);
return (
<div>
{selectedUserData ? currentUserRender : null}
</div>
);
}
export default UserInformation;
As selectedUserData was returning an array instead of an object, I needed to map the data rather than call it with an object method such as {selectedUserData.User_Firstname}.
const currentUserRender = selectedUserData.map(
({ User_Firstname, User_Lastname }) => (
<div key={unique_ID}>
<p>{User_Firstname}</p>
</div>
)
);
The above snippet maps the selected data properties found inside selectedUserData ({ User_Firstname, User_Lastname }), with the whole map being called in the return via {selectedUserData ? currentUserRender : null}.
Hopefully my explanation of the above solution is clear for anyone reading, and a big thanks again to Mohamed and Antonio (as well as a few others in the reactiflux Discord community) for helping!
You're trying to set the current user to an object with key "selectedUserData".
So if you want to access it you've to access it by this key name so change this line currentUser.User_Firstname to currentUser.selectedUserData.User_Firstname
Related
I've got posts with comments and like to implement a post view that includes all comments to that post.
My getServerSideProps passes the post (including all comments) to my page. Whenever a new comment is written the comments should be dynamically updated, but I'm currently facing some problems with that.
My post view:
const PostView: NextPage = ({ post }) => {
return (
<Layout>
{post.title}
<CommentList initialComments={post.comments} postId={post.id} />
</Layout>
);
};
export default PostView;
export const getServerSideProps = () => {
const post = await getPost(); // returns the post and all its comments
return { props: { post } };
};
The CommentList component:
const CommentList = (initialComments, postId) => {
const { data: comments } = useQuery(["comments", postId], async () => getComments(), { initialData: initialComments);
return (
<>
Comments: {comments.length}
... new comment form ...
... list of comments ...
</>
);
}
The reason why I still want to query comments with react-query is simple: comments should be server-side rendered so that they become seo-relevant, while I want human users to get a dynamic list that can be updated.
When writing new comments I update the QueryClient of react-query by hand:
export const useCreateCommentMutation = (postId: string) => {
const queryClient = useQueryClient();
return useMutation(
["comments", postId],
async (values) =>
await axios.post("/api/comments", values),
{
onSuccess: async res => {
queryClient.setQueryData<CommentWithAuthor[]>(
["comments", postId],
prev => [...(prev || []), res.data],
);
},
},
);
};
This seems to work at first glance; when I check the DOM the comments are included and when writing new comments they dynamically appear.
Unfortunately, when I refresh the page I get the following error:
Text content did not match. Server: "3" Client: "4"
3 (or 4) in this case is the comments.length output.
What am I doing wrong in this case?
Thanks
Edit 1:
I've also tried fixing it by using useEffect:
const [usedComments, setUsedComments] = useState([]);
useEffect(() => {
setUsedComments(comments || initialComments);
}, [comments])
And render usedComments instead - but unfortunately now the comments are no longer part of the DOM.
Why don't you try using useState() hook to store that the Comment data . Every time useQuery runs it will update the state which will cause re-rendering of the comment and also.
I can't think of anything other then this. I don't know what your comment json/data look like to do the server side dynamic rendering.
And useQuery runs on user action like click on add new comment button or a time loop.
and your error seems like It is caused by some Server and client attribute of component.
The question is simple. How to fetch data in your React blog and stay DRY? Let's say that you have just two components in your blog - PostsList and SinglePost, in both components you must fetch data, activate isLoading state, etc. There will be chunks of the same code in both components.
I investigated the situation a little bit, checking React-blog demo apps of big headless CMS providers, like Prismic or Sanity.io, and they all just repeat fetch functions in both PostsList and SinglePost.
Does anybody have any idea? You can point me to some good resources?
You can achieve this by using High Order Components. You can use them for reusing component logic. Let me show you an example of how to handle the isLoading with a HOC:
HOC:
import React, { useState } from 'react'
const hocLoading = (WrappedComponent, loadingMessage) => {
return props => {
const [ loading, setLoading ] = useState(true)
const setLoadingState = isComponentLoading => {
setLoading(isComponentLoading)
}
return(
<>
{loading && <p>{loadingMessage}</p>} //message showed when loading
<WrappedComponent {...props} setLoading={setLoadingState} />
</>
)
}
}
export default hocLoading
As you can see this HOC is receiving the WrappedComponent and a message that you can set depending on your component. Then you will have to wrap every component where you want to show the loading feedback with the HOC and you can use the setLoading prop to stop showing the loading feedback:
const Component = props => {
const { setLoading } = props
useEffect(() => {
const loadUsers = async () => {
await fetchData() // fetching data
setLoading(false) // this function comes from the HOC to set loading false
}
loadUsers()
},[ ])
return (
<div className="App">
{usuarios.data.map(x => <p key={x.id}>{x.title}</p>)}
</div>
);
}
export default hocLoading(Component, "Data is loading") //component wrapped
// with the HOC and setting feedback message
This way you avoid repeating this process for every component. Regarding the data fetching you can create a Hook or a function that receives dynamic params so you can just call something like fetchData(url). Here is an example of a dynamic function for making request using axios:
const baseUrl = "" //your BASE URL
async function request(url,method,data){
try {
const response = await axios({
method,
url: `${baseUrl}${url}`,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
data: data ? data : undefined
})
return response
} catch (e) {
// handle error
}
}
Preface: I'm fairly new to React (Coming over from Angular). I know things a similar but different.
I have referenced the following SO threads to no avail in my situation:
React not displaying data after successful fetch
Objects are not valid as a React child. If you meant to render a collection of children, use an array instead
Currently, I'm trying to get my data to display from an API I developed. I'm used to the Angular approach which would call for a ngFor in the template for most data showcase situations.
I'm having trouble wrapping my mind around what I have to do here in order to display my data. The data is expected to be an array of objects which I would then parse to display.
I also receive the following error: Error: Objects are not valid as a React child (found: object with keys {data}). If you meant to render a collection of children, use an array instead.
I've searched high and low for a solution but sadly, nothing I've seen has worked for me. (All of the answers on SO are using the class-based version of React, of which I am not).
You can see my data output in the following screenshot:
I am also including my custom hook code and the component that is supposed to render the data:
CUSTOM DATA FETCH HOOK
interface Drone{
id: number;
name: string;
model: string;
price: number;
}
export function useGetData(urlpath:string) {
const [droneData, setData] = useState<any>()
async function handleDataFetch(path:string){
const result = await fetch(`https://drone-collections-api-jc.herokuapp.com${path}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'x-access-token': 'Bearer API-TOKEN'
}
})
const response = await result.json();
setData(response)
}
useEffect( () => {
handleDataFetch(urlpath)
})
return droneData
}
THE DRONE COMPONENT
import { useGetData } from '../../custom-hooks'
export const Drones = () => {
let data = useGetData('/drones')
console.log(data)
// const DisplayDrone = ( ) => {
// return (
// Array.prototype.map( data => {
// <div>{ data.name }</div>
// })
// )
// }
return (
<div>
<h1>Hello Drones</h1>
</div>
)
}
Also, for more context, the current code can be found at this repo: https://github.com/carter3689/testing-drone-frontend
Please, help me understand what I'm missing. Many Thanks!
There are several locations that needed to be fixed
In fetchData.tsx
export function useGetData(urlpath: string) {
const [droneData, setData] = useState<any>([]);
async function handleDataFetch(path: string) {
const result = await fetch(`https://jsonplaceholder.typicode.com/posts`, {
...
});
const response = await result.json();
setData(response);
}
useEffect(() => {
handleDataFetch(urlpath);
}, []);
Explanation:
you need a "blank" array for looping through. I guess that the error causes by the fact that at the start, before the data is fetched, there is nothing to loop through. It's same as doing undefined.map(), which is obviously fail.
You need a dependencies array for useEffect. Right now your code will do an infinite loop since everytime it get data, it update the state, thus re-run the useEffect and repeat. Add dependencies array limit when that useEffect will run
In Drones.tsx
return (
<div>
{data.map(item => <div>{item.name}</div>}
</div>
)
Not much to say here. I don't use Angular so I'm not sure why you use Array.prototype.map, but in React you can loop through your variable directly. I also have a CodeSandbox link for your project (I use public API)
I have a blog link. Link is dynamic route with blog id. It's the Link wrapper from Next.
//link
<h3 className="blogTitle">
<Link href="[blog]" as={props.item.postingId}>{props.item.title}</Link>
</h3>
Now I want to pass "blog id" to the component and to present data in a new page.
//page where link leads to
const ad = () => {
const router = useRouter()
const {
query: {blog},
} = router
const [data, setData] = useState(false);
const [loading, setLoading] = useState(false);
console.log('....outside useEffect log', blog)
useEffect(() => {
console.log('useEffect consolelog', blog);
axios.get('httpwww.blogapiadress.com/'+ ad)
.then(response => setData(response.data))
.then(setLoading(false))
}, [])
return(
<Container fluid className="padding0">
/// data should be here.
</Container>
);
}
export default ad;
Problem: in useEffect console.log('blog', blog) returns undefined, so router does not return value from query. However, outside of useEffect it does. How to solve that issue, I want to fetch data related to the router query?
Since axios is getting undefined instead of blog id, I am getting 404.
You can use getStaticProps() to fetch the blog data at build time.
Example:
// posts will be populated at build time by getStaticProps()
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
)
}
// This function gets called at build time on server-side.
// It won't be called on client-side, so you can even do
// direct database queries. See the "Technical details" section.
export async function getStaticProps() {
// Call an external API endpoint to get posts.
// Access route params:
const blog = context.query.blog // or context.params.blog for parametrized routes
return {
const res = await fetch('https://...')
const posts = await res.json()
// By returning { props: { posts } }, the Blog component
// will receive `posts` as a prop at build time
return {
props: {
posts,
},
}
}
export default Blog
More info on NextJS docs.
I don't think you need to use the global window object to access dynamic data related to your route -- you should be able to use the Next router. I think the way you are defining href -- the only required prop for Link is causing issues. Looking at docs and your current exampel you probably want to use something like:
<Link
href={{
pathname: '/[blog]',
query: { blog: props.item.postingId },
}}
>
<a>{props.item.title}</a>
</Link>
// or
<Link href={`/${encodeURIComponent(props.item.postingId)}`}>
<a>{props.item.title}</a>
</Link>
Then you should be able to properly access [blog] (i.e., your postingId) using Router. For example, if your route was defined dynamically by /[blog].js, you could use the following:
import { useRouter } from 'next/router'
const ad = () => {
const router = useRouter()
const { blog } = router.query
const [data, setData] = useState(false);
const [loading, setLoading] = useState(false);
useEffect(()=>{
axios.get('httpwww.blogapiadress.com/'+ blog)
.then(response => {
setData(response.data)
setLoading(false)
})
}, [])
if (loading || !data) return <div> Loading... </div>
return(
<Container fluid className="padding0">
/// Render data
</Container>
);
}
export default ad;
Looking for answer for a few hours, and when I posted question on stack.. I figured out the answer.
So problem was that query is empty with next static generation at build time https://nextjs.org/docs/api-reference/next/router#router-object
I havent found best solution, but i found working one.
I got blog id from the windows.location href
useEffect(()=>{
const last = window.location.href.split('/').pop();
console.log('last', last)
axios.get('https://blogpostings.com/'+last)
.then(response => setData(response.data))
}, [])
I am not sure if its proper or good way, but it works.
I hope someone will find this helpful.
If you want get the query parameter from the link, you need to insert at the end of the url: https://sample-link.com?blog=123
Then same as your code above:
import { useRouter } from 'next/router'
const router = useRouter()
const {
query: {blog}
} = router;
console.log("blog: ", blog)
Result:
blog: 123
i am looking to pass the json data that i received using the fetch API and use in the Useraccount component.
i have looked around and i can find a lot of material related to passing from child to parent and very few that mention from parent to child.
I have tried using this userinfo={credentailverify} and clearly it is not working for me, any suggestions please
Update3:
i have upload the small clip for the issue that i am facing for better understanding. i have tried to make the code very simple but still cant understand the reason why loginscreen is showing before showing the user account information.
youtube link showing issue
import Useraccount from "./Useraccount";
function Signin({ userinfo1, userinfo2 }) {
//userinfo1 is having customer account information
//userinfo2 is Boolean and showing if user is looged in or not if not then go to login page
return (
<div>
{userinfo2 ? (
<Useraccount userinfo={userinfo1} />
) : (
<SigninOptions />
)}
</div>
);
}
export default Signin;
Update2: i am also experience one strange thing as when i set setUserinfo and pass the new state into the child it does show the new state in child component there but here in main code if i try to console the userinfonew after its set it is showing me the initial state as empty array, is it some thing i am missing here!!
.then((data) => {
setUserinfo(data.data)
console.log(userinfonew)
}
-Note i can see if i run console.log(userinfonew) outside the Async function then it does show the updated status but not inside the async function, although i am updating the status inside the Async function. cant understand the reason behind it
Update1: initial problem is solved thanks and i have updated the code, now the only issue i am facing is the condition that i am using in the return statement is both getting executed i.e first for few seconds < SigninOptions /> component and then the correct one as per the logic < Useraccount userinfo={userinfonew} /> component not sure if there is a delay somewhere or code is runnig twice
function Signin() {
const [siginalready, setifsignedin] = useState(false);
const [userinfonew, setUserinfo] = useState([]);
useEffect(() => {
credentailverify();
}, []);
let url = "http://localhost:5000/api/verifyifloginalready";
let options = {
credentials: "include",
method: "POST",
};
let verifyifloginalready = new Request(url, options);
let credentailverify = async () => {
const x1 = await fetch(verifyifloginalready)
.then((res) => {
if (res.status == 400 || res.status == 401) {
return setifsignedin(false);
} else {
setifsignedin(true);
return res.json();
}
}).then((data)=>
{
// here the console is shoewing empty array
setUserinfo(data.data)
console.log(userinfonew)
})
.catch((err) => console.log("err"));
return x1;
};
return (
<div>
// here first <SigninOptions /> renders for a SECOND and then <Useraccount userinfo={userinfonew} />
{siginalready ? (
<Useraccount userinfo={userinfonew} />
) : (
<SigninOptions />
)}
</div>
);
}
export default Signin;
the below is the code at the user account,
import React, { useState, useEffect } from "react";
import "../App.css";
function Useraccount({ userinfo }) {
return <div>{ `The user email address is ${userinfo}`}</div>;
}
export default Useraccount;
and after the data is passed to the child component how can i use it, i have seen one place mentioned to use as this.props.userinfo but i am using React Hook so cant use this method to access.
Thanks in advance.
You need to access the props passed to child:
function Useraccount({ userinfo }) {
if (!userInfo) return <div />
return <div>{`The user email address is ${userinfo}`}</div>;
}
Also use a template string like I did above
My recommendation is to track the response of your response in the state, then pass that state value into the child component.
It can be helpful to think of an effect as happening in a different execution than your main code. Any data inside of there can only be communicated to your component through the functions that you pass in as the effect dependencies.