174: Set Up the Core Pages: index, posts, contact, [slug]
175: Get Started With Home Page: Output a hero section and a featured-posts section. Wrap in a fragment component a hero component and a FeaturedPosts Component. Create a components folder and home-page folder inside. Style files should be downloaded or bring your own. CSS variables are defined in globals.css stylesheet file.
176: Add Hero Component: Image, H1 and P. Import scoped stylesheet. Using Image from next/image to grab image in public/images/site folder. Import Hero Component into starting page.
177: Add Layout and Navigation: Add Layout.js and MainNavigation.js and Logo.js Components to the comp/layout folder. In Layout add a Fragment Comp and wrap around MainNavigation comps. In _app.js wrap Layout around Component {..pageProps} code. Import comps as necessary. In MainNav import Link from next/link and return a header that inside has the Logo comp and a nav element with a ul an li elements that uses next Link to point to /posts and /contact. To make logo clickable, to take you back to the starting page, wrap Logo comp with Link with href set to “/” but remember there is this special behavior of Link when the child passed to it is not plain text. When you pass your own component or other html content to a Link it will not render an anchor tag by default, only if you pass plain text. If you pass anything else other than plain text it will not render it for you so you have to bring your own “a tag.” Do not have to add an href to anchor tag since Link will pass href on Link to direct child. Add anchor tag element to tell Link it should render an anchor tag. This is not a bug or inconsistency but a feature that exists to make sure you can turn any element into a Link (a div, a button, etc.).
178. Add Styling & Logo: Download and add stylesheets for logo and mainnav comps. Import files and add classes. In logo comp set up simple text-based logo with div that has classname of classes.logo.
179. Start “Featured Posts” Part: Create FeaturedPosts comp in new file and add a reusable posts-grid.js component (saved in comp/posts folder) to render a ul where we map through posts and output a postItem component for each post.
180. Add Post Grid & Post Items: post-item.js component (saved in comp/posts folder) create dummy data as model. Image width/height 300x200px. In public folder create images folder with posts folder inside
181. Render Dummy Post Data: post-item.js component. Each Post has specific named folder in public/images/posts/folder?! deleted later I think. (Interesting compared to WP process)
182. Add “All Posts” Page: PostsGrid component.
183. Work on the “Post Detail” Page: post-content.js render PostHeader component. Intro to markdown.
184. Render Markdown as JSX: Translate plain markdown text into renderable JSX using package react-markdown. install and import ReactMarkdown from “react-markdown” and leveraging tags.
185. Add markdown files as data source: Can store in DB but for a blog like this we can store on our own file system so we don’t have to set up or pay for those services. IN root directory create a posts folder and a file that ends with .md file extension. Add metadata with gray matter concetp with three dashes above and below content in yaml format which is just key-value pairs. delete extra post folders from public/images/posts/folder.
186. Add Functions to read & fetch data: Add lib folder and posts-util.js inside. Install gray-matter package. getAllPosts, . Uses fs from ‘fs’ and path module from node.js. use readdirSync method. readFileSync method.
187. Use Markdown data for rendering posts: On homepage index.js use GSProps to get featured posts and return as props a posts key with a value of featuredPosts. Pass on revalidate key. In posts/index.js use GSProps and run getAllPosts function.
188. Render Dynamic Post Pages and Paths: In posts-util.js make getPostData more flexible to call with filename with extension or just the slug. In [slug].js add GSprops and leverage context object to extract params key and set a slug constant. Create constant named postData and set it to call getPostData(slug) function. Add a revalidate key with value set to 600. In addition to GSprops use GSPaths set fallback to false and programatically get all the post titles. In posts-util.js create function called getPostsFiles.
189. Note about Rendering Custom HTML Elements with React Markdown: In the next two lectures, we’ll fine-tune ReactMarkdown to overwrite some default HTML elements it renders. Unfortunately, since I recorded these lectures, ReactMarkdown received a major update and the shown syntax changed slightly (i.e. what will be shown in the next two lectures has to change). But thankfully, only minor adjustments are needed. There is a link in the note that shows the differences between all the code we’ll write in the next lectures and adjustments that will be needed for React Markdown
190. Render Images with the “nextimage” component (from markdown): Use next/image and all attributes have curly brackets. Rewatch.
191. Render Code Snippets From Markdown: Code snippet has three back ticks and override the styling in post-content.js add a code(code) and react-syntax-highlighter to make highlighting code very simple and we use built-in Prism and atomDark theme.
192. Prepare Contact Form: In components folder we create a contact-form.js and matching CSS file. In ContactForm return as jsx an H1 and form. Import and add component in contact page.
193. Add the Contact API Route: Pages/api create contact.js and add a handler function that allows POST requests and after simple validation stores in the database, (we could fire off an email using sendGrid as well right?).
194. Send Data From the client to the api route: Set up the talking to api from the front-end. In contact-form.js set up the function that is triggered when the form is submitted. In sendMessagehandler, preventDefault, and send http request using fetch with config object as second argument but quick timeout here for now. Connect sendMessageHandler to form via onSubmit{}. Bring in useState for enteredEmail, enteredName, and enteredMessage. Use two-way binding to form by adding to inputs onChange{} props that updates the state with event.target.value. Skipping validation besides basic “required” attribute on textarea element in jsx. Now go back to your fetch request and in config object set the body key for data you are sending and make sure it is converted to string using JSON.stringify.
195. Store Messages with MongoDB in a Database: install mongodb package. import MongoClient and after simple validation and creating object of data you want to store, use mongoClient with connect method and pass url but instead of callback configure with async/await. Put this connection in try catch block. Initialize client with let client above the try block to have the right scope. Now after try/catch block you have connection and can continue, set constant named client to client.db() and below it use db.collection(messages).insertOne(newMessage). Wrap in a try catch block. Close client in success case and in catch block. As it comes to fetching data the visitors of our page should not see instead just having them in db is nice and we either build some kind of admin interface for ourselves the owner to fetch the data there or we just dive into the db to read messages users send us here.
196. Add UI Feedback with Notifications: Create ui folder in your components folder that has a notification.js file and a stylesheet. Using locally instead of using context api for app-wide state. Add a new piece of state to Contact form in contact-form.js. convert sendMessageHandler to async function and add await to fetch and store as constant named response. set some if response is not okay (!response.ok) throw new Error(data.message || ‘something went wrong’). To not have all http logic in here and to be able to conveniently try/catch the entire http block in one go, grab that code where we fetch, parse and handle not ok case and cut and move into separate function in the same file async sendContactData function where you expect to recieve contactDetails as argument and that is the object that is added as value to body key in config object, converted to JSON of course using JSON.stringify(contactDetails). Now in sednMessageHandler we just await sendContactData and pass our object where we set email to entered email and so on for name and message. now before we send contact data setRequestData to pending and when we are done set it to success and if it fails set it to error and thus try and catch any errors. Now we can use these status updates to show notification. Add notification variable using let notification. have “if” cases that set object depending on different status’. Create in requestError state. Add conditional content using ternary operator. Leverage useEffect to watch for when requestStatus changes and set timer using setTimeout to reset to null. There is a lot in this chapter, and so a lot to type out so may be good idea to rewatch. Need a cleanup function to clearTimeout.
197. Add “head” data: Let’s add general metadata to make site responsive-ready and add favicon. Import head from next/head in _app.js and add a head component and set meta name to viewport and content to width=device-width, initial-scale=1. Also add page-specific metadata title and description to homepage via index.js. Lets also work on single post pages which may be the most important. Go into [slug].js and import and render head. Use dynamic values.
198. Add a “_document.js” file: Defines general structure or add extra elements which we will do. Add a class based component, (whoa), and set lang=’en’ attribute.
199. Use React Portals: Utilize _document.js file to render notification through a portal. Instead of dumping randomly into complex html structure somewhere nested html tree this can be bad semantically for accessibility. React portals allows you to render a component anywhere in your component tree but technically inject it in a different place in the actual DOM tree which is better for semantics and accessibility. To implement you need to use “_document.js” file because that allows us to use and extra hook which this portal will need. So in _document.js add a div with an id of notifications. Go to notification component and convert it to a portal by importing ReactDOM from “react-dom” and return ReactDOM.createPortal() where first argument is your jsx code and a second argument is a selector where you select the DOM element where it should be ported to and we use native browser api to select this, for example document,.getElementById(‘notifications’). Pretty nice react feature that is easy to use with Next.js using the _document.js file.