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:
components\ui\Card.js
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 aPromise
that resolves to theResponse
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 aninit
options object as the second argument (seeRequest
).
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.
Introduce Navigation and Links
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
- https://stackoverflow.com/questions/1651197/how-do-you-force-a-web-browser-to-use-post-when-getting-a-url
- 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
//
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
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