I am new to next js. In my project I need to display youtube videos. I have an api which provides me the video ids to show with its meta details. I wanted to create dynamic pages for each videos. I am using react-player as player.
Here is my code
[videoId].tsx
import Head from 'next/head';
import { useRouter } from 'next/router'
import Layout from '../../components/layout';
import { IVideoItem } from '../../models/videos.model';
import VideoContainer from '../../components/videos-page/video-container';
import { getVideosPaths, getVideosPageTitle, getVideosPageDescription, getVideosData } from '../../services/videos-page.services';
export default function VideoPage({videoInfo} :IVideosPageProp) {
const router = useRouter()
if (router.isFallback) {
return <div>Loading...</div>
}
return(
<>
<Head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charSet="utf-8" />
<title>{getVideosPageTitle(videoInfo)}</title>
<meta name="description" content={getVideosPageDescription(videoInfo)} />
<meta property="og:title" content={getVideosPageTitle(videoInfo)} key="ogtitle" />
<meta property="og:description" content={getVideosPageDescription(videoInfo)} key="ogdesc" />
</Head>
<VideoContainer data={videoInfo} />
</>
)
}
export async function getStaticPaths() {
const paths = await getVideosPaths()
//console.log('paths: ',paths);
return {
paths,
fallback: false
}
}
export async function getStaticProps({ params }:IVideosPageStaticProp) {
const {videoId} = params;
const videoInfo = await getVideosData(videoId)
return {
props: {
videoInfo
}
}
}
interface IVideosPageProp {
videoInfo: IVideoItem
}
interface IVideosPageStaticPropParams {
videoId: string
}
interface IVideosPageStaticProp {
params: IVideosPageStaticPropParams
}
video-container.tsx
import { Row, Col } from 'react-bootstrap'
import { IVideoItem } from '../../models/videos.model';
import styles from './videos-container.module.scss';
import VideoTag from '../home/videos-block/video-tag';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faEye, faThumbsUp, faThumbsDown } from '#fortawesome/free-solid-svg-icons';
import moment from 'moment';
import dynamic from 'next/dynamic';
const ReactPlayer = dynamic(
() => import('react-player'),
{ loading: () => <p>...</p>, ssr: false }
)
export default function VideoContainer({data} :IVideosPageProp){
const videoInfo:IVideoItem = data;
const videoTag = [{"tagName": "Foo", "tagId": 1}]
const fallBackElement = () => {
return <img src={videoInfo.default_thumbnail_url} width="100%"/>
}
return (
<div className={styles['videos-container']}>
<ReactPlayer
url={`https://youtu.be/${data.video_id}`}
controls
width = "100%"
light={true}
playing={true}
fallback={fallBackElement()}
config={{
youtube: {
playerVars: { showinfo: 1 }
}
}}
/>
<div className={styles['videos-body']}>
<div className={styles['tag-list-container']}>
{videoTag.map((tag, index) =>{
return <VideoTag videoTag={tag} key={index}/>
})}
</div>
<div className={styles['video-title']}>
{videoInfo.title}
</div>
<Row className={styles['video-numbers']}>
<Col md={2} xs={2}><FontAwesomeIcon icon={faEye} className={styles['views-icon']} />{videoInfo.views_count}</Col>
<Col md={2} xs={4}>{moment(new Date(videoInfo.published_at)).format('Do MMMM YYYY')}</Col>
<Col md={4} xs={2}></Col>
<Col md={2} xs={2}><FontAwesomeIcon icon={faThumbsUp} className={styles['views-icon']} />{videoInfo.like_count}</Col>
<Col md={2} xs={2}><FontAwesomeIcon icon={faThumbsDown} className={styles['views-icon']} />{videoInfo.dislike_count}</Col>
</Row>
<div className={styles['video-description']}>
{videoInfo.description}
</div>
</div>
</div>
)
}
interface IVideosPageProp {
data:IVideoItem
}
When I run yarn dev the page is loading properly and the video player is rendering and working as expected. But when I run next buld and after that next start, the page is loading, but player is not loading. Insted it shows the "Loading..." message on the page, I refreshed several times, no luck. Not able to understand the issue. Can any one help?
Update 1:
The page is rendering with video title, video description etc. But the dynamically imported video player is not rendered. At the place of video player, it shows 'Loading...'.
Not sure if you can dynamically load from the node_module, like this:
const ReactPlayer = dynamic(
() => import('react-player'),
{ loading: () => <p>...</p>, ssr: false }
)
But you should be able to do this by creating a react-player component first, then dynamic import it like this:
// create a component named Player.js
import ReactPlayer from 'react-player';
const Player = props => (<ReactPlayer {...props}/>)
export default Player;
// then dynamic import it:
const Player = dynamic(
() => import('../components/Player'),
{ ssr: false }
)
// Then use <Player> with the same props
Related
I am trying to convert my existing code from Next js 12 to 13. But I am unable to make it work. How should I transform to make it work? Getting errors like you can't use "useState" etc.
import { useState } from 'react';
import Head from 'next/head';
import { loadData } from './api/post';
import {
Section,
Cover,
SocialNetworks,
BuyMeCoffee,
Title,
PostGrid,
Post,
Button
} from '../components';
const LOAD_MORE_STEP = 4;
export default function Home({ initialPosts, total }) {
const [ posts, setPosts ] = useState(initialPosts);
const [ loadedAmount, setLoadedAmount ] = useState(LOAD_MORE_STEP);
const [ loading, setLoading ] = useState(false);
const showLoadButton = total > loadedAmount;
const getMorePosts = async () => {
setLoading(true);
try {
const data = await fetch(`/api/post?start=${loadedAmount}&end=${loadedAmount + LOAD_MORE_STEP}`).then((response) => response.json());
setLoadedAmount(loadedAmount + LOAD_MORE_STEP);
setPosts([...posts, ...data.posts])
setLoading(false);
} catch (error) {
console.log(error);
setLoading(false);
}
};
return (
<div style={{
marginBottom: '1rem',
}}>
<Head>
<title>My blog</title>
</Head>
<Section>
<Cover title="Elena<br />Litvinova" />
<SocialNetworks />
<BuyMeCoffee />
</Section>
<Section>
<Title>New Post</Title>
<PostGrid>
{posts.map((post) => (
<Post
key={post._id}
{...post}
/>
))}
</PostGrid>
{showLoadButton && (
<div style={{
display: 'flex',
justifyContent: 'center',
}}>
<Button
disabled={loading}
onClick={getMorePosts}
>
Load more posts...
</Button>
</div>
)}
</Section>
</div>
)
}
export async function getServerSideProps() {
const { posts, total } = await loadData(0, LOAD_MORE_STEP);
return {
props: {
initialPosts: posts,
total
},
}
}
React hooks are only available on the client. You have to add 'use client' to the top of your file, if it is a client component. In Next.js 13 all components are React server component by default. Here are the docs for server and client components in Next.js
I would also recommend you to fetch your data with #tanstack/react-query.
I have deployed the next js app to the server using vercel. I have referenced the two google fonts in _document.js. While I am running the app locally both font load without any problem.
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document
{
static async getInitialProps(ctx)
{
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render()
{
return (
<Html>
<Head>
<link href="https://fonts.googleapis.com/css2?family=Crete+Round&family=Work+Sans:wght#500;600&display=swap" rel="stylesheet" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
index.js
import Head from "next/head";
import Script from "next/script";
import Banner from "../components/Banner";
import { fetchAPI } from "../lib/api";
import Articles from "../components/Articles";
export default function Home({ articles })
{
return (
<>
<Head>
<title>Life Sciencify - Explore the mystery of life with Science! </title>
</Head>
<Articles articles={articles} />
</>
);
}
export async function getServerSideProps()
{
const [articlesRes] = await Promise.all([
fetchAPI("/posts", { populate: ["cover", "category"] })
]);
console.log(articlesRes)
return {
props: {
articles: articlesRes.data
}
};
}
app.js
import Script from "next/script";
import "bootstrap/dist/css/bootstrap.css";
import "../styles/globals.css";
import { useEffect } from "react";
import Header from "../components/Header";
import SearchBlock from "../components/SearchBlock";
import Footer from "../components/Footer";
function MyApp({ Component, pageProps })
{
useEffect(() =>
{
import("bootstrap/dist/js/bootstrap");
}, []);
return (
<>
<Component {...pageProps} />
</>
);
}
export default MyApp;
After the deployment it is showing the weird behavior.
Initially When I am in the home page the page doesn't load any font.
Now, when I click the link Post1 or Post 2, it will be redirected to the detail page.
at first font is not loaded in this page too.
Now, after the page refresh the font gets loaded.
Now, when I go to the back page in the browser, the home page will have the font loaded. But again when the page is refreshed the font will be gone.
What is the causing the weird behavior?
I am running the application in the next js version of "12.1.6".
Referenced:
google-font-display
font-optimization
In the _document.js i used two google fonts separately and it is working now.
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document
{
static async getInitialProps(ctx)
{
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render()
{
return (
<Html>
<Head>
<link href="https://fonts.googleapis.com/css2?family=Work+Sans:wght#500;600&display=swap" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css2?family=Crete+Round&display=swap" rel="stylesheet" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
and in index.js, change server side rendering to static props:
export async function getStaticProps()
{
const [articlesRes] = await Promise.all([
fetchAPI("/posts", { populate: ["cover", "category"] })
]);
console.log(articlesRes)
return {
props: {
articles: articlesRes.data
}
};
}
After this changes I deployed to vercel it worked fine, again after some time i changes to getServerSideProps, it was not working. So, the culprit was getServerSideProps with google font.
tsx file having a Home component below it there is getServerSiderProps method.i want to get server side but props inside my Home component show undefined.
import type { NextPage } from 'next'
import Head from 'next/head'
import Banner from '../Components/Banner'
import Header from '../Components/Header'
import Posts from '../Components/Posts'
import { sanityClient, urlFor } from '../sanity'
const Home: NextPage = ({ posts }) => {
console.log(posts)
return (
<div>
<Head>
<title>Medium Blog</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Header></Header>
<Banner></Banner>
<Posts></Posts>
</div>
)
}
export default Home
//Server Side Rendering (SSR)
export const getServerSiderProps = async () => {
const query = `*[_type=="post"]{
_id,
title,
slug,
author->{
name,
image
},
description,
mainImage,
slug
}`
//Fetch
const posts = await sanityClient.fetch(query)
return { props: { posts } }
}
I want to get server side props every time it refresh my page but it became undefined.
what am i doing wrong here? i am trying to do getServerSideProps but
localhost is working fine when hosted i get
Internal Server Error 500
index.js
import React from "react";
import Head from "next/head";
import Navigation from "./navigation";
import { GetServerSideProps } from "next";
// import MyEditor from "./editor";
import Form from "react-bootstrap/Form";
import { useState } from "react";
import Questions3 from "../pages/question";
import axios from "axios";
import { FormControl, Button } from "react-bootstrap";
import InputGroup from "react-bootstrap/InputGroup";
function Home({ data }) {
const [Questions, setQuestions] = useState();
const [deatils1, setdeatils] = useState();
function clickQuestion() {
axios
.post("https://ask-over.herokuapp.com/questionpost", {
Name: Questions,
Summary: deatils1,
// username: this.props.sign.displayName,
// useremail: this.props.sign.email,
})
.then(() => {
window.location.reload();
});
}
function question(e) {
setQuestions(e.target.value);
// this.setState({ ask: e.target.value });
}
function deatils(e) {
setdeatils(e.target.value);
// this.setState({ ask: e.target.value });
}
return (
<>
<Head>
<title>wixten </title>
<meta
name="google-site-verification"
content="rqVH7Jc-L-NyyCYGf2LOEjRPFEUvi8iImncslSfxtac"
/>
<link rel="shortcut icon" href="/wixten.png" />
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<meta
name="description"
content="have all ur doubts cleared here at wixten . At wixten ask any thing you want and anyone in the world can see your questin and will be able to answer it "
/>
</Head>
<Navigation />
<div>
<div className="container search-box">
<Form>
<Form.Group className="mb-3" controlId="exampleForm.ControlInput1">
<Form.Label>Title</Form.Label>
<Form.Control
type="text"
onChange={question}
placeholder="ask anything?"
/>
</Form.Group>
<Form.Group
className="mb-3"
controlId="exampleForm.ControlTextarea1"
>
<Form.Label>question</Form.Label>
<Form.Control onChange={deatils} as="textarea" rows={3} />
</Form.Group>
</Form>
{/* <Form>
<InputGroup
className="mb-3"
// onChange={this.question}
// value={ask}
// value={this.state.ask}
>
<FormControl
placeholder="ask anything?"
aria-label="ask anything?"
// aria-label="ask anything?"
aria-describedby="basic-addon2"
/>
<FormControl as="textarea" rows={3} />
</InputGroup>
</Form> */}
<Button
type="submit"
disabled={!deatils1 || !Questions}
onClick={clickQuestion}
variant="outline-secondary"
id="button-addon2"
>
ask?
</Button>
<Questions3 data={data} />
</div>
</div>
</>
);
}
export async function getServerSideProps() {
// Fetch data from external API
const res = await fetch(`https://ask-over.herokuapp.com/questapi`);
const data = await res.json();
// console.log(data);
// Pass data to the page via props
return { props: { data } };
}
export default Home;
pages/question/index.jsx
import React from "react";
import Alert from "react-bootstrap/Alert";
import axios from "axios";
import { useState } from "react";
import { useEffect } from "react";
import Link from "next/link";
import Head from "next/head";
function Question3(props) {
const data = props.data;
return (
<div className="question11">
{data.map((itm) => (
<Link
key={itm._id}
href={{
pathname: "query/[itm]",
}}
as={`query/${encodeURIComponent(itm._id)}`}
>
<Alert className="question13">{itm.Name}</Alert>
</Link>
))}
</div>
);
}
export default Question3;
when i call http://localhost:3000/the question page is rendering
after deploying to vercel i get the following error
when i depolyed i get this error
This looks like it's a non-page component. You can't use getServerSideProps in non-page components.
Try calling the API from your page file and pass it down as props. You could also create a context.
getServerSideProps can only be exported from a page. You can’t export it from non-page files.
Source
Goal:
For React TS.
The page List1 and List2 should use the same method named useFetch (retrieve data by using api link) by using generic approach by sending the interface (named Client and TheComputer) to the useFetch.
Each interface has different datamember.
You should enable to use useFetch by many and different interface's name.
In other words,
UseFetch should be a independent tool that can be used by different interface by sending api link and interface asa parameter.
Problem:
You are enable to use react js to achieve it (without using syntax interface) but not for React TS.
I have problem to make useFetch as a independent component with react TS. How should it be solved?
Other info:
*It is achieved for ReactJS but not for ReactTS.
*Somehow it doesn't work in my local computer probably due to strictly linting and TS error.
You need to use interface to order to retrieve data and then display it.
*Newbie in ReactTS
Thank you!
Stackblitz:
JS
https://stackblitz.com/edit/react-mjvs38?
TS
https://stackblitz.com/edit/react-ts-7oeqen?
index.tsx
import React, { Component } from 'react';
import { render } from 'react-dom';
import {
BrowserRouter as Router,
Link,
Route,
Routes,
useParams,
} from 'react-router-dom';
import './style.css';
import useFetch1 from './useFetchTS1';
import useFetch2 from './useFetchTS2';
function App() {
return (
<div>
<h1>Home</h1>
</div>
);
}
function List1() {
const { data, loading, error } = useFetch1('https://api.github.com/users');
if (loading) {
return <div>Loading</div>;
}
return (
<div>
{data.map((item) => (
<div>
<img src={item.avatar_url} />
<div>{item.id}</div>
</div>
))}
;
</div>
);
}
function List2() {
const { data, loading, error } = useFetch2(
'https://jsonplaceholder.typicode.com/todos'
);
if (loading) {
return <div>Loading</div>;
}
return (
<div>
{data.map((item) => (
<div>
<div>
Id: {item.id} Title: {item.title}
</div>
</div>
))}
;
</div>
);
}
render(
<Router>
<div>
<header>
<Link to="/">Home</Link>
<br />
<Link to="/list1">List1</Link>
<br />
<Link to="/list2">List2</Link>
<br />
<hr />
</header>
<Routes>
<Route path="/" element={<App />} exact></Route>
<Route path="/list1" element={<List1 />} exact></Route>
<Route path="/list2" element={<List2 />}></Route>
</Routes>
</div>
</Router>,
document.getElementById('root')
);
useFetchTS1.tsx
import { useState, useEffect } from 'react';
interface Client {
id: number;
avatar_url: string;
}
export default function useFetch1(url) {
const [data, setData] = useState<Client[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function init() {
//debugger;
try {
const response = await fetch(url);
if (response.ok) {
const json = await response.json();
setData(json);
} else {
throw Response;
}
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}
init();
}, [url]);
return { data, error, loading };
}
useFetchTS2.tsx
import { useState, useEffect } from 'react';
interface TheComputer {
id: number;
title: string;
}
export default function useFetch2(url) {
const [data, setData] = useState<TheComputer[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function init() {
//debugger;
try {
const response = await fetch(url);
if (response.ok) {
const json = await response.json();
setData(json);
} else {
throw Response;
}
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}
init();
}, [url]);
return { data, error, loading };
}
There is a design that used to be called the Service Agent pattern that may work well for you:
Use React in the standard way, with useEffect etc
Views simply get type safe data and update their model
Views know nothing about APIs and just ask the agent class
The agent class can express the API interface
A lower level fetch class can do plumbing in a shared way
For sonething to compare against, see if any of my code is useful:
View classes
API classes
In these examples:
CompaniesContainer is the view class
ApiFetch sends and receives any type of API payload, and does common tasks such as refreshing OAuth tokens
ApiClient ensures that views use only type safe requests and responses
You can adapt some of this into a React hook if you prefer. Personally though I prefer to limit React syntax to view logic, and use plain Typescript classes in other places. I can then use equivalent classes in other types of UI, such as mobile apps.
So I believe we can get the useFetch hook to be generic for you if we change it to the following:
import { useEffect, useState } from 'react';
export default function useFetch1<TData = any>(url) {
const [data, setData] = useState<TData[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function init() {
//debugger;
try {
const response = await fetch(url);
if (response.ok) {
const json = await response.json();
setData(json);
} else {
throw Response;
}
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}
init();
}, [url]);
return { data, error, loading };
We use a generic <TData = any> in the function definition and TData[] in the useState hook for data.
Then in your index.tsx file you can define the interfaces there, and pass them to the generic useFetch1 hook like this:
useFetch1<Client>('https://api.github.com/users');
and
useFetch1<TheComputer>('https://jsonplaceholder.typicode.com/todos');
This lets you have the useFetch hook be generic, and still get the data returned to be the correct Interface/Type.
Your updated index.tsx file would look like this:
import './style.css';
import {
Link,
Route,
BrowserRouter as Router,
Routes,
useParams,
} from 'react-router-dom';
import React, { Component } from 'react';
import { render } from 'react-dom';
import useFetch1 from './useFetchTS1';
interface Client {
id: number;
avatar_url: string;
}
interface TheComputer {
id: number;
title: string;
}
function App() {
return (
<div>
<h1>Home</h1>
</div>
);
}
function List1() {
const { data, loading, error } = useFetch1<Client>('https://api.github.com/users');
if (loading) {
return <div>Loading</div>;
}
return (
<div>
{data.map((item) => (
<div>
<img src={item.avatar_url} />
<div>{item.id}</div>
</div>
))}
;
</div>
);
}
function List2() {
const { data, loading, error } = useFetch1<TheComputer>(
'https://jsonplaceholder.typicode.com/todos'
);
if (loading) {
return <div>Loading</div>;
}
return (
<div>
{data.map((item) => (
<div>
<div>
Id: {item.id} Title: {item.title}
</div>
</div>
))}
;
</div>
);
}
render(
<Router>
<div>
<header>
<Link to="/">Home</Link>
<br />
<Link to="/list1">List1</Link>
<br />
<Link to="/list2">List2</Link>
<br />
<hr />
</header>
<Routes>
<Route path="/" element={<App />} exact></Route>
<Route path="/list1" element={<List1 />} exact></Route>
<Route path="/list2" element={<List2 />}></Route>
</Routes>
</div>
</Router>,
document.getElementById('root')
);
This utilizes generic types: https://www.typescriptlang.org/docs/handbook/2/generics.html
I updated the stackblitz too and seems to be working: https://stackblitz.com/edit/react-ts-zasl3r?file=useFetchTS1.tsx