NextJS: handing over data/props to a dynamic page? - javascript

I'm building a NextJS application and struggle to pass data into dynamically generated pages.
In my application, data is fetched from an Amazon S3 bucket and mapped. The fetch works as intended delivering a set of tiles populated with data from the external source.
{data
?.filter(
(item: any) =>
selectedCategory === null ||
item.videoCategory === selectedCategory
)
.map((item: any) => (
<div key={item.id}>
{showOverlay && (
<div className="relative inset-0 flex h-80 w-80 animate-pulse items-center justify-center bg-red-500 text-center"></div>
)}
<Link
href="/videos/normal/[video]"
as={`/videos/normal/${item.videoID}`}
>
<HoverVideoPlayer videoSrc={item.videoLink} />
</Link>
</div>
))}
Now, I want every tile to link to a dynamically generated detail page, for this I have generated a slugged [video].tsx page.
The data on each dynamically generated page should be based on the 'videoID' property of the data / array it was mapped from. The data looks like this:
"id": "2",
"videoLink": "https://media.w3.org/2010/05/sintel/trailer_hd.mp4",
"videoLinkDeep": "https://media.w3.org/2010/05/sintel/trailer_hd.mp4",
"videoTitle": "Stopmotion",
"videoID": "100007_LX",
Linking to the page works, the page component is rendered correctly, but as far as I can tell - no data whatsoever is passed.
My [video].tsx is setup like this:
import React from 'react';
const VideoPage = (props: {
params: VideoParams;
json: any;
video: any;
data: any;
}) => {
if (!props.video) {
return <p>Video not found</p>;
}
return (
<div>
<h1>{props.video.videoTitle}</h1>
</div>
);
};
interface VideoParams {
id: string;
}
export async function getServerSideProps({ params }: { params: VideoParams }) {
const res = await fetch('https://www.externalsource.com/VideoData.json');
const json = await res.json();
console.log(params.id);
console.log(json);
const video = json.find((item: any) => item.link === params.id);
console.log(video);
if (video) {
return {
props: {
video,
},
};
}
return {
props: {
video: null,
},
};
}
export default VideoPage;
I have checked all spelling / import related issues and integrated a Fallbacks and multiple console logs to debug:
This fallback is triggered: delivering a 'Video not Found' when opening one of the dynamic pages
if (!props.video) {
return Video not found;
}
My console.log for the
console.log(video);
tells me that all data from the external source is fetched, but closes with an 'undefined'.
What is going wrong in the handover between the Link and the dynamic page?

When you define a dynamic route in NextJS, the name of the file will be used as the param in params. You're trying to access params.id, but the file is named [video].tsx, so you should be accessing params.video.
Here's the documentation on dynamic routes: https://nextjs.org/docs/routing/dynamic-routes

Related

How to properly organize mobx data structure in react app with complex hierarchy

I'm writing an app with react framework, mobx state manager and using typescript. The application includes a left menu and a workspace in the form of tabs. As in the picture below.
Visual representation of the application
Each tab contains its own content. As in any browser.Tabs can be created by clicking on the menu button, as well as by clicking on the button from the content of the tab itself.
For example, we open the first tab. Its content is a table that contains information about some list of elements. We can edit each element by clicking on the "Edit" button, this should open a new tab.
Table with editable data
Thus we get a hierarchical structure. Also, the same tabs should manage one global storage.
What MobX data structure would you suggest to use for such an application?
At the moment, I came up with the following data structure Based on the MVVM pattern.
There is a global tab manager which has the following methods: add tab, remove tab and set current open tab.
class TabManager {
currentTab: BaseTabViewModel | null = null
allTabs: BaseTabViewModel[] = []
constructor() {
makeAutoObservable(this, {}, { autoBind: true })
}
addTab<T extends BaseTabViewModel>(Tab: T) {
this.allTabs.push(Tab)
this.setCurrentTab(Tab)
}
deleteTab<T extends BaseTabViewModel>(Tab: T) {
this.allTabs.splice(this.allTabs.indexOf(Tab), 1)
}
setCurrentTab<T extends BaseTabViewModel>(Tab: T) {
this.currentTab = Tab
}
}
export default TabManager
Each tab is inherited from the base tab and is rendered by the view model.The base model looks like this.
class BaseTabViewModel {
id: string | null = null
title = ''
constructor(id: string, title: string) {
this.id = id
this.title = title
}
}
export default BaseTabViewModel
The tab itself might look like this::
class SomeTabViewModel extends BaseTabViewModel {
constructor(
id: string,
title: string,
private readonly _someStore: SomeStore,
private readonly _someService: typeof SomeService
) {
super(id, title)
this.getListItems()
}
get someStore() {
return this._someStore
}
getListItems = () => {
this._someService
.getListItems()
.then((someItemsResponse) => {
runInAction(() => {
this.someStore.setListItems(someItemsResponse)
})
})
.catch()
}
addSomeItem = (data: NewSomeItemRequest) => {
this._someService
.addSomeItem(data)
.then((someItemResponse) => {
runInAction(() => {
this.someStore.addSomeItem(someItemResponse)
})
})
.catch()
}
//...other methods
}
export default SomeTabViewModel
Services simply interact with the server through requests.
The store simply stores business entities and operations on them. The base store from which the rest are inherited:
export default class BaseListStore<TListItem extends Identifiable> {
protected items: Array<TListItem> = []
constructor() {
makeObservable<BaseListStore<TListItem>>(this, {
list: computed,
setItems: action,
addItem: action,
removeItem: action
})
}
get list(): Array<TListItem> {
return this.items
}
setItems(items: Array<TListItem>) {
this.items = items
}
addItem(item: TListItem) {
this.items.push(item)
}
removeItem(item: TListItem) {
this.items.splice(this.items.indexOf(item), 1)
}
}
export type BaseListStoreType = BaseListStore<Identifiable>
Tabs are rendered according to the following principle.
const MainTabs: FC = observer(() => {
const { allTabs, currentTab, deleteTab } = useTabManager()
return (
<div className={styles.tabsContainer}>
{allTabs.length !== 0 && (
<Tabs>
<TabList>
{allTabs.map((tab) => (
<Tab key={tab.id}>
<div className={styles.title}>{tab.title}</div>
<div className={styles.close} onClick={() => deleteTab(tab)}>
<FontAwesomeIcon icon={faTimes} />
</div>
</Tab>
))}
</TabList>
{allTabs.map((tab) => (
<TabPanel key={tab.id}>
{tab instanceof SomeTabViewModel && <SomeTabScreen content={tab} />}
{tab instanceof SecondSomeTabViewModel && (
<SecondSomeTabScreen content={tab} />
)}
</TabPanel>
))}
</Tabs>
)}
</div>
)
})
export default MainTabs
Complexity arises in those situations when we create a tab that depends on the previous one. Since the tabs are stored independently of each other and neither of them knows about the existence of the other, there is some difficulty.
Let's imagine that we have a tab with a table and we clicked on the "Edit" button from the example above. How to transfer a specific item there? I didn’t come up with anything better than creating a view model that stores information about a specific entity. Example:
class RedactorTabViewModel extends BaseTabViewModel {
constructor(
id: string,
title: string,
private readonly _specificItem: Item,
private readonly _notificationService: typeof SomeService
) {
super(id, title)
}
get item() {
return this._specificItem
}
getFile = () => {
if (!this.item.fileId) return
this._someService.getFile(this.item.fileId).then((data: File) => {
runInAction(() => {
this.item.setFile(data)
})
})
}
}
export default RedactorTabViewModel
But this approach, in my opinion, is not the most correct. Because we are modifying an entity that is in a particular store regardless of that store. What storage structure can you suggest or my approach is optimal?

Fetch only one element from Graphql in Gatsby

am new to gatsby and graphql and I came across a tutorial where it is mentioned to fetch all the data using .map. But I want to fetch only one element from the DB. So how do I do it?
import React from "react";
import Layout from "../components/layout";
import { useStaticQuery, graphql, Link } from "gatsby";
const Blogs = () => {
const data = useStaticQuery(
graphql`
query {
allMarkdownRemark(sort: { frontmatter: { date: ASC } }) {
edges {
node {
frontmatter {
title
date(formatString: "DD MM, YYYY")
}
excerpt
id
fields {
slug
}
}
}
}
}
`
);
return (
<Layout>
<ul>
{data.allMarkdownRemark.edges.map((edge) => {
return (
<li key={edge.node.id}>
<h2>
<Link to={`/blog/${edge.node.fields.slug}/`}>
{edge.node.frontmatter.title}
</Link>
</h2>
<div>
<span>
Posted on {edge.node.frontmatter.date}
</span>
</div>
<p>{edge.node.excerpt}</p>
<div>
<Link to={`/blog/${edge.node.fields.slug}/`}>Read More</Link>
</div>
</li>
);
})}
</ul>
</Layout>
);
};
export default Blogs;
Lets say I have multiple blogs and I wish to show only a specific one in a page through query like...
query MyQuery {
markdownRemark((id: {eq: "9ac19d6d"}) //Some ID {
title
description
content
}
}
How to get this on a page to display?
Thanks in advance!
Depending on what do you want to achieve:
If you want just a specific single post. You can filter your useStaticQuery to add the value of the id (if you know it beforehand) like:
query MyQuery {
markdownRemark((id: {eq: "123"}) {
title
description
content
}
}
useStaticQuery as the name points out, is static and doesn't accept dynamic values.
Another alternative is to get a specific position from data.allMarkdownRemark to display it.
If you just want a single post without any filter you can take advantage of the GraphQL query options:
{
allMarkdownRemark(limit: 1) {
edges {
node {
frontmatter {
title
}
}
}
}
}
If you are trying to create dynamic posts, hence each post template will display a different blog post (one per template), you need to pass a filter value from gatsby-node.js (where you create the post pages) to the template through Gatsby's context:
// gatsby-node.js
posts.forEach(({ node }, index) => {
createPage({
path: node.fields.slug,
component: path.resolve(`./src/templates/blog-post.js`),
context: {
id: node.id,
title: node.title
},
})
})
Note: here I'm lifting the id and the title. Use whatever works better for you
Now, you can take advantage of the context in your Blogs component (as long as it's a template):
const Blogs = ({data}) => {
console.log("your blog post is:", data)
return (
<Layout>
<h1>{data.markdownRemark.title}</h1>
</Layout>
);
};
export const query = graphql`
query($id: String!, $title: String!) {
markdownRemark((id: {eq: $id}) {
title
description
content
}
}
`
export default Blogs;
In other words: the first approach uses a static query (via useStaticQuery hook. Static, no dynamic parameters allowed) and the second uses a page query (only available in pages or templates)
With your query:
query MyQuery {
markdownRemark((id: {eq: "9ac19d6d"}) //Some ID {
title
description
content
}
}
Your data will be in data.markdownRemark
You can access those 3 fields directly.
const { title, description, content ] = data.markdownRemark;
return (
<Layout>
<div>
<p>{title}</p>
<p>{description]</p>
<p>{content}</p>
</div>
</Layout>
)

How to return a component/component-tag dynamically in vue/nuxt?

I am trying to convert a next.js app(https://medium.com/strapi/how-to-create-pages-on-the-fly-with-dynamic-zone-8eebe64a2e1) to a nuxt.js app. In this app I can fetch data from an API and the next.js app uses the APIs data to generate new pages with its corresponding content. Works well in Next.js.
The data/content from the API consists of Seo data for the page, some static values and very important of blocks. These blocks have an attribute called __component where the components name is saved and also have the components data like images, text, etc. So I only have to deal with next.js when adding new components.
In next.js I used the catch-all-route ./pages/[[...slug]].js to catch any slug an user may enter. Then the API is fired with the slug from the context.query and I get back the pages data if it exists. Now the APIs json data only needs to be passed to the blockmanager component.
const Universals = ({blocks}) => {
return <div><BlockManager blocks={blocks}></BlockManager></div>;
};
Here the blockmanager gets the json list of blocks, from which to parse the components.
import Hero from '../../blocks/Hero';
import Pricing from '../../blocks/Pricing';
const getBlockComponent = ({__component, ...rest}, index) => {
let Block;
switch (__component) {
case 'blocks.hero':
Block = Hero;
break;
case "blocks.prices":
Block = Pricing;
break;
}
return Block ? <Block key={`index-${index}`} {...rest}/> : null;
};
const BlockManager = ({ blocks }) => {
return <div> {blocks.map(getBlockComponent)} </div>;
};
BlockManager.defaultProps = {
blocks: [],
};
export default BlockManager;
How can I replicate this line now in nuxt js?
return Block ? <Block key={`index-${index}`} {...rest}/> : null;
How to return a component/component-tag dynamically in vue/nuxt ?
Is there maybe another solution to automatically insert the wanted component?
Maybe someones knows ho to convert the blockmanagers logic to vue/nuxt logic entirely.
I think you're looking for the is attribute. You can read about it here.
Your template would look like:
<component
:is="__component"
key={`index-${index}`}
/>
Ok I think I got it. No strange stuff actually. I thought about it too complicated. Wanted all dynamically created but no need as I saw later ...
<template v-if="blocks">
<div id="example-1">
<div v-for="({__component, ...rest}=block, i) in blocks" :key="i">
<Hero :data="rest" v-if="__component === 'blocks.hero'"/>
<Pricing :data="rest" v-if="__component === 'blocks.pricing'"/>
</div>
</div>
</template>
<script>
import Hero from '../../blocks/Hero/Hero.vue';
import Pricing from '../../blocks/Pricing/Pricing.vue';
export default {
components: {
Hero, Pricing
},
props: {
blocks: Array
}
}
</script>

I can't export the array I have created in one of my react components

I have been trying to export this array to dynamically render on another page (based on input from fakeApi) within my app and can't get it to work. I'm new to react and not sure if this is the correct way to achieve what I want.
Basically, I would like the full api (yet to create just using fake one for testing) to appear on one page (which is working). Then based on the information received from the array show which networks are down on the homepage of my app. Any help is much appreciated. Please see code.
import NetworkComponent from './NetworkComponent';
let fakeApi = [
{
key: 1,
HostIPAddress: "1.1.1.1",
HostFriendlyName: "BBC",
HostMonitored: "down",
HostType: "VPN"
},
{
key: 2,
HostIPAddress: "8.8.8.8",
HostFriendlyName: "johnlewis.co.uk",
HostMonitored: "ok",
HostType: "VPN"
},
{
key: 3,
HostIPAddress: "2.3.4.5",
HostFriendlyName: "hello.co.uk",
HostMonitored: "down",
HostType: "VPN"
},
];
const NetworkScan = () => {
return (
<div>
<h1>Network Monitor</h1>
<div className="container mx-auto">
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
{fakeApi.map(service => (
<NetworkComponent key={service.key} service={service} />
))}
</div>
{fakeApi.forEach((service) => {
if(service.HostMonitored === "down") {
let networkErr = [];
networkErr.push(service.HostMonitored, service.HostFriendlyName, service.HostIPAddress)
console.log(networkErr);
}
})};
</div>
</div>
);
};
export default NetworkScan;
You can export it as object export { fakeApi } and import it as import { fakeApi } from '../..'
Put that array object into a .json file (i.e. fakeApi.json) and import it like import fakeApi from './fakeApi.json' wherever you want.

GatsbyJS - TypeError: Cannot read property 'childImageFluid' of undefined

I am working on a Gatsby website, and I keep getting "TypeError: Cannot read property 'childImageFluid' of undefined"
The code I have is this in my Project.js file
import React from "react"
import PropTypes from "prop-types"
import Image from "gatsby-image"
import { FaGithubSquare, FaShareSquare } from "react-icons/fa"
const Project = ({description, title, github, stack, url, image, index}) => {
return (
<article className="project">
<Image fluid={image.childImageFluid.fluid} className="project-img" />
</article>
)
}
Project.propTypes = {}
export default Project
and I have the graphql set up in the index.js file where it will be displayed, and everything is working as it should in graphql...
export const query = graphql`
{
allStrapiProjects(filter: { featured: { eq: true } }) {
nodes {
github
id
description
title
url
image {
childImageSharp {
fluid {
...GatsbyImageSharpFluid
}
}
}
stack {
id
title
}
}
}
}
`
everything up to the what I am working on in the Project.js file is in my github - https://github.com/matthewbert86/gatsby-site but all of that code is in the first code section above.
When you use a page query in GraphQL, your gathered data is stored inside a data object (as a props). You need to iterate through it until you get your fluid image. It should be in: props.data.allStrapiProjects.nodes.image.childImageFluid.fluid. Since you are destructuring everything in your <Project> component:
const Project = ({ data }) => {
let { description, title, github, stack, url, image } = data.allStrapiProjects.nodes; // your data is here
return (
<article className="project">
<Img fluid={data.allStrapiProjects.nodes.image.childImageFluid.fluid} className="project-img" />
</article>
)
}
After destructuring, you can refactor it to:
<Img fluid={image.childImageFluid.fluid} className="project-img" />
Another point, I guess that the gatsby-image import should be Img, not Image.

Categories