Day 2

Key Learnings:

  • pages folder name is reserved in nextjs.
    • Others like components is not reserved.
  • How props are passed to a component.
  • Utilize _app.js for anything that affects all pages like Layout - wrap with Layout component, Navigation (with links) etc
  • Using Programmatic (Imperative) Navigation
    • The usual rule of a Reach Hook:
      • Only use directly only at the root level of component function. (e.g. using useRouter)
    • useRouter has not just query that gives slug, it also has push() whose job is equivalent to Link component

Starting point of project with some templates


Create a home page with list of cards

Props are passed to a component.

Component NoteList then uses the notes prop)

import NoteItem from "./NoteItem";
import classes from "./NoteList.module.css";

function NoteList(props) {
  return (
    <ul className={classes.list}>
      {props.notes.map((note) => (
        <NoteItem
          key={note.id}
          id={note.id}
          image={note.image}
          title={note.title}
          address={note.address}
        />
      ))}
    </ul>
  );
}

export default NoteList;

Passing the props to component:

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

let note = [
  {
    id: 1,
    image:
      "https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__480.jpg",
    title: "sunset",
    address: "Beach Hawaii",
  },
];

function HomePage() {
  return <NoteList notes={note} />;
}

export default HomePage;

Adding a New Note page - where user can add a new note.

Notice the path - its under pages

But need a form component now!


Create a form component

Use this sample implementation:

  • Uses useRef (standard React)
  • Relies on a Prop onAddNote to do something, when submit is pressed.
  • The prop as usual comes from the ? _____
    • the parent page
      • Page(nextjs) - Component (form)
      • Page that contains component - is a style I see in Next or Maxmilian.
import { useRef } from "react";

import Card from "../ui/Card";
import classes from "./NewNoteForm.module.css";

function NewNoteForm(props) {
  const titleInputRef = useRef();
  const imageInputRef = useRef();
  const addressInputRef = useRef();
  const descriptionInputRef = useRef();

  function submitHandler(event) {
    event.preventDefault();

    const enteredTitle = titleInputRef.current.value;
    const enteredImage = imageInputRef.current.value;
    const enteredAddress = addressInputRef.current.value;
    const enteredDescription = descriptionInputRef.current.value;

    const noteData = {
      title: enteredTitle,
      image: enteredImage,
      address: enteredAddress,
      description: enteredDescription,
    };

    props.onAddNote(noteData);
  }

  return (
    <Card>
      <form className={classes.form} onSubmit={submitHandler}>
        <div className={classes.control}>
          <label htmlFor="title">Note Title</label>
          <input type="text" required id="title" ref={titleInputRef} />
        </div>
        <div className={classes.control}>
          <label htmlFor="image">Note Image</label>
          <input type="url" required id="image" ref={imageInputRef} />
        </div>
        <div className={classes.control}>
          <label htmlFor="address">Address</label>
          <input type="text" required id="address" ref={addressInputRef} />
        </div>
        <div className={classes.control}>
          <label htmlFor="description">Description</label>
          <textarea
            id="description"
            required
            rows="5"
            ref={descriptionInputRef}
          ></textarea>
        </div>
        <div className={classes.actions}>
          <button>Add Note</button>
        </div>
      </form>
    </Card>
  );
}

export default NewNoteForm;

Import the form (component) in page:

Form (component): Just created above

Page pages\new-note\index.js

Import and Use:

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

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

Something is missing: The prop we need to pass!

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

function NewNotePage() {
  function onAddNoteHandler(noteData) {
    console.log(noteData);
  }

  return <NewNoteForm onAddNote={onAddNoteHandler} />;
}
export default NewNotePage;

Learnt from this error - Error: EISDIR: illegal operation on a directory, read because had creaed the folder new-note by mistake named as new-note.js pages\new-note.js\index.js. The error means node is trying to treat a folder (by mistake) as a file.


Add Layout and Navigation

  • Layout - to wrap out components Gives a general layout to our components.

  • Navigation Bar - for links

Use this ready template for Layout and Navigation as reference.

Adding Layout

Original pages\_app.js:

import '../styles/globals.css'

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp

Adding Layout:

import "../styles/globals.css";
import Layout from "../components/layout/Layout";

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

export default MyApp;

Add Navigation bar

Its part of Layout! so by adding to _app.js, navigation also gets added to all pages!

Fix this small pending stuff in MainNavigation.js

  • import Link
  • user href
  • Remove margin on top bar

Final!

Home Page

New Note Page


Programmatic Navigation (Imperative)

We want the buttons to work (Show Details button on home list) On click - Load individual Note item So we would need link to single individual note URL, and can use Link.

We could use the Link Component, or <button> component. But for learning, lets try programmatically changing pages (navigating)

  • using useRouter : General rule of React: use a react hook directly only at the root level of component function.

Go to index.js > look for components…. > NoteItem has the button Show Details

  1. Add onClick to <button>

  2. Write the function - where!?? - got stuck here

    a. In parent - and pass in propr and use here from prop?

    b. or in same NoteItem

Had to refer - example chooses to got with b.

Use next hook useRouter

Ab kya kare!

Tried this wrong:

import Card from "../ui/Card";
import classes from "./NoteItem.module.css";
import { useRouter } from "next/router";

function NoteItem(props) {
  const router = useRouter();
  function handleShowDetailsClick(event) {
    console.log(event);
    let noteId = event.target.id;    
    router.push(`/${noteId}`);
  }
  ...

}

Solution: Actually nothing to do ith event.target.id to get the URL.

  1. We want URL of page.
  2. We need nothing from button info. We can know the individual card’s (items’s) detail like key and since it is used on the URL as slug, we can use it - create URL - push on Router.

Using useRouter to get redirect using push: Using useRouter to get redirect using push

The key was passed originally as prop: The key was passed originally:

…and futher as prop:

We can see that on click, it redirects to the URL! 😁 Although there is no such page yet, so we get an error.

getserversideprops-dynamic-route.gif


Add individual Note component

At /pages/[niteId]/index.js

import { Fragment } from "react";

function Note() {
  return (
    <Fragment>
      <img
        src="https://www.nasa.gov/sites/default/files/thumbnails/image/pia23378-16.jpg"
        alt="selfie from mars"
      ></img>
      <h1>First Note</h1>
      <p>This is Curiosity's selfie from Mars</p>
    </Fragment>
  );
}

export default Note;

The styling looks off. So create a new STYLING

Naming the css files in a particular way - e.g. say component is Comp, then its css file should be named Comp.module.css. Doing this restricts the scope of the CSS class styles defined to Comp only. Its a React thing, that Next also supports. This is called CSS Modules

Before going into styling let’s try to make everything on this page flexible: Create a pure component - that does everything from its props

By doing this we are also trying to keep pages folder lean

Create function component in components\notes\NoteDetail.js

import classes from "./NoteDetail.module.css";
function NoteDetail(props) {
  return (
    <segment className={classes.detail}>
      <img src={props.image} alt={props.title}></img>
      <h1>{props.title}</h1>
      <p>{props.details}</p>
    </segment>
  );
}

export default NoteDetail;

Also create related CSS:

at components\notes\NoteDetail.module.css

.detail img {
  /* Makes the large image fit the container */
  width: 100%;
}

.detail {
  text-align: center;
}

Now pass dummy data as props:

at pages\[noteId]\index.js

import NoteDetail from "../../components/notes/NoteDetail";
function NoteDetails() {
  return (
    <NoteDetail
      title="Selfie from Mars"
      details="This is Curiosity's selfie from Mars"
      image="https://www.nasa.gov/sites/default/files/thumbnails/image/pia23378-16.jpg"
    ></NoteDetail>
  );
}

export default NoteDetails;