Linking to page causes re-render of entire page in NextJS - javascript

I have static pages and some dynamic pages with following page components:
pages/home.js
pages/about.js
pages/search/[term].js
To link the pages I am using next/link. Going from static to static pages performs pretty well. However, when I navigate to pages/search/[term].js I can see that the entire page re-renders. This is pretty bad user experience if you are expecting the app to behave like an SPA. My assumption was the nextjs will render pages on server side and on all subsequent requests it will diff the components rendered with what needs to re-render and then re-render only updated components. If this was the case though, the Nav component would not re-render.
Is it possible to only have changing components re-render? I might be doing something wrong. I have tried making sure that I'm not unnecessarily changing props which may result in re-render but no luck so far. On Dev Tools I see 404 requests on static files which makes sense because this is a dynamic page: http://localhost:3000/_next/static/development/pages/search/hello.js net::ERR_ABORTED 404 (Not Found)
My layout looks like this:
import Head from 'next/head';
import Navbar from './Navbar';
const Layout = (props) => (
<div>
<Head>
<title>Some title</title>
</Head>
<Navbar />
{props.children}
</div>
);
export default Layout;
...and a page looks something like this:
import Layout from '../components/Layout';
const About = (props) => (
<Layout>
<main className="main">
<div className="container">
<h1>About</h1>
<Link href="/">Go home</Link>
</div>
</main>
</Layout>
);
export default About;
and [term].js:
class Search extends React.Component {
static async getInitialProps({ query, req }) {
return { query };
}
render() {
const { query } = this.props;
return (<p>{JSON.stringify(query)}</p>);
}
}
export default Search;

This was an oversight with dynamic link handling. NextJS was treating the link as static. To handle the dynamic links I should have added the as attribute to Link as per documentation:
<Link href="/search/[term]" as={`search/${term}`}>...</Link>
where the term is actual term value coming from props.
This fixed my problem.

Related

How to open/close a modal from my header component in Next.js 13?

I am building an e-commerce application with Next.js 13 for fun. I have difficulties when I try to make a Login modal that pops up when I click on a button in <Header/> component.
My approach would be make a useState in the parent, which is the rootLayout here, pass the setter to <Header/>, and pass the state to the modal.
But my question is, since I will be using useState to toggle the visibility of the modal, I will need to use client component by declaring "use client". But if I use "use client" in my root layout, and use the useState hook here, will it make my whole app client component? If that's the case, then this is not ideal. How should I implement it? Is it a bad layout? Should I change it? Thanks.
This is my root layout:
import "./globals.css";
import Header from "./Header";
import LoginForm from "#components/LoginForm";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<head />
<body className="relative">
<Header /> <-------------------------- THIS IS THE HEADER/NAVBAR COMPONENT
<main className="p-5">
<div>{children}</div>
</main>
<LoginForm /> <------------------------ THIS IS THE LOGIN MODAL COMPONENT
</body>
</html>
);
}

Render HTML of embed-deployed Component

I want to integrate my react app to a 3rd party app which enables me to put a script that appends the elements into the html.
But it appears that it is not that straightforward in react. The script does not append the elements so my custom component does not appear on the page.
This is what I am trying to do ( but not a gist ):
function Snippet){
return (
<Wrapper>
<h1> Snippet 1</h1>
<script src="https://gist.github.com/xxxx/e4208e452e32e353b6076944c80a1058.js"></script>
</Wrapper>
)
}
Is there a way to do this with just react?
Hi Going back to this inquiry, I found an npm package that enables the embedding of github gist in a react component.
Following the implementation of the Gist Component in the package, we can also deploy the code programmatically instead of pasting the deployment script directly to the component and it will work just fine.
export default class DeployedComponent extends React.Component{
constructor(props){
super(props);
}
componentDidMount(){
(function(props,doc,elementName,id){
let jsElement;
if(doc.getElementById(id)){
return;
}
jsElement = doc.createElement(elementName);
jsElement.id = id;
jsElement.src= "deployment_script_src";
doc.getElementsByClassName("cb-deployment-wrapper")[0].appendChild(jsElement);
})(this.props, document,"script","script_id");
}
render(){
return (
<div className="w-100 cb-deployment-wrapper ">
</div>
);
}
}

How to get element from another javascript

First, I would like to dynamic title tab.
Title tab change by every single page's <h2> Something</h2>
So I tried to make <h2 id="name">something</h2> I made title tab page as one single html page. and each of different javascript page has own <h2>
I tried to use var something =document.getElementById("name") then document.title=something like this.
But this main file can't get elements which is in external file.
Is there anyway I can make dynamic title tab?
no jquery.
Using ReactJS
You can create a component just for the title. Have that component accept a prop called "title" and then display that title.
Title Component: your title component can be a functional component
import React from 'react';
export default (props) => {
return (
<div className="your class names for this title">
<h2>{this.props.title}</h2>
</div>
)
}
This is perfectly fine functional component syntax. Just save the file as "Title.js". And you can import it in your parent component like so:
import Title from "./path/of/Title/Title";
And that will work just fine. If you are not comfortable with that syntax you can rewrite it like this:
const Title = (props) => (
<div className="your class names for this title">
<h2>{this.props.title}</h2>
</div>
);
This is perfectly valid as well. Next, let's discuss the parent component. Your parent component is your page. So, let's call this component "Home" just for this example.
Home Component: a class component (assuming it will have state but it does not have to be a class component)
import React, { Component } from 'react';
//import Title component
import Title from "./path/of/Title/Title"; //note: your component should be in a directory that has the same name as the component
export default class Home extends Component {
render() {
return (
<div>
<Title title="insert title here" />
<div>
Rest of your home component
</div>
</div>
)
}
}
That's it. You have a dynamic title. Now, let's say you want to pass a variable to the prop "title" instead of always hard coding a string. Well, you can update this line:
<Title title="insert title here" />
to this:
<Title title={nameOfVariable} />
And if that variable is coming from your state you can do this:
<Title title={this.state.nameofvariable} />
You can always destructure your state and do this instead:
render(){
const { nameofvariable } = this.state;
return (
<div>
<Title title={nameofvariable} />
<div>
Rest of your home component
</div>
</div>
);
}
That's all you need. Hope that helps. Good luck.

React.Children.only expected to receive a single React element child with <Link > component

I am learning react by myself. In my rendering loop I tried to add the element inside so that I can make the hyperlink for each data. But I got this issue:React.Children.only expected to receive a single React element child. Could someone know why it happened? Here is part of my code.Hope it make easier to understand my question. I skipped some parts of my coding as it seems the issue happened in the render part.
app.js
render() {
return (
<Router className="App">
<div>
<nav className="navbar navbar-default">
<div className="container-fluid">
<Link to="/coding-fun">Coding Fun</Link>
</div>
</nav>
<Switch>
// import condingFun.js file as Coding
<Route exact path="/coding-fun" component={Coding} />
<Route path="/coding-fun/:title" component={singleArticle} />
</Switch>
</div>
</Router>
);
}
}
codingFun.js
ps: posts is json data which I didn't add here as too many data.
class Coding extends Component {
render() {
return (
<div className="nav-text">
<h1>Coding fun page</h1>
// posts is data from api, and it renders listPage.js as
ListPage
<ListPage items={posts} />
</div>
);
}
}
export default Coding;
listPage.js
import React, { Component } from "react";
import { BrowserRouter as Link } from "react-router-dom";
class Listing extends Component {
constructor(props) {
super(props);
this.state = { data: this.props.items };
}
render() {
return (
<table>
<tbody>
// loop "post" data from parent component (items) codingFun.js
{this.state.data.map(post => (
<tr key={post.id}>
<td>
<Link to={"coding-fun/" + post.title}>{post.title}</Link>
</td>
<td>{post.content}</td>
</tr>
))}
</tbody>
</table>
);
}
}
If I just add
<Link to={"coding-fun/" + post.title}>{post.title}</Link>
this line, it got "React.Children.only expected to receive a single React element child." issue. If I only add {post.title} in the tag, there is no any issue. So I tried to make the title as link in each row. But I don't know how to make it.
Thanks a lot
The property to does not exist in BrowserRouter. You are also confusing yourself a little bit there by aliasing BrowserRouter with Link because there exists an actual component in react-router called Link. And this is how you use it:
import { Link } from 'react-router-dom'
<Link to={"coding-fun/" + post.title}>{post.title}</Link>
I'm guessing it's because you are doing the import wrong. The import statement should be import { Link } from 'react-router-dom'
You are confusing between Router, Link and Route. So basically,
Router is used to wrap your entire app, to make it fullstack-alike, which means the URL in address bar can be changed and the specific view is rendered respectively
Link is used the same way as <a> tag, which means that it will take you to that URL when clicked
Route, on the other hand, is used to decide what should be rendered under a specific link. This is what you should use to pass children component
In your case, you should change the import statement to:
import { Link } from 'react-router-dom'
Hope this help solve your problem :)

react-router v4: triggering a redirect programmatically (without having to render a <Redirect / >)

I'm currently switching my web app to react. The old one is located here.
What I'm trying to do is: when an user enter a player's username into the text field and submit, the app would redirect to the corresponding route (/:username), and the text field is cleared.
In the react version, this is what I'm doing currently:
https://github.com/AVAVT/g0tstats-react/blob/master/src/components/SideBar/SearchBox.js
submit(event){
...
this.setState({
redirect : true
});
...
}
And
render(){
...
{
this.state.redirect && (
<Redirect to={`/${this.state.username}`} push />
)
}
}
Which kinda work. But there are 2 things I don't like about it:
I'm rendering an element in order to redirect. It feels stupid and roundabout. It stinks of potential bug in the future.
I'm stuck with the text field not cleared. Because I if I set state.username to null the <Redirect /> component will not redirect correctly. In fact I don't have any precise control over when the redirection occur (unless I do it in another roundabout way).
I have searched for alternative, but couldn't find one. withRouter doesn't work because <SearchBox /> is not a <Route /> and doesn't receive the history props.
So how can I say "redirect me to that place NOW" in react-router v4?
Here is an example that shows when using the withRouter HOC, the routing props get injected to components even if they are not routed to.
Here is my App.js
class App extends Component {
render() {
return (
<div className="App">
<BrowserRouter>
<div>
<Route path='/test' component={Sample} />
<Sibling />
</div>
</BrowserRouter >
</div>
);
}
}
export default App;
Here is my Sample.js. This is like an example container that is rendering a child.
export default class Sample extends React.Component {
render() {
return (
<div>
<span>{this.props.location.pathname}</span>
<br />
<Nested />
</div>
)
}
}
This component can display information about the current route even without the withRouter HOC since it is being routed to.
Here is my Nested.js.
class Nested extends React.Component {
render() {
return (
<div>
<span>I am nested {this.props.location.pathname}</span>
</div>
)
}
}
export default withRouter(Nested);
My nested component needs the withRouter HOC in order to display the current route.
Finally here is my Sibling.js. (This is like your example where <SearchBox /> is a sibling.)
class Sibling extends React.Component {
render() {
return (
<span>{this.props.location.pathname}</span>
)
}
}
export default withRouter(Sibling);
Here all that is needed is to make sure that the sibling is nested within the router as you can see in my App.js, and then using the withRouter HOC it can display the current pathname.
To clarify: If a component can access the current pathname then it can also change the routes programmatically by doing this. this.props.history.push(some path).
I hope this helps.

Categories

Resources