I'm trying to render data from props in React functional component that look like this:
interface TagsComponentProps {
tags: Tag[];
}
const TagsComponent: FC<TagsComponentProps> = (props: TagsComponentProps) => (
<>
{props.tags.length === 0 &&
<LoadingStateComponent />
}
{props.tags.map(tag => {
{ tag.tagId }
{ tag.tagName }
})
}
</>
)
export default TagsComponent;
Within Next.js page that receiving data inside the getStaticProps method. It looks like that:
const IndexPage = ({ tags }: InferGetStaticPropsType<typeof getStaticProps>) => (
<>
<LayoutComponent>
<TagsComponent tags={tags} />
</LayoutComponent>
</>
)
export default IndexPage;
export const getStaticProps = async () => {
const res = await fetch(`${process.env.HOST}/api/tags/read`)
const data = await res.json()
// if (error) {
// return <ErrorComponent errorMessage={'Ошибка загрузки тегов'} />
// }
return {
props: {
tags: data.Items as Tag[]
}
}
}
But nothing is getting rendered at all although I'm receiving data. Probably I'm missing some concept of data fetching for SSR in Next.js.
I guess the issue is .map() is not returning anything in your code here:
{props.tags.map(tag => {
{ tag.tagId }
{ tag.tagName }
})
}
Instead you should try as the following:
{
props.tags.map(tag => (
<>
{ tag.tagId }
{ tag.tagName }
</>
))
}
Also you can do a null check before as props.tags && props.tags.map().
Related
my props in the children class is supposed to be an array of Event objects.
I am checking beforehand, if the array is empty in App.js like this:
function App() {
class Event {
constructor(id, title, date){
this.id = id;
this.title = title;
this.date = date;
}
}
const [events, setEvents] = useState([])
const [ids, setIds] = useState([])
const [safedIds, setSafedIds] = ([])
const [eventsPrep, setEventsPrep] = useState([Event])
useEffect(() => {
fetch('https://someAPI.com')
.then(response => response.json())
.then(
res => {setEvents(res);
console.log(res);
})
.catch(err => console.log(err))
.then(handleIncomingData())
//.then(console.log("was here"))
}, [])
function handleIncomingData () {
if(events.length > 0) {
events.forEach(event => {
ids.push(event["_id"]);
let date = new Date(event["date"]);
eventsPrep.push(new Event(event["_id"], event["title"], date.toDateString()))
})
}
}
return (
<>
<Navbar/>
{eventsPrep.length > 0 ? <Home events={eventsPrep}/> : <></>}
</>
);
}
export default App;
but whenever I try to reach the props in the child component it is considered undefined.
My child component:
import React from 'react'
import SingleEvent from '../../event/SingleEvent'
export const Home = (props) => {
console.log(props.events)
return (
<>
{props?.events
? props.events.forEach((event) => {
console.log('was here 2');
return <SingleEvent title={event.title} start={event.date} />;
})
: 'no upcomming events'}
</>
);
}
Even if I only pass a string down, it is still undefined.
Thanks for help!
In your useEffect() you update events via setEvents(res) and call handleIncomingData() after that.
In handleIncomingData() you use events, but it will still hold the value from previous render / from the initialization as setEvents(res) will not change events immidiately. You can console.log(events) inside that function to investigate this.
Instead you can pass res into handleIncomingData() and use it instead of events inside that function. So in you useEffect you would have:
.then(response => response.json())
.then(
res => {
setEvents(res);
handleIncomingData(res);
})
In addition in handleIncomingData() use setEventsPrep(...) instead of eventsPrep.push(), as mentioned in the comment.
You are returning 2 return statments in the component.
Only need to return 1 return statement in component
export const Home = (props) => {
return (
<>
{props?.events && Object.values(props.events).length > 0
? props.events.forEach((event) => {
console.log('was here 2');
return <SingleEvent title={event.title} start={event.date} />;
})
: 'no upcomming events'}
</>
);
};
Also to check whether eventsPrep has a length > 0 then try it this way if eventsPrep is an object
eventsPrep && Object.values(eventsPrep).length > 0
I have component which represent page, therefore it is parent Component.
I also have a second component which represents table, therefore it is child Component.
In parent component I skip api call, but I want watch data, because if there's no data, I don't want display filters, etc.
const Page = () => {
const { isUninitialized, data } = useGetUsersQuery({ skip: true})
if (!isUninitialized && !data.length) return <div>There's no data.</div>
return <>
<Filters />
<Table />
</>
}
const Table = () => {
const { isLoading, isFetching, data } = useGetUsersQuery()
if (isLoading || isFetching) return <div>Loading...</div>
if (data) return <table>...</table>
return null
}
For some reason it doesn't work.
I thought of using a useSelector hook.
const Page = () => {
const { isUninitialized, data } = useSelector("???")
if (!isUninitialized && !data?.length) return <div>There's no data.</div>
return <>
<Filters />
<Table />
</>
}
But it's not look much effective.
Till now I used combination of setState and useEffect.
const Page = () => {
cont [ noData, setNoData ] = useState(false)
if (noData) return <div>There's no data.</div>
return <>
<Filters />
<Table setNoData={setNoData}/>
</>
}
const Table = ({noData}) => {
const { isUninitialized, isLoading, isFetching, data } = useGetUsersQuery()
if (isLoading || isFetching) return <div>Loading...</div>
useEffect(()=> {
if (isUninitialized && !data.length) setNoData(true)
},[data])
if (data) return <table>...</table>
return null
}
However, I am not completely satisfied with any solution. Has someone a better idea?
Currently I have a BooksList component and I'm passing down props to my BooksDetails component when a title is clicked. How do I use an Apollo hook to only query on props change?
I'm not sure how to do this using hooks. I've looked through the UseQuery documentation from Apollo. I couldn't find documentation on UseLazyQuery.
I've tried the following, but it always returns undefined:
const { loading, error, data } = useQuery(getBookQuery, {
options: (props) => {
return {
variables: {
id: props.bookId
}
}
}
})
BookList:
const BookList = () => {
const {loading, error, data} = useQuery(getBooksQuery)
const [selectedId, setId] = useState('');
return (
<div id='main'>
<ul id='book-list'>
{data && data.books.map(book => (
<li onClick={() => setId(book.id)} key={book.id}>{book.name}</li>
)) }
</ul>
<BookDetails bookId={selectedId} />
</div>
);
};
export default BookList;
BookDetails:
const BookDetails = (props) => {
const { loading, error, data } = useQuery(getBookQuery, {
options: (props) => {
return {
variables: {
id: props.bookId
}
}
}
})
console.log(data)
return (
<div id='book-details'>
<p>Output Book Details here</p>
</div>
);
};
export default BookDetails;
EDIT - I forgot to add that my GetBookQuery has a parameter of ID so an example would be getBookQuery(123).
Use the useLazyQuery like this instead:
const [getBook, { loading, error, data }] = useLazyQuery(getBooksQuery);
Full example:
import React from 'react';
import { useLazyQuery } from '#apollo/react-hooks';
const BookList = () => {
const [getBook, { loading, error, data }] = useLazyQuery(getBooksQuery);
return (
<div id='main'>
<ul id='book-list'>
{data && data.books.map(book => (
<li onClick={() => getBook({ variables: { id: book.id } })}} key={book.id}>{book.name}</li>
)) }
</ul>
<BookDetails data={data} />
</div>
);
};
export default BookList;
Examples and documentation can be found in Apollo's GraphQL documentation
https://www.apollographql.com/docs/react/data/queries/
I was also following along the same example and I came up here with the same question. I tried doing the following way. This might be helpful for someone.
BookDetails.js:
function BookDetails({ bookId }) {
const [loadDetails, { loading, error, data }] = useLazyQuery(getBook);
useEffect(() => {
if (bookId) {
loadDetails({ variables: { id: bookId } });
}
}, [bookId, loadDetails]);
if (!bookId) return null;
if (loading) return <p>Loading...</p>;
if (error) return <p>Error!</p>;
// for example purpose
return <div>{JSON.stringify(data)}</div>;
}
export default BookDetails;
I have a parent component GoalList which maps to a child component:
{data.goals.map((item, index) => {
return (
<Link
href={{ pathname: "/goal", query: { id: item.id } }}
key={`goal-item-${index}`}
>
<a>
<li>
<div>{item.title}</div>
</li>
</a>
</Link>
);
})}
next/router's page:
import SingleGoal from "../components/SingleGoal";
const Single = () => {
return <SingleGoal />;
};
export default Single;
Child Component:
const SingleGoal = () => {
const [id, setId] = useState("");
const router = useRouter();
useEffect(() => {
if (router.query.id !== "") setId(router.query.id);
}, [router]);
const { loading, error, data } = useQuery(SINGLE_GOAL_QUERY, {
variables: { id: id },
});
if (loading) return <p>Loading...</p>;
if (error) return `Error! ${error.message}`;
return (
<div>
<h1>{data.goal.title}</h1>
<p>{data.goal.endDate}</p>
</div>
);
};
When I click on Link in the parent component, the item.id is properly transferred and the SINGLE_GOAL_QUERY executes correctly.
BUT, when I refresh the SingleGoal component, the router object takes a split second to populate, and I get a GraphQL warning:
[GraphQL error]: Message: Variable "$id" of required type "ID!" was not provided., Location: [object Object], Path: undefined
On a similar project I had previously given props to next/router's page component, but this no longer seems to work:
const Single = (props) => {
return <SingleGoal id={props.query.id} />;
};
How do I account for the delay in the router object? Is this a situation in which to use getInitialProps?
Thank you for any direction.
You can set the initial state inside your component with the router query id by reordering your hooks
const SingleGoal = () => {
const router = useRouter();
const [id, setId] = useState(router.query.id);
useEffect(() => {
if (router.query.id !== "") setId(router.query.id);
}, [router]);
const { loading, error, data } = useQuery(SINGLE_GOAL_QUERY, {
variables: { id: id },
});
if (loading) return <p>Loading...</p>;
if (error) return `Error! ${error.message}`;
return (
<div>
<h1>{data.goal.title}</h1>
<p>{data.goal.endDate}</p>
</div>
);
};
In this case, the secret to props being transferred through via the page was to enable getInitialProps via a custom _app.
Before:
const MyApp = ({ Component, apollo, pageProps }) => {
return (
<ApolloProvider client={apollo}>
<Page>
<Component {...pageProps} />
</Page>
</ApolloProvider>
);
};
After:
const MyApp = ({ Component, apollo, pageProps }) => {
return (
<ApolloProvider client={apollo}>
<Page>
<Component {...pageProps} />
</Page>
</ApolloProvider>
);
};
MyApp.getInitialProps = async ({ Component, ctx }) => {
let pageProps = {};
if (Component.getInitialProps) {
// calls page's `getInitialProps` and fills `appProps.pageProps`
pageProps = await Component.getInitialProps(ctx);
}
// exposes the query to the user
pageProps.query = ctx.query;
return { pageProps };
};
The only downfall now is that there is no more static page generation, and server-side-rendering is used on each request.
I need to use a different component depending on a propType, as a first attempt I'm using an object to store the components I need the problem is that It only works for the first key, for example it only works for AvatarList.Item, when I try to load Avatar.List it just doesn't load.
const component = {
AvatarList: {
Item: async () => (await import('../List/Avatar')).Avatar,
List: async () => (await import('../List/Avatar')).List,
},
Simple: {
List: async () => (await import('../List/Simple')).List,
Item: async () => (await import('../List/Simple')).Simple,
},
};
// Here there is the component and the default I componentType is "AvatarList"
class Articles extends Component {
renderListItem() {
const { componentType, newsArticles } = this.props;
const Item = importComponent(component[componentType].Item);
return newsArticles.map(({
url,
id,
imageUrl,
title,
description,
}) => (
<Item
id={id}
url={url}
imageUrl={imageUrl}
title={title}
description={description}
/>
));
}
renderList() {
const { componentType } = this.props;
const List = importComponent(component[componentType].List);
return (
<List>
{this.renderListItem()}
</List>
);
}
render() {
return (
this.renderList()
);
}
}
// This is the HOC I use for the loading the components with async/await
import React, { Component } from 'preact-compat';
import Loader from '../components/Loader/Loader';
export default function importComponent(importFunction) {
return class ComponentImporter extends Component {
async componentWillMount() {
this.setState({ component: await importFunction() });
}
render() {
const ImportedComponent = this.state.component;
return (
<Loader loaded={Boolean(this.state.component)}>
<ImportedComponent {...this.props} />
</Loader>
);
}
};
}
Apparently I can't get the named exports if they're coming from the same file dynamically, so the solution for this was just importing those files and get the variables on the componentDidMount.