I am working with Reactjs and Nextjs and i am working on dynamic routes,In other words i have list of blogs and now i want to display blog details for this i created folder name "blogs" and put file name "[slug.js"] inside this but unable to redirect to that url, Here is my current code in Allblog.tsx
<Link href={`/blog/${todoList.id}`}>Edit</Link>
And here is my code inside "blog/[slug.js]"
import Axios from "axios";
import {useRouter} from "next/router";
import { Editor } from '#tinymce/tinymce-react';
//import LatestBlogs from "../../components/LatestBlogs/LatestBlogs";
import Link from 'next/link'
import { useEffect, useState } from 'react'
import Sidebar from '../../components/Sidebar'
const slug = ({ posts }) => {
return(
<div>
<h2> Hello World </h2>
</div>
);
};
export default slug;
export const getStaticProps = async ({ params }) => {
const { data } = await Axios.get(`https://xxxxxxxxxxxxxx/api/getblogbyuserid/${params.slug}`);
const post = data;
return {
props: {
post,
},
};
};
export const getStaticPaths = async () => {
const { data } = await Axios.get("http://xxxxxxxxxxxxxxxxx/api/blogs");
const posts = data.slice(0, 10);
const paths = posts.map((post) => ({ params: { slug: post.id.toString() } }));
return {
paths,
fallback: true,
};
};
file name should be blog/[slug].js
Related
This question already has answers here:
useRouter/withRouter receive undefined on query in first render
(9 answers)
Closed 4 months ago.
I am having a problem getting the router.query to pull back the params of the url.
I have tried several different ways of approaching this problem and think that i am using the right solution. However, I keep getting an undefined result.
Here is the query param highlighted in yellow...
/pages/artist/[artistId].js
import React, { useEffect, useState } from "react";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { getDoc, doc } from "firebase/firestore";
import { db } from "../api/auth/firebase/config";
import Head from "next/head";
import ArtistHeader from "../../components/ArtistHeader";
import UploadButton from "../../components/UploadButton";
import styles from "../../styles/artistPage.module.css";
export default function Artist() {
const { data: session, status, loading } = useSession();
const [artist, setArtist] = useState();
const router = useRouter();
const { artistId } = router.query;
useEffect(() => {
if (status === "unauthenticated") {
router.push("/auth/signin");
}
}, [status, loading, router]);
// useEffect to fetch artist page data
useEffect(() => {
const fetchArtist = async () => {
const artistRef = doc(db, "users", `${artistId}`);
const docSnap = await getDoc(artistRef);
setArtist(docSnap);
};
fetchArtist();
}, [setArtist, artist, artistId]);
return (
<section className={styles.wrapper}>
<Head>
<title>{artist.screenName}</title>
</Head>
<div className={styles.artistPage}>
<ArtistHeader artist={artist} />
<div className={styles.songContainer}>
<UploadButton />
</div>
</div>
</section>
);
}
Is this problem being caused by the router not being ready and the page is trying to render before it is able to get the params?
Bit lost here and could use some pointers. Thanks in advance.
It should be either,
const { artistId } = router.query;
or
const artistId = router.query.artistId;
The problem was that i would get undefined when getting the router.query due to the useEffect being actioned before the router was finished doing its operation. now my code looks like that and works...
import React, { useEffect, useState } from "react";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { getDoc, doc } from "firebase/firestore";
import { db } from "../api/auth/firebase/config";
import Head from "next/head";
import ArtistHeader from "../../components/ArtistHeader";
import UploadButton from "../../components/UploadButton";
import styles from "../../styles/artistPage.module.css";
export default function Artist() {
const { data: session, status, loading } = useSession();
const [artist, setArtist] = useState();
const router = useRouter();
const artistId = router.query.artistId;
useEffect(() => {
if (status === "unauthenticated") {
router.push("/auth/signin");
}
const fetchArtist = async () => {
if (!router.isReady) return;
const artistRef = doc(db, "users", `${artistId}`);
const docSnap = await getDoc(artistRef);
setArtist(docSnap);
};
fetchArtist();
}, [status, loading, router, artistId]);
return (
<p> it works </p>
}
I'm in the process of building a merch e-commerce website for a client utilizing the commerce.js API however I've run into a problem. When passing the "cart" object as a prop to the checkout file it returns as an empty object which breaks the website. The web application passes the "cart" object as a prop in other parts of the code and works just fine. Is there something I'm doing wrong?
Code for reference:
import React, { useState, useEffect } from 'react';
import {Paper, Stepper, Step, StepLabel, Typography, CircularProgress, Divider, Button} from '#material-ui/core';
import { commerce } from '../../../lib/commerce';
import Addressform from '../Addressform';
import Paymentform from '../Paymentform';
const steps =['Shipping Address', 'Payment details'];
const Checkout = ({ cart }) => {
const [activeStep, setActiveStep] = useState(0);
const [checkoutToken, setCheckoutToken] = useState(null);
useEffect (() => {
const generateToken = async () => {
console.log(cart.id);
// returns as undefined
try {
const token = await commerce.checkout.generateToken(cart.id, { type: 'cart' });
console.log(token);
setCheckoutToken(token);
console.log("Success!")
} catch (error) {
console.log(error); //Returns 404 Error Obv
console.log("Didnt work")
}
}
generateToken();
}, []);
const Confirmation = () => (
<>
Confirmation
</>
);
const Form = () => activeStep === 0
? <Addressform />
: < Paymentform />
return(
<>
...
</>
);
};
export default Checkout;
I am using the official with-apollo example to create a nextjs frontend. I am trying to use the user's slug, which can be found in the url string to render the user profile. However, I am not able to use the url parameter (the slug) as a variable in the graphql query.
The Link to the user profile
<Link href={{ pathname: "/users/[slug]", query: { slug: user.slug } }}>
The user profile component
import { gql, useQuery } from "#apollo/client"
import ErrorMessage from "./ErrorMessage"
import { useRouter } from "next/router";
export const USER_QUERY = gql`
query getUser($slug: String!) {
user(slug: $slug) {
id
email
}
}
`
// I can not get this to work using url parameters
export const userQueryVars = {
slug: "userSlug", // This should be a url parameter!!
}
export default function UserProfile() {
const router = useRouter()
const userSlug = router.query.slug
const { loading, error, data } = useQuery(USER_QUERY, {
variables: {slug: userSlug},
})
if (error) return <ErrorMessage message="Error loading users." />
if (loading) return <div>Loading</div>
if (!data) return <div>No data</div>
const { user } = data
return (
<section>
<div>
<h3>
{user.firstName} {user.lastName}
</h3>
<p>{user.email}</p>
</div>
</section>
)
}
The user profile page
import App from "../../../components/App"
import Header from "../../../components/Header"
import UserProfile, {
USER_QUERY,
userQueryVars,
} from "../../../components/UserProfile"
import { initializeApollo, addApolloState } from "../../../lib/apolloClient"
const UserProfilePage = () => (
<App>
<Header />
<UserProfile />
</App>
)
export async function getServerSideProps() {
const apolloClient = initializeApollo()
await apolloClient.query({
query: USER_QUERY,
variables: userQueryVars, // This is passed from the component!
})
return addApolloState(apolloClient, {
props: {}
})
}
export default UserProfilePage
What I have tried so far (among a lot of other things):
Using router:
export const userQueryVars = {
slug: router.query.slug,
}
Error: You should only use "next/router" inside the client side of your app.
Using router and checking that is it called on client side:
if (process.browser) {
export const userQueryVars = {
slug: router.query.slug,
}
}
Error: 'import' and 'export' may only appear at the top level.
I would be very thankful for any kind of help!!
When using getServerSideProps you can find your slug (and all other dynamic params if you have them) inside context.params:
export async function getServerSideProps(context) {
const { slug } = context.params;
// Do whatever you need with `slug`
// ...
}
Very simple app, I'm trying to display content from my API using Mobx and Axios, here's my Axios agent.ts:
import { ITutorialUnit } from './../model/unit';
import axios, { AxiosResponse } from "axios";
//set the base URL
axios.defaults.baseURL = "http://localhost:5000/api";
//store our request in a const
const responseBody = (response: AxiosResponse) => response.data;
const requests = {
get: (url: string) => axios.get(url).then(responseBody),
};
//create a const for our activty's feature,all our activities' request are go inside our Activities object
const TutorialUnits = {
list: ():Promise<ITutorialUnit[]> => requests.get("/tutorialunits"),
};
export default{
TutorialUnits
}
then I call this agent.s in a store:
import { ITutorialUnit } from "./../model/unit";
import { action, observable } from "mobx";
import { createContext } from "react";
import agent from "../api/agent";
class UnitStore {
#observable units: ITutorialUnit[] = [];
//observable for loading indicator
#observable loadingInitial = false;
#action loadUnits = async () => {
//start the loading indicator
this.loadingInitial = true;
try {
//we use await to block anything block anything below list() method
const units = await agent.TutorialUnits.list();
units.forEach((unit) => {
this.units.push(unit);
// console.log(units);
});
this.loadingInitial = false;
} catch (error) {
console.log(error);
this.loadingInitial = false;
}
};
}
export default createContext(new UnitStore());
then I call this in my App component:
import React, { Fragment, useContext, useEffect } from "react";
import { Container } from "semantic-ui-react";
import "semantic-ui-css/semantic.min.css";
import NavBar from "../../features/nav/NavBar";
import { ActivityDashboard } from "../../features/Units/dashboard/tutorialUnitDashboard";
import UnitStore from "../stores/unitStore";
import { observer } from "mobx-react-lite";
import { LoadingComponent } from "./LoadingComponent";
const App = () => {
const unitStore = useContext(UnitStore);
useEffect(() => {
unitStore.loadUnits();
//need to specify the dependencies in dependenciy array below
}, [unitStore]);
//we are also observing loading initial below
if (unitStore.loadingInitial) {
return <LoadingComponent content="Loading contents..." />;
}
return (
<Fragment>
<NavBar />
<Container style={{ marginTop: "7em" }}>
<ActivityDashboard />
</Container>
</Fragment>
);
};
export default observer(App);
Finally, I want to use this component to display my content:
import { observer } from "mobx-react-lite";
import React, { Fragment, useContext } from "react";
import { Button, Item, Label, Segment } from "semantic-ui-react";
import UnitStore from "../../../app/stores/unitStore";
const UnitList: React.FC = () => {
const unitStore = useContext(UnitStore);
const { units } = unitStore;
console.log(units)
return (
<Fragment>
{units.map((unit) => (
<h2>{unit.content}</h2>
))}
</Fragment>
);
};
export default observer(UnitList);
I can't see the units..
Where's the problem? My API is working, I tested with Postman.
Thanks!!
If you were using MobX 6 then you now need to use makeObservable method inside constructor to achieve same functionality with decorators as before:
class UnitStore {
#observable units: ITutorialUnit[] = [];
#observable loadingInitial = false;
constructor() {
// Just call it here
makeObservable(this);
}
// other code
}
Although there is new thing that will probably allow you to drop decorators altogether, makeAutoObservable:
class UnitStore {
// Don't need decorators now anywhere
units: ITutorialUnit[] = [];
loadingInitial = false;
constructor() {
// Just call it here
makeAutoObservable(this);
}
// other code
}
More info here: https://mobx.js.org/react-integration.html
the problem seems to be the version, I downgraded my Mobx to 5.10.1 and my mobx-react-lite to 1.4.1 then Boom everything's fine now.
I've got component that displays contact information from a dealer as chosen by the user. To be more specific, a user selects their location, setting a cookie which then is used to define the API call. I pull in the contact information of the dealer in that location using Axios, store it in a context, and then display the information as necessary through several components: the header, a "current location" component etc. However, I'm having an issue with the content flickering each time the page is refreshed.
I've tried storing the JSON response in local storage, but, for a brief moment on page load, it shows as undefined, making the flicker continue. Obviously, I'm needing to eliminate that so that the data persists.
I've got it working via ApiContext, and I'm displaying the data in my Header component. Below is the code for both:
ApiContext.tsx
import React, { createContext, useEffect, useState } from 'react';
import axios from 'axios';
const contextObject = {} as any;
export const context = createContext(contextObject);
export const ApiContext = ({ children }: any) => {
const [selectedDealer, setselectedDealer] = useState(`1`);
useEffect(() => {
axios
.get(`${process.env.GATSBY_API_ENDPOINT}/${selectedDealer}`)
.then((response) => setselectedDealer(response.data));
}, [selectedDealer]);
const changeDealer = (id: any) => {
setselectedDealer(id);
};
const { Provider } = context;
return (
<Provider value={{ data: selectedDealer, changeDealer: changeDealer }}>
{children}
</Provider>
);
};
Header.tsx
import React, { ReactNode, useContext, useEffect, useState } from 'react';
import Logo from 'assets/svg/logo.svg';
import css from 'classnames';
import { Button } from 'components/button/Button';
import { Link } from 'components/link/Link';
import { MenuIcon } from 'components/menu-icon/MenuIcon';
import { context } from 'contexts/ApiContext';
import { NotificationBar } from '../notification-bar/NotificationBar';
import s from './Header.scss';
import { MainNav } from './navigation/MainNav';
interface HeaderProps {
navigationContent: ReactNode;
}
export const Header = ({ navigationContent }: HeaderProps) => {
const [scrolled, setScrolled] = useState(false);
const [open, setOpen] = useState(false);
const data = useContext(context);
const buttonLabel = data ? data.name : 'Find a Dealer';
const buttonLink = data ? `tel:${data.phone}` : '/find-a-dealer';
useEffect(() => {
const handleScroll = () => {
const isScrolled = window.scrollY > 10;
if (isScrolled !== scrolled) {
setScrolled(!scrolled);
}
};
document.addEventListener('scroll', handleScroll, { passive: true });
return () => {
document.removeEventListener('scroll', handleScroll);
};
}, [scrolled]);
return (
<>
<NotificationBar notificationContent={navigationContent} />
<header className={scrolled ? css(s.header, s.header__scrolled) : s.header}>
<nav className={s.header__navigation}>
<ul className={s.header__container}>
<li className={s.header__logo}>
<Link to="/" className={s.header__link}>
<Logo />
</Link>
</li>
<li className={s.header__primary}>
<MainNav navigationItems={navigationContent} />
</li>
<li className={s.header__utility}>
<Button href={buttonLink}>{buttonLabel}</Button>
</li>
<li className={s.header__icon}>
<MenuIcon onClick={() => setOpen(!open)} />
</li>
</ul>
</nav>
</header>
</>
);
};
I would assume that this is because the API call is being triggered each time the page is refreshed, so I wonder if there's any way to persist the data in a more efficient way?
Thanks in advance!
Your ApiContext.tsxcould persist the data in localStorage is such a way:
import React, { createContext } from 'react';
import axios from 'axios';
import { makeUseAxios } from 'axios-hooks';
import { useCookie } from 'hooks/use-cookie';
const contextObject = {} as any;
export const context = createContext(contextObject);
const useAxios = makeUseAxios({
axios: axios.create({ baseURL: process.env.GATSBY_API_ENDPOINT }),
});
const loadData = (cookie) => {
const stored = localStorage.getItem("data");
const parsed = JSON.parse(stored);
// You can also store a lastSync timestamp along with the data, so that you can refresh them if necessary
if (parsed) return parsed;
const [{data}] = useAxios(`${cookie}`);
if (!isEqual(parsed, data)) {
localStorage.setItem('data', JSON.stringify(data));
}
return data
}
export const ApiContext = ({ children }: any) => {
const [cookie] = useCookie('one-day-location', '1');
const [{ data }] = loadData(cookie);
const { Provider } = context;
return <Provider value={data}>{children}</Provider>;
};
The above implementation will only fetch the data once, so remember to refresh them at some point inside your code and update the localStorage item, or use a timestamp to compare and force the api call as commented in my code.
Keep in mind that even this implementation may take a fraction of a second to be completed, so I would suggest to always use loaders/spinners/skeletons while your application is fetching the required data.
I got this worked out, using a hook that persists my state, storing it in a localStorage item.
usePersistState.ts
import { useEffect, useState } from 'react';
export const usePersistState = (key: string, defaultValue: string) => {
const [value, setValue] = useState(() => {
if (typeof window !== 'undefined') {
const stickyValue = window.localStorage.getItem(key);
return stickyValue !== null ? JSON.parse(stickyValue) : defaultValue;
}
});
useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
};
Then, in ApiContext, I set my default state, but when that state changes, it updates and persists the state. Here's my context component now:
ApiContext.tsx
import React, { createContext, useEffect } from 'react';
import { usePersistState } from 'hooks/use-persist-state';
import axios from 'axios';
const contextObject = {} as any;
export const context = createContext(contextObject);
const LOCAL_STORAGE_KEY_SELECTED_DEALER = 'selectedDealerInformation';
export const ApiContext = ({ children }: any) => {
const [selectedDealer, setselectedDealer] = usePersistState(LOCAL_STORAGE_KEY_SELECTED_DEALER, '1');
useEffect(() => {
axios
.get(`${process.env.GATSBY_API_ENDPOINT}/${selectedDealer}`)
.then((response) => setselectedDealer(response.data));
}, [selectedDealer]);
const changeDealer = (id: any) => {
setselectedDealer(id);
};
localStorage.setItem(LOCAL_STORAGE_KEY_SELECTED_DEALER, JSON.stringify(selectedDealer));
const { Provider } = context;
return (
<Provider value={{ data: selectedDealer, changeDealer: changeDealer }}>{children}</Provider>
);
};