Server side load a global component with Next.js in React - javascript

The fact that I can't find anyone asking this question probably means that I'm not understanding something fully or I'm searching with the wrong keywords so please don't bite my head off if this is a stupid question.
I'm pasting the relevant parts of the code but if you want a repo of the full example, here it is. The full question will be at the bottom.
Here's my folder structure:
server.js
/components
Layout.js
/pages
contact.js
server.js
// tells next which page to load
server.get("/contact/:id", (req, res) => {
const actualPage = "/contact"
const queryParams = {id: req.params.id}
app.render(req, res, actualPage, queryParams)
})
// api uri for grabbing contacts from the database
server.get("/api/contact/:id", (req, res) => {
Contact.findOne({first_name: req.params.id}, (error, contact) => {
if (error) return next(error)
res.status(200).json(contact)
})
})
pages/contact.js
const Contact = props => (
<Layout>
<h1>{props.contact.first_name} {props.contact.last_name}</h1>
</Layout>
)
// a static async call passes fetched data into the props
Contact.getInitialProps = async function (context) {
const {id} = context.query
const res = await fetch(`http://localhost:3000/api/contact/${id}`)
const contact = await res.json()
return {contact: contact}
}
components/Layout.js
const Layout = (props) =>
<div>
<div>
<Link href="/contact/John">
<a>John</a>
</Link>
<Link href="/contact/Jed">
<a>Jed</a>
</Link>
<Link href="/contact/Fred">
<a>Fred</a>
</Link>
</div>
{props.children}
</div>
I'm trying to figure out whether or not it's possible to dynamically query the database to build a navigation of the documents in the database. The only way I can think to do it is by re-rendering the entire navigation with every component but this seems extremely unnecessary. Again, if you want to give the code a try, here's my example repo.

One of the ways I can think of is to use custom app.js and add componentDidMount method (it is fired only once) where you can fetch all the contacts, store it inside app.js state and pass it down to pages and components.
_app.js
import React from 'react';
import App, { Container } from 'next/app';
export default class MyApp extends App {
static async getInitialProps({ Component, router, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return { pageProps };
}
// store contacts in the state
state = {
contacts: undefined
};
componentDidMount() {
// get contacts and store them in the _app.js state
fetch('some-api/all-contacts-endpoint').then(contacts => {
this.setState({ contacts });
});
}
render() {
const { Component, pageProps } = this.props;
return (
<Container>
<Component {...pageProps} contacts={this.state.contacts} />
</Container>
);
}
}
pages/contact.js
// contacts will be inside props here
const Contact = props => (
<Layout contacts={props.contacts}>
<h1>
{props.contact.first_name} {props.contact.last_name}
</h1>
</Layout>
);
// a static async call passes fetched data into the props
Contact.getInitialProps = async function(context) {
const { id } = context.query;
const res = await fetch(`http://localhost:3000/api/contact/${id}`);
const contact = await res.json();
return { contact: contact };
};
components/Layout.js
const Layout = ({ contacts = [] }) => (
<div>
<div>
{contacts.map(contact => (
<Link key={contact.id} href={`/contact/${contact.id}`}>
<a>{contact.name}</a>
</Link>
))}
</div>
{props.children}
</div>
);
Hope this helps!

Related

NextJS change data in API from component

I've got a problem with updating data from API in NextJS. I want to increment the value by clicking on the button and then update the API.
My local JSON file is in at http:localhost/api/data
data.js:
export default function handler(req, res) {
res.status(200).json({ value: 0 })
}
I want to update this value from Home component
index.js:
import { useState } from 'react'
export default function Home({data) {
const [currentData, setCurrentData] = useState(data)
return (
<div>
{data.value}
<button onClick={(e) => setCurrentData(currentData.value + 1)}>+</button>
</div>
)
}
export async function getServerSideProps() {
const res = await fetch('http://localhost:3000/api/data')
const data = await res.json()
return {
props: { data },
}
}
Is there any way to do it? If something is unclear feel free to ask :)

Getting an single user using server side props nextjs

I'm using nextjs to create a dashboard and I have the authentication using next-auth.
However, I'm trying to render the individual users data when they login to the dashboard but not sure where I'm going wrong, I know I have to use the findOne callback but for some reason I can't grab the ID or email.
Here is what I have so far
import { connectToDatabase } from '../../lib/mongodb';
import Head from 'next/head';
import Sidebar from '../components/Sidebar';
export default function Dashboard({ user }) {
return (
<>
<Head>
<title>Ellis Development - Dashboard</title>
</Head>
<Sidebar />
<section className="content dashboard-content">
<h1>Dashboard</h1>
{ users.map(user => (
<div key={user.id}>
<h2>{user.firstname} {user.lastname}</h2>
<p>{user.email}</p>
</div>
)) }
</section>
</>
)
}
// get data from database using server side rendering and mongodb connection
export async function getServerSideProps() {
const client = await connectToDatabase();
const users = await client.db().collection('users').findOne({ _id: id }).toArray();
return {
props: {
users: JSON.parse(JSON.stringify(users))
}
}
}
You can use getSession to handle server-side authentications.
check reference for more resources link
import { getSession } from "next-auth/react"
...
export async function getServerSideProps(ctx) {
const session = await getSession(ctx) //pass context to authenticate create session
const id = session.user.id //get id from session
const client = await connectToDatabase();
const user = await client.db().collection('users').findOne({ _id: id }) // No need to use toArray its returns only 1 object
return {
props: {
user
}
}
}

fetching data from component in NextJS

Hi i am sorry if this question is too basic. But I recently started learning React and NextJS. I am creating simple application that fetch some data and display it on Home page. I have two functions in my component file. I imported that component file in my index.js and using it as composition. The error says map is returning data of undefined.
// Names.js Component
export const getStaticProps = async () => {
const prisma = new PrismaClient()
const names = await prisma.name.findMany()
return {
props: {
names
}
}
}
function Data({names}) {
return (
<div>
<ul>
{names.map((namelist) => (
<li key={namelist.id}>{namelist.title}</li>
))}
</ul>
</div>
)
}
export default Data
// Index.js
export default function Home() {
return(
<div>
<Names />
</div>
)
}
getStaticProps can only be called from pages, not from components. You'll need to move it to Index.js (assuming that's under /pages folder).
// Index.js
export default function Home({ names }) {
return(
<div>
<Names names={names} />
</div>
)
}
export const getStaticProps = async () => {
const prisma = new PrismaClient()
const names = await prisma.name.findMany()
return {
props: {
names
}
}
}
// Names.js Component
function Data({ names }) {
return (
<div>
<ul>
{names.map((namelist) => (
<li key={namelist.id}>{namelist.title}</li>
))}
</ul>
</div>
)
}
export default Data

Unhandled Rejection (TypeError): Cannot read property 'map' of undefined when doing React tutorial

Doing a Hacker News Clone tutorial in React and Next.js, but I'm stumbling into a problem. I have read other questions that are dealing with the same error, but the solutions offered don't work for me. Here is the code:
import React from 'react';
const StoryList = ({ stories }) => (
<div>
{stories.map(story => (
<h3 key={story.id}>{story.title}</h3>
))}
</div>
);
export default StoryList;
The error is specifically at line 4 of that, on the first div tag.
Here is my index page:
import React from 'react';
import fetch from 'isomorphic-fetch';
import Error from 'next/error';
import StoryList from '../components/StoryList';
class Index extends React.Component {
static async getInitialProps() {
let stories;
try {
const response = await fetch(
'https://node-hnapi.herokuapp.com/news?page=1'
);
stories = await response.json();
} catch (err) {
console.log(err);
stories = []
}
return { stories };
}
render() {
const { stories } = this.props;
if (stories.length === 0) {
return <Error statusCode={503} />;
}
return (
<div>
<h1>Hacker News Clone</h1>
<StoryList storeis={stories} />
</div>
)
}
}
export default Index;
you have used storeis prop and using as stories
see this,
<div>
<h1>Hacker News Clone</h1>
<StoryList storeis={stories} /> // here prop is storeis
</div>
and used as this,
const StoryList = ({ stories }) => ( // and here using as stories
<div>
{stories.map(story => (
<h3 key={story.id}>{story.title}</h3>
))}
</div>
);
make both same.
I feel that was spell mistake.

Fetch data from API depending on the components props

I have problems figuring out how to setup the structure where to the fetch data depending on what the props.catergory value is in my PodcastList component and set state
I could fetch the data in my parent component (Home.js), set the state and pass the state as props. But the API endpoint need to take in an categoryId, I cant fetch all podcasts at once.. Thats why I made a child component that takes in and categoryId. Like this:
<PodcastList category='1301' />
And my tought was to do the fetch in the child component passing this.props.category to the api endpoint. (I accutally dont know what im doing)
Can someone help explain how to accomplish what I want?
My code looks like this:
Home.js component:
import React, { Component } from 'react'
import { fetchPopularPodcasts } from './api'
import PodcastList from './PodcastList'
export default class Home extends Component {
render() {
return (
<div className='container'>
<PodcastList category='1301' /> // Lists all Podcasts from category: Arts
<PodcastList category='1303' /> // Lists all Podcasts from category: Comedy
<PodcastList category='1304' /> // Lists all Podcasts from category: Educationrts
<PodcastList category='1305' /> // Lists all Podcasts from category: Kids & Family
</div>
);
}
PodcastList.js component
import React from 'react'
import { fetchPodcastCategory } from './api'
export default class PodcastList extends Component {
state = {
podcasts: [],
loading: true,
}
async componentDidMount () {
const podcasts = await fetchPodcastCategory(this.props.categoryId);
this.setState({
podcasts,
loading: false,
})
}
render() {
return (
<div className='row'>
<div className='col-md-12'>
{category.map((pod) => {
return (
<div className='pod-box'>
{pod.Title}
{pod.Label}
</div>
)
})}
</div>
</div>
)
}
}
export default PodcastList;
Api.js
import Feed from 'feed-to-json-promise'
export async function fetchPopularPodcasts () {
const response = await fetch('https://itunes.apple.com/search?term=podcast&country=se&media=podcast&entity=podcast&limit=200')
const podcasts = await response.json()
return podcasts.results
}
export async function fetchPodcastCategory (categoryId) {
const response = await fetch(`https://itunes.apple.com/se/rss/toppodcasts/limit=100/genre=${categoryId}/explicit=true/json`)
const podcasts = await response.json()
return podcasts.feed
}
export async function fetchPodcast (podId) {
const response = await fetch(`https://itunes.apple.com/lookup?id=${podId}&country=se`)
const podcasts = await response.json()
return podcasts.results
}
export async function fetchPodcastEpisodes (feedUrl) {
const feed = new Feed()
const episodes = await feed.load(feedUrl)
return episodes
}
I would do that inside podcastlist component, if you want data back to parent component you can run a callback,
give a function to podcastlist as a prop and run that function like this,
const podcasts = await fetchPodcastCategory(this.props.categoryId);
this.setState({
podcasts,
loading: false,
},this.props.callback(podcasts))
}
I don't think your design is all too bad.
Basically if you change
{category.map((pod) => {
with
{this.state.podcasts.map((pod) => {
this code should work.
What are you trying to accomplish exactly and why is this architecture not doing it for you? If you clarify this you can get a better answer.

Categories