Next.js: Passing data to nested routes - next.js

Issue:
Right now, I have a dynamic route that fetches data using getServerSideProps(). Within this page, there are multiple tabs that renders different data depending on state (which tab is selected).
I wish to transition from using multiple tabs on this page, to instead using nested routes. However, I am having difficulty obtaining the data originally fetched in these nested routes. Is there an efficient way of doing so, without having to call getServerSideProps() again?
My intended setup looks like this, where [page] calls getServerSideProps():
[page].jsx
|_tab1.jsx
|_tab2.jsx
|_tab3.jsx
My current [page].jsx, where I would like to use separate, nested pages that have access to these props (instead of rendering each tab based on state):
export default function Page(props) {
const [currentTab, setCurrentTab] = useState("home");
return (
<div>
<div id="tab1" onClick={() => setCurrentTab("home")}>
home
</div>
<div id="tab2" onClick={() => setCurrentTab("posts")}>
posts
</div>
<div id="tab3" onClick={() => setCurrentTab("info")}>
info
</div>
{currentTab === "home" ? (
<HomeTab props={props}/>
) : currentTab === "posts" ? (
<PostsTab props={props}/>
) : (
<InfoTab props={props}/>
)}
</div>
);
}
Attempts
I've attempted using the context API to utilize data globally, which my other pages can use. However, this requires the user to visit the original dynamic route first.
Call getServerSideProps() on each nested route. Although this works, I wish to find a better solution, since I'm fetching data on each nested route while the route they're nested under has all of this data available already.

You can use shallow routing in next/route or next/link
Note that, in the below example, I'm using next/link for the demonstration. Without your tab data, I'd assume you have an array of tabs in data
import { useEffect } from 'react'
import Link from 'next/link'
//the path is `/tab/:tabId`
function Tab({ data }) {
const [tabData, setTabData] = useState(data[0]) //first tab data as default for example
useEffect(() => {
setTabData(data[tabId])
}, [router.query.tabId])
return <>
<Link href="/tab/0" shallow />
<Link href="/tab/1" shallow />
<div>
{tabData}
</div>
</>
}
export async function getServerSideProps(context) {
return {
props: {
data: [], //tabs' data you fetched from the API
},
}
}
export default Tab

Related

nextjs localStorage getItem

after searching half a day I still not able to getItem from local storage.
the idea is to save some data to local storage and based on that I want to route a user in the Layout component. I am able to save to local storage and delete but not able to get data from it. I get error 'local storage not defined' or 'destroy is not a function'
I have 3 components save, delete and get. save and delete I execute after a client side api call, the get function I need to be working in the Layout as it is the top level for all routes.
I Need a bit help to the right direction please.
---Upadte
I found something that works
export const IsAuth = ()=>{
const [auth, setAuth] = useState();
useEffect(()=>{
if(typeof windows === undefined) return;
const item = localStorage.getItem('ltu');
setAuth(!!item);
},[]);
return auth;
}
now my problem is I have not much understanding of nextjs. I used the Layout to create a theme template, I basically have only 3 pages that can be visited if not logged in and the rest one needs to be logged in. I get so many examples but it seems like I need to verify auth on every single page instead of being able to do this on root/layout level.
all examples I get are without the use of Layout and I am totally stuck.
I want a simple login system just with jwt and check if thats there to show pages.
I could not get the localStorage.getItem() to work in the layout template.
My solution while maybe not perfect is.
in the _app.js I create useState() and pass those along to the menu trough the Layout, in in the menu useEffect() with 'use client' in the useEffect I set the state I need global.
_app.js
export default function App({ Component, pageProps }){
const [isAuth, setAuth] = useState()
const [user, setUser] = useState()
return (
<Layout setAuth={setAuth} isAuth={isAuth} user={user} setUser={setUser}>
<Component user={user} setUser={setUser} isAuth={isAuth} {...pageProps} />
</Layout>
)
}
Layout.js
export default function Layout({ children, setAuth, isAuth, user, setUser }) {
return (
<>
<Headd />
<SideMenu setAuth={setAuth} isAuth={isAuth} user={user} setUser={setUser}/>
<main>
<div className="menu-spacer"></div>
<content>
{children}
</content>
</main>
</>
)
}
menu.js
'use client';
const SideMenu = ({setAuth, isAuth, user, setUser}) => {
useEffect(()=>{
if(typeof windows === undefined) return;
const item = localStorage.getItem('ltu');
setAuth(!!item);
if(item) setUser(JSON.parse(localStorage.getItem('Ud')))
}, [router, router.isReady])
}
Now I can use the {isAuth, user,} on any page and component.
I am pretty sure this is not the right solution, but I could not find any other working solution and no one here yet posted a answer.

Next.js SSG page component gets new object from props every time it renders

I'm using Next.js and i want to build simple SSG page, which take immutable data from db on build time and render it to some list of elements. For that purpose i'm using getStaticProps. Here's source code of this page:
import Layout from '../components/Layout'
import utilStyles from '../global-styles/utils.module.css'
import { getSortedPostsData } from '../utils/posts'
import { useMemo } from 'react'
const Home = ({ allPostsData }) => {
const posts = useMemo(() => {
console.log('useMemo log');
return allPostsData.map(({ id, date, title }) => (
<li className={utilStyles.listItem} key={id}>
{title}
<br />
{id}
<br />
{date}
</li>
))
}, [allPostsData])
return (
<Layout home>
<ul className={utilStyles.list}>
{posts}
</ul>
</Layout>
)
}
export const getStaticProps = async () => {
console.log('getStaticProps run')
const allPostsData = await getSortedPostsData()
return {
props: {
allPostsData
}
}
}
export default Home
In production build I can see 'getStaticProps run' log once in console. All required data gets from database and passes to Home component in props. This component renders posts list fine. When i switch to this page in my browser first time I can see exactly one 'useMemo log' in the console. But...
Every time I switch to another page and then back to Home page I see another 'useMemo log' in the browser console too. I'm not understand why this is happening. This page loaded exactly once (not on every page switch). Data for this page is obtained exactly once (in build time). But Next.js passes new array (array with new address) in Home page props every time it renders. Why this is happening and how can i avoid this behavior to memoize my posts list and not render it every time I switch the page?

VueJS rendering data from REST service

I've attempted to render data from a http request to a component which is working fine, the issue is that it's null while the data is being fetched. While the data is null the console is throwing a TypeError until all the data is loaded and committed to the Vuex store.
All is working how I'd suspect, I'm just trying to figure how I can prevent the errors being thrown and to wait until all the appropriate data is fetched. I've seen others using v-if to check if the data is null which will work. It just seems tedious and that there surly is a better way to achieve the same outcome, without an application riddled with v-if statements checking every single state.
I came across this solution but it's still not working how I thought it would, I'm still receiving the same console errors. Am I using these key words correctly and are they in the correct location? since nothing has changed with every variation I've tried.
Vuex Action:
const actions = {
getThread ({ commit }, payload) {
Vue.http
.get(`http://localhost:9000/threads/${payload.id}`)
.then(async response => {
commit(FETCH_THREAD, await response.data)
})
}
}
This is within my vue file calling upon the action:
created () {
this.$store.dispatch('getThread', {id: '59280ab5acbafb17af9da902'})
}
I assume you are trying to display something from your store in your template. The problem is, Vue cannot render something that does not exist yet. The solution is to check whether the data exists or not.
Let's take this component example:
<template>
<div>
{{ someObject.name }}
</div>
</template>
<script>
export default {
data () {
return {
someObject: null
}
},
methods: {
fetchTheObject () {
this.someObject = {
id: 1,
name: 'My object'
}
}
},
created () {
setTimeout( () => {
this.fetchTheObject()
}, 3000)
}
}
</script>
As you can see, you will get an error in your console because someObject.name does not exist until fetchTheObject() has been called.
The solution is to put some v-if attribute to control that:
<template>
<div>
<span v-if="someObject === null">Fetching the object</span>
<span v-else>{{ someObject.name }}</span>
</div>
</template>
In general, you would want to display some spinner to show the user that something is loading...
Hope this helps
EDIT: And forget about the async await in your code, you don't need that here

How can I change the subscriptions query parameters in react-komposer (meteor) from a child component?

I'm building an app with Meteor using the react-komposer package. It is very simple: There's a top-level component (App) containing a search form and a list of results. The list gets its entries through the props, provided by the komposer container (AppContainer). It works perfectly well, until I try to implement the search, to narrow down the results displayed in the list.
This is the code I've started with (AppContainer.jsx):
import { Meteor } from 'meteor/meteor';
import { composeWithTracker } from 'react-komposer';
import React, { Component } from 'react';
import Entries from '../api/entries.js';
import App from '../ui/App.jsx';
function composer(props, onData) {
if (Meteor.subscribe('entries').ready()) {
const entries = Entries.find({}).fetch();
onData(null, {entries});
};
};
export default composeWithTracker(composer)(App);
App simply renders out the whole list of entries.
What I'd like to achieve, is to pass query parameters to Entries.find({}).fetch(); with data coming from the App component (captured via a text input e.g.).
In other words: How can I feed a parameter into the AppContainer from the App (child) component, in order to search for specific entries and ultimately re-render the corresponding results?
To further clarify, here is the code for App.jsx:
import React, { Component } from 'react';
export default class App extends Component {
render() {
return (
<div>
<form>
<input type="text" placeholder="Search" />
</form>
<ul>
{this.props.entries.map((entry) => (
<li key={entry._id}>{entry.name}</li>
))}
</ul>
</div>
);
}
}
Thanks in advance!
I was going to write a comment for this to clarify on nupac's answer, but the amount of characters was too restrictive.
The sample code you're looking for is in the search tutorial link provided by nupac. Here is the composer function with the corresponding changes:
function composer(props, onData) {
if (Meteor.subscribe('entries', Session.get("searchValues")).ready()) {
const entries = Entries.find({}).fetch();
onData(null, {entries});
};
};
The solution is the session package. You may need to add it to your packages file and it should be available without having to import it. Otherwise try with import { Session } from 'meteor/session';
You just need to set the session when submitting the search form. Like this for instance:
Session.set("searchValues", {
key: value
});
The subscription will fetch the data automatically every time the specific session value changes.
Finally, you'll be able to access the values in the publish method on the server side:
Meteor.publish('entries', (query) => {
if (query) {
return Entries.find(query);
} else {
return Entries.find();
}
});
Hope this helps. If that's not the case, just let me know.
There are 2 approaches that you can take.
The Subscription way,
The Meteor.call way,
The Subscription way
It involves you setting a property that you fetch from the url. So you setup your routes to send a query property to you Component.Your component uses that property as a param to send to your publication and only subscribe to stuff that fits the search criteria. Then you put your query in your fetch statement and render the result.
The Meteor.call way
Forget subscription and do it the old way. Send your query to an endpoint, in this case a Meteor method, and render the results. I prefer this method for one reason, $text. Minimongo does not support $text so you cannot use $text to search for stuff on the client. Instead you can set up your server's mongo with text indexes and meteor method to handle the search and render the results.
See what suits your priorities. The meteor.call way requires you to do a bit more work to make a "Search result" shareable through url but you get richer search results. The subscription way is easier to implement.
Here is a link to a search tutorial for meteor and read about $text if you are interested

Rendering React Components to Specific Div Using Routing

I am using FlowRouter as a router for a Meteor/React application I am trying to create. I'm having a very hard time trying to get my react components to render in specific places. Does anyone know how to do this?
So on my landing page, when I click a button, I want to route to a secondary page. I have three different components that I want to render in certain parts of the page. I've been using ReactLayout.render(), but I can't seem to make sure components get rendered in certain areas. I thought document.getElementById would work
ReactLayout.render(LandingPage, document.getElementById("landing-page")
but it hasn't been.
The second parameter of ReactLayout.render expects an object. If you want to render several components into your LandingPage element, it might look something like this:
LandingPage = React.createClass({
render() {
return (
<div className="app-root">
<AppHeader />
<div className="container">
{this.props.testOne}
</div>
<div className="app-root">
{this.props.testTwo}
</div>
</div>
);
}
});
Then render using:
FlowRouter.route( '/testRedirect', {
name: 'test',
action() {
ReactLayout.render( Default, { testOne: <TestOneComponent />, testTwo: <TestTwoComponent /> } );
}
});

Resources