npx create-next-app

took ~ 1min


Add components\note\NewNoteForm.js and its css

return above component from main index.js pages\index.js

import NewNoteForm from "../components/note/NewNoteForm";

export default function Home() {
  return <NewNoteForm />;
}

Notice that Card is needed

Add:

  1. components\ui\Card.js
  2. components\ui\Card.module.css

Note finally looks:

Styling needed at root: Remember this, maybe _app.js that will work on ALL pages. Will do later

Customize NewNoteForm

Add a heading

remove unwanted fields and code to read them

Set button to 100% width

commit

Add a Login Page

Add components\login\LoginForm.js Add components\login\LoginForm.module.css

Customize LoginForm.js

Add pages\login.js

import LoginForm from "../components/login/LoginForm";
export default function Home() {
  return <LoginForm />;
}

on localhost:3000/login

commit


Do something with Login Data

From form (component) > page

page has a handler that was passed as prop to form.

write that handler in page:

pages\login.js

import LoginForm from "../components/login/LoginForm";

function onLogin(loginData) {
  console.log(loginData);
}

export default function Home() {
  return <LoginForm onLoginHandler={onLogin} />;
}

Logs it in browser: console.log(loginData);

commit


Make the login info go to backend

Create a new route

pages/api/login

export default function handler(req, res) {
  res.status(200).json({ name: "John Doe" });
}

get the req body params, by req.body:

export default function handler(req, res) {
  const loginData = req.body;
  console.log(loginData);     //notice this log!! see later what it prints
  // res.status(200).json({ name: "John Doe" });
}

Write the part that sends req: in pages/login.js

  //make API call to backend to login
  fetch("/api/login", {
    method: "POST",
    body: loginData,              //WRONG! always stringify whatever you add in body - send it as JSON in body.
  }).then((res) => {
    console.log(res.json());    // see later that this logs a Promise! I didnt want that!
  });

Kept getting:

from req sender part:

this console.log should be response from server I was expecting, why is it a promise? Will check this after taking a look at server.

BUT on the server log in api route (previous code block in pages/api/login.js) the log was strange:

[object Object]

This is strange. I was expecting

{"username":"asdf","password":"asdf"}

Maybe I need to parse? But parsing gave error!

SyntaxError: Unexpected token o in JSON at position 1

Parse fails like this because the thing you are trying to parse is NOT a valid JSON. and this particular hint that there is o in “JSON” at position 1 usually shows the common case where [object Object] is the string you are trying to parse (thinking it is JSON). Whereas it is actually a string but not JSON string - "[object Object]". This SO answer helps

Why is there a string "[object Object]" in req.body - what do I do with a string like this!
its not an object - its not a JSON string!!! - this made me realize that the request sending has happend incorrectly !!

The req body when doing POST req should not be made to contain an object directly - otherwise be prepared for "[object Object]" at server req.body 😜

Correct thing - ALWAYS send an JS OBJECT as JSON string in req body

In other words, ALWAYS JSON.stringify any object you wanna pass in request body when doing POST call.


And why is the req. sender logging a promise?

Reading docs of fetch

Googled node fetch Nahi Nahi browser fetch

from MDN docs

The fetch() method takes one mandatory argument, the path to the resource you want to fetch. It returns a Promise that resolves to the Response to that request — as soon as the server responds with headers — even if the server response is an HTTP error status. You can also optionally pass in an init options object as the second argument (see Request).

Then I saw how this works!

fetch('http://example.com/movies.json')
  .then(response => response.json())
  .then(data => console.log(data));

cant stop at response.json(), even that returns a promise. use one more then and you get the data.

Cool thing is, you get the data as object. That is what .json() did - get the json part from response and return object of it.

Finally saw this works (see comments for learning from mistakes)

  //make API call to backend to login
  fetch("/api/login", {
    method: "POST",
    body: JSON.stringify(loginData),
  })
    .then((res) => {
      // console.log(res.json());        //This causes problem!!! cant log and return later. Do .json() only once, when returning. Doing it during  logging and returning both, caused error 
      return res.json();
    })
    .then((data) => console.log("final: " + data.name));

It printed final: John Doe as expected, from the fake response from our server.

Also learnt that cant do .json more than once.

body stream already read

remove comment and good to go.


So above code fixed everything.

But there’s more!!

we can improve: instead of promises, use asyc await

async function onLogin(loginData) {             // ADD async
  console.log(loginData);
  
  //make API call to backend to login
  const resp = await fetch("/api/login", {      // NOTE: await instead of promises
    method: "POST",
    body: JSON.stringify(loginData),
  });
  
  const data = await resp.json();
  console.log("final: " + data.name);
  
  //redirect using next/router > useRouter > .replace
}

export default function Home() {
  return <LoginForm onLoginHandler={onLogin} />;
}

works!

commit


Redirect after login

But got this error:

# Unhandled Runtime Error

ReferenceError: router is not defined

##### Source

pages\login.js (17:2) @ _callee$

 15 |   16 | //redirect using next/router > useRouter > .replace
> 17 |  router.push("/");
     | ^
 18 | }
 19 |  20 | export default function Home() {

Problem was:

Should have done everthing in React Function Component:

Corrected code:

import LoginForm from "../components/login/LoginForm";
import { useRouter } from "next/router";

export default function Login() {
  const router = useRouter();
  async function onLogin(loginData) {
    console.log(loginData);
    //make API call to backend to login
    const resp = await fetch("/api/login", {
      method: "POST",
      body: JSON.stringify(loginData),
    });

    const data = await resp.json();
    console.log("final: " + data.name);

    //redirect using next/router > useRouter > .replace
    router.replace("/");
  }
  
  return <LoginForm onLoginHandler={onLogin} />;
}

Works!

Note: Clearly saw that had to restart the server npm run dev - then it worked fine. Without restarting it didnt work, wa showing old error! Seems to because change was done in RFC


List of Notes (home after login)

new home /pages/index.js

import NoteList from "../components/home/NoteList";

export default function Home() {
  return <NoteList />;
}

Create NoteList

mock NoteCard for now:


Next thing to do:

  • Improve the cards now.
  • Introduce Navigation
  • Introduce props with dummy data
  • Introduce data fetching - DB
  • Login

Realized the usefulness of a Trello like utility/workflow

have a list - so you know the steps for any next work too, and current work go smooth, with next steps visible.

know that have to add it in _app.js but dont remember the components. Refer example.

Create:

/components/layout/Layout.js

components\layout\MainNavigation.js

export default function MainNavigation() {
  return (
    <div>
      <h1>Your Notes</h1>
    </div>
  );
}

Add this to _app,js

--- a/pages/_app.js
+++ b/pages/_app.js
@@ -1,7 +1,13 @@
-import '../styles/globals.css'
+import MainNavigation from "../components/layout/MainNavigation";
+import "../styles/globals.css";

 function MyApp({ Component, pageProps }) {
-  return <Component {...pageProps} />
+  return (
+    <div>
+      <MainNavigation />
+      <Component {...pageProps} />
+    </div>
+  );
 }

No clue on what to add in Layout.

Referring example: For CSS really no clue. So copy pasting:

Layout.module.css - turns out very simple!!

.main {
  margin: 3rem auto;
  width: 90%;
  max-width: 40rem;
}

MainNavigation.module.css from example

Using Layout:

import MainNavigation from "./MainNavigation";
import classes from "./Layout.module.css";

export default function Layout(props) {
  return (
    <div>
      <MainNavigation />
      <main className={classes.main}>{props.children}</main>
    </div>
  );
}

And actually use Layout in _app.js

--- a/pages/_app.js
+++ b/pages/_app.js
@@ -1,11 +1,13 @@
+import Layout from "../components/layout/Layout";
 import MainNavigation from "../components/layout/MainNavigation";
 import "../styles/globals.css";

function MyApp({ Component, pageProps }) {
   return (
     <div>
-      <MainNavigation />
-      <Component {...pageProps} />
+      <Layout>
+        <Component {...pageProps} />
+      </Layout>
     </div>
   );
 }

Improve how inbox looks - NoteList

really dont know the CSS - copy pasting

components\home\NoteList.module.css

Ye to chota sa hai!

.list {
  list-style: none;
  margin: 0;
  padding: 0;
}

NoteCard - styling

from here

Now use it in js

  • return a li

  • Wrap it with whatever Card was - because elements can have children - you created Card without content - thinking where will component come! But Card’s usage and its filling was not done just by what you see on Card - it is done by creating children.

Since the Card has button, it will need a function here, to work:

Change NoteList - dynamic using map


Going to make a [ ].js dynamic page.

create a new folder

/pages/[nodeId]/index.js

  • can name a file: [pageId].js
  • or can name a folder: [pageId]/index.js

Created this file:

export default function index() {
  return <div>This is {id}</div>;
}

Connect to DB

DBManager

npm install mongodb


Noticed that if using

const { MongoClient, ServerApiVersion } = require("mongodb");

If ServerApiVersion is not in use in getStaticPaths or GSP for example, then we get error: Not sure, will need to check more

Mongo takes some time to deploy password change too:

I was facing problem in connecting to DB - auth failed.

I though password change on Mongoi Atlas UI is now working - because it didnt show new password. Showed something that was Chrome’s password maanger. It was a little confusing. Used other browser - it also didnt show set password editing the user.

To be sure, Created new cluster - deleted exising cluster (allowed only 1 free cluster)

Finally found solution - itwas the file not getting correctly the user and pass- I was exporting a function from my secrets module. changeed it to export an object.


This SO artivle was very useful when I faced the error:

Error: Error serializing .noteId returned from getStaticProps in “/[noteId]”. Reason: undefined cannot be serialized as JSON. Please use null or omit this value.

In following, confused name of context with params.

it was

params.params.noteId and I thought it looked strange.

Actually

context.params.noteId is actual. **Rememer - ** lesson learnt - GSP has context as parameter (although name doesnt matter much - does not have to be context)

export async function getStaticProps(context) {
  const noteId = context.params.noteId;
  console.log(`ANN2 In props, noteId = ${context.params.noteId}`);

  return {
    props: {
      noteId,
    },
  };

Did some more plumbing - got things working with DB - able to open single Note fetching data from DB.

While looking at higher picture - realized that: URL should be:

/[userId]/new-note

Unauthenticated.

/[userId]/inbox

Protected. if session > proceed. if no session > redirect to Login

Is there no way except bringing the username in URL ?

leads to

Can I make a link click do POST request? - Never thought this

  1. https://stackoverflow.com/questions/1651197/how-do-you-force-a-web-browser-to-use-post-when-getting-a-url
  2. https://stackoverflow.com/questions/3915917/make-a-link-use-post-instead-of-get

Anyway, URL seems much clean, and doable for my use.

Or maybe query param?

whole question comes to:

URL route –vs– Query Param

query param can look like

/inbox?user=id // protected

/new-post?user=id //

https://stackoverflow.com/questions/49035670/is-it-wrong-to-use-routes-versus-query-string-parameters

Not super duper important.

I will go with query params then! 😄

How to get query string param from URL in nextjs - this SO


Revising an old open point:

If using DB stuff outside of GSP, then need to do stringify and then parse again, otherwise get this error:

Server Error
Error: Error serializing `.notes[0].id` returned from `getStaticProps` in "/".
Reason: `object` ("[object Object]") cannot be serialized as JSON. Please only return JSON serializable data types.

Confirmed because when I removed the stfingify and parse, got the above error:

More here: https://github.com/vercel/next.js/issues/11993#issuecomment-617375501


Back.

To work with the login etc, need to think in detail, what user journeys we have - to understand the features needed to be added:

Add the pages:

  • user does not exist
  • your message is sent
export default function NotFound(props) {
  return (
    <div>
      <h1>User Not Found</h1>
      <p>The user you are looking for does not exist.</p>
    </div>
  );
}

pages\sent.js

export default function NotFound(props) {
  return (
    <div>
      <h1>Message Sent!</h1>
      <p>Your message has been sent to ___ anonymously.</p>
    </div>
  );
}

Realized can also do like - a dedicated page, but shows component inside, so that props can be passed to the component.


Read query param from URL, for /new-post?user=

Google “reading query param in nextjs” This SO

import { useRouter } from 'next/router'

export default () => {
  const router = useRouter()
  console.log(router.query);

  ...
}

but was getting empty object on logging (on strigify saw that object is empty,)

import { useRouter } from "next/router";

export default function NewNote() {
  const router = useRouter();
  console.log("query param" + JSON.stringify(router.query));

Saw some answers about it:

https://github.com/vercel/next.js/issues/10521

Noticed every doc is using the values in HTML, console log wil be empty because of:

I tried same, and got the values:

btw, using object directly inside fails with error: Error: Objects are not valid as a React child (found: object with keys {}) More on this by googling: https://www.g2i.co/blog/understanding-the-objects-are-not-valid-as-a-react-child-error-in-react

This is correct behavior and documented here: https://nextjs.org/docs/routing/dynamic-routes#caveats Pages that are statically optimized by Automatic Static Optimization will be hydrated without their route parameters provided, i.e query will be an empty object ({}). After hydration, Next.js will trigger an update to your application to provide the route parameters in the query object.

THought if getting it in getStaticProps, but

cant get query param in GSP : https://stackoverflow.com/a/64159389/11515646 because it is run serverside.

Trying to use:

Also referred to as “SSR” or “Dynamic Rendering”. If a page uses Server-side Rendering, the page HTML is generated on each request.

The looking at this discussion realized that why am I worrying about getting data extracted from query param, when Next lets us do it from the URL route itself! the dynamic route.

use the username as slug

/new-post/[username]

Lets do that

getServerSideProps used to get dynamic route

adsfasdf


Add new DB + collection for registered users.

Added this to get that registered users:

async function getUser(username) {
  const uri = `mongodb+srv://${secret.user}:${secret.pass}@cluster0.xf7jy.mongodb.net/?retryWrites=true&w=majority`;
  const client = await MongoClient.connect(uri);
  const db = client.db("users");
  const user = await db.collection("users").findOne({ username });
  client.close();
  return user;
}

created corresponding DB and collection:

Updated the page to get Name from the username from DB, and use it:


Submit the message to a user on DB, by username

Lets do this by using api routes of next

But this post has become too long! Continued in Part 2