Retrieve Route Parameters in any Component - javascript

Suppose my URL looks something like this:
/blog/[post_id]/something
What is the recommended way to pass $post_id down to any component anywhere in the tree?
I know how to retrieve route parameters using getInitialProps but passing the values down is always giving me a hard time.
For pages I could technically use React Contexts although this seems a bit oversized for such a trivial use case.
For layouts I am honestly completely lost because pages are children of layouts and the return value of getInitialProps is passed to the page and not the layout.
My components could make use of useRouter but this requires useEffect and would also make my component depend on the route itself...
Any advice would be welcome (:

My components could make use of useRouter but this requires useEffect and would also make my component depend on the route itself...
useRouter seems like the obvious solution here. I'm not exactly understanding your concerns regarding the component depending on the route. I guess it does make the Layout less flexible since it needs to know that the post id is stored in the post_id query variable. But I would do it anyways :) It gives you a nice and simple way to access the query variables which can be used in a Layout that's outside of your BlogPost or in a deeply-nested component that you use inside the BlogPost.
Using the per-page layouts approach:
/components/Layout
import { useRouter } from "next/router";
import { ReactNode } from "react";
export default function Layout({ children }: { children: ReactNode }) {
const router = useRouter();
return (
<div>
<h3>You are viewing post id #{router.query.post_id}</h3>
{children}
</div>
);
}
/pages/blog/[post_id].jsx
import Layout from '../../components/Layout';
export default function BlogPost() {
return <div>Hello World</div>
}
BlogPost.getLayout = function getLayout(page) {
return (
<Layout>
{page}
</Layout>
)
}
/pages/_app.tsx (to support per-page layouts, copied from docs)
export default function MyApp({ Component, pageProps }) {
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout || ((page) => page)
return getLayout(<Component {...pageProps} />)
}

I think the easiest and the cleanest way is to use window.location.pathname. this will give you the part after the domain name. for example for
http://localhost:3001/blog/[post_id]/something
you will get /blog/[post_id]/something
const pathname=window.location.pathname
const splittedPathname=pathname.split("/") // ['', 'blog', '[post_id]', 'something']
const dynamicId=splittedPathname[2]
you can run above code in useEffect and set a state. or you could write a hook and use it in the components that under dynamicId components
import React, { useState, useEffect } from "react";
const usePathname = () => {
const [postId, setPostId] = useState("");
useEffect(() => {
const pathname = window.location.pathname;
const splittedPathname = pathname.split("/");
const dynamicId = splittedPathname[2];
setPostId(dynamicId);
}, []);
return { postId };
};
export default usePathname;

If you are looking for client side rendering, useRouter is the best way to go. If you are looking for SSR or SSG, you should rather use getStaticProps or getServerSideProps.

Related

Designing persistent layouts in Next.js

I'm going through this article and I'm trying to figure out how the persistence is supposed to occur in Option 4. From what I can tell, you'd need to redefine the .getLayout for every page. I'm not sure how the logic for nesting is incorporated into further urls.
Here's the code from the article
// /pages/account-settings/basic-information.js
import SiteLayout from '../../components/SiteLayout'
import AccountSettingsLayout from '../../components/AccountSettingsLayout'
const AccountSettingsBasicInformation = () => <div>{/* ... */}</div>
AccountSettingsBasicInformation.getLayout = page => (
<SiteLayout>
<AccountSettingsLayout>{page}</AccountSettingsLayout>
</SiteLayout>
)
export default AccountSettingsBasicInformation
// /pages/_app.js
import React from 'react'
import App from 'next/app'
class MyApp extends App {
render() {
const { Component, pageProps, router } = this.props
const getLayout = Component.getLayout || (page => page)
return getLayout(<Component {...pageProps}></Component>)
}
}
export default MyApp
For example, say AccountSettingsBasicInformation.getLayout is /settings/, how would I use this template to produce something at /settings/username
P.S. If someone has done something in the past they'd recommend over this, I'm open to ideas.
Yes, you have to redefine the getLayout function to every page. As long as the SiteLayout component stays “unchanged” (eg.no props change) the rendered content in that layout component (not the page content itself) stays persistent. This is because React wont rerender that component.
I used Adam’s article when I was building next.js lib for handlin modal routes. You can check the example folder where you can see I am defining the getLayout property on every page which should be rendered with layout.
Example: https://github.com/svobik7/next-bodies/tree/master/example

React.lazy() import can't find module if its path is from a constant

I'm trying to implement a contextual sidebar based on the route. The idea is that the container page is the same but the sidebar and some of the content is dynamically loaded.
React.lazy() does exactly what I want but for some reason, it only works if I define my module path as an actual string:
const SideNav = React.lazy(() =>
import("./navigation/concepts") // this is fine
);
However, I would very much like to make this dynamic so I could define a map of routes to module locations as such:
const module_map = {"/docs/concepts" : "./navigation/concepts"};
console.log(module_map[this.props.location.pathname] === "./navigation/concepts"); // Just for sanity: this print 'true'
const SideNav = React.lazy(() =>
import(module_map[this.props.location.pathname])
);
This results in an error:
Error: Cannot find module './navigation/concepts'
I tried defining the module path first as a constant and even passing it as a property from the parent component but the result is the same.
Any ideas why this is happening and how can I resolve it?
use __dirname instead of using relative paths.
import React, { lazy, Suspense } from "react";
const file = `${__dirname}/components/MyComponent`;
const MyComponent = lazy(() => import(file));
export default function App() {
return (
<Suspense fallback="Loading">
<MyComponent />
</Suspense>
);
}
codesandbox

How to route class component by clicking button in React?

I am new to React. I am trying to build a page and having like 3 button or img on main page. When I click either one, I shall be routed to another class component. You can treat it like click the icon and route you to another category page (just an example). Below is my structure and partial code I tried. I have no idea how to achieve that, and I googled and seems cannot find the stuff I want.
Structure:
/src
.index.js
.App.js
.BookStore.js
.FruitStore.js
.FoodStore.js
index.js
import React from "react";
import { render } from "react-dom";
import App from "./App";
render(
<App />,
document.getElementById('root')
);
App.js:
import React from "react";
import BookStore from "./BookStore";
const AppContainer = () => {
return (
//do the routing
<BookStore/>
)
};
export default AppContainer;
BookStore.js
export default class BookStore extends React.Component {
}
const contentDiv = document.getElementById("root");
const gridProps = window.gridProps || {};
ReactDOM.render(React.createElement(BookStore , gridProps), contentDiv);
First, you could have a look at the/one react router, e.g. https://reactrouter.com/web/guides/quick-start
However, since you're writing you're new to react, this might be a little too much ...
First, I was wondering why you're using the "ReactDOM" in your indexjs (that seems to be correct), but also in the BookStore.js. I would also recommend to write your components as functions, like your "AppContainer" and not use the class components anymore (or do you really need to do that? - why?). You can use hooks instead to have e.g. state in the components.
You would then need any kind of state in your AppContainer which is used for the routing. Maybe like this:
const AppContainer = () => {
const [showDetail, setShowDetail] = useState();
return <>
{!showDetail && <BookStore onDetail={detail => setShowDetail(detail)} />}
{showDetail && <DetailPage detail={showDetail} onBack={() => setShowDetail(undefined)}}
</>
}
Your AppContainer then has a state wheter or not to show the Bookstore (which is shown when "showDetail" is falsy, or a DetailPage which is shown when showDetail is truthy.
For this to work, your Bookstore needs to provide callbacks to let the AppContainer know that something should change. Very simply it could look like this:
const BookStore = ({onDetail}) => {
return <button onClick={() => onDetail("anything")}>Click me</button>
}
Now when someone clicks the button on the bookstore, it calls the "onDetail" callback, which was set in the AppContainer to set the "showDetail" state. So this one will be updated to "anything" in this case. This will result in a rerender on the AppContainer which will now render a DetailPage component instead.

Persist data between two pages with Next.js

I would like to refactor my Next.js webapp to have different pages handle different screens. Currently, I have this component holding several states to know in which screen I'm in. In the jsx section, I'm using {value && ... } to render the right component.
But I feel this is not good design, and won't be maintainable when adding more and more screens.
I would also like to avoid Redux as it is overkill for my project.
I was thinking about persisting data in cookies so I can retrieve them with getInitialProps in every component when rendering a new page, but is there a more elegant way?
I've read about tweaking the _app.js but I'm not sure to understand the consequences of doing so, and how it could help me..
Any suggestion?
When multiple of your pages need to make use of same data, you can make use of Context to store the result. It a good way to make a centralized storage without using complex and more self sufficient libraries like redux
You can implement context inside of _app.js file which must reside inside your root folder. This way next.js treats it as a root wrapper and you would just need to use 1 instance of Context
contexts/appContext
import React from 'react';
const AppContext = React.createContext();
export const AppProvider = AppContext.Provider;
export const AppConsumer = AppContext.Consumer;
export default AppContext;
_app.js
import React from 'react'
import App from 'next/app'
import AppProvider from '../contexts/appContext';
class MyApp extends App {
state={
data:[]
}
render() {
const { Component, pageProps } = this.props;
// You can implement logic in this component to fetch data and update state
return (
<div>
<AppProvider value={this.state.data}> // pass on value to context
<Component {...pageProps} />
</AppProvider>
</div>
)
}
}
export default MyApp
Now further each component can make use of context value by using AppConsumer or using useContext if you use hooks
Please read more about how to use Context here

having access to a parent state from component's props.children:

I am making a container for a d3 line graph that I'm going to create, my format so far is this:
import React from "react";
import AnalyticPads from "../AnalyticPad/AnalyticPad";
import Pad from "../AnalyticPad/Pad";
import MainContent from "../AnalyticPad/MainContent";
import Extention from "../AnalyticPad/Extention";
const GraphPad = () => {
return (
<AnalyticPads properties={{height: "200px"}}>
<Pad>
<MainContent>
</MainContent>
<Extention>
</Extention>
</Pad>
</AnalyticPads>
)
}
export default GraphPad;
And my "AnalyticsPad" looks like this:
import React from "react";
const AnalyticPads = (props) => {
return (
<div className="analytic-pad-container">
{props.children}
</div>
)
}
export default AnalyticPads;
What I want is that there will be a grid of "Pads" and I want this "AnalyticsPad" to provide default styles for each pad, for example if I want each pad to have a height of 200px I set it in this wrapper and then for any individual pad that I want to differ from the default I can overide it.
The "MainContent" component is where the line graph will be and any extra information will be put inside the "Extention" which will render if a button is pressed.
Throughout my react app I keep using the context api to provide the data to the children components, but I know ( or think ) it is bad practice to do this, from what I understand context should only be used for providing data to global components.
What is best practice?
please don't answer with a solution for class components as I have never used them before as I am new to react.

Categories

Resources