Advanced React
03 - An Intro to Next.js, Tooling, and Routing
Basics & Tooling
Next.js makes things simpler by handling all of the tooling for you such as React compiling, code splitting, etc under the hood for you. You can expose the Webpack and Babel config and provide your own.
Next.js also handles server-side rendering which is great for instant loading, SEO, and preloading. Provides a new React life cycle method called getInitialProps()
.
Routing
It also does routing for you. There really isn't much in the way of routing since you just create pages. Every single page that you want to visit must have a page.js
file (very much like old PHP apps).
Next.js will take care of importing React into stateless functional components.
Next.js pages resolve to the file name less .js
. So sell.js
would resolve to http://my.app/sell
. Everything that is resuable goes into components/
whereas the pages themselves go into the pages
directory. Majority of logic and heavy duty lifting should go in the components directory. Like the pages directory to controllers which should be thin by their nature.
Links
To create a link bound to HTML pushstate (important so that you don't lose next.js cached data on the frontend) import Link
from next/link
as shown here in a stateless functional component:
import Link from 'next/link';
const Home = props => (
<div>
<p>Hey!!!</p>
<Link href="/sell">
<a>Sell!</a>
</Link>
</div>
);
export default Home;
The above link will take you to the /sell
page.
04 - Custom _app.js Layout
Parent pages can handle state. Next.js by default wraps your entire application in an app component. You often want to create a custom wrapper for your application. It's good for persisting state between page changes, keeping state when navigating pages, custom error handling using `componentDidCatch, and inject additional data into pages (such as processing GraphQL queries).
The name of the custom app component is pages/_app.js
. A simple custom app component would look like the following:
import App, {Container} from 'next/app';
class MyApp extends App {
render() {
const { Component } = this.props;
return (
<Container>
<p>I'm on every page</p>
<Component />
</Container>
);
}
}
export default MyApp;
Use {this.props.children}
inside of a render method to render out the children passed to the component.
To wrap every page's content in another component, you can create a generic page component and use that to wrap the content:
render() {
const { Component } = this.props;
return (
<Container>
<Page>
<Component />
</Page>
</Container>
);
}
Using lots of Stateless Functional Components since a lot of the state and life cycle methods will be handled by higher-order components and most of these are just display components.
05 - An Intro to Styled Components
CSS in JS is using the CSS inside of the JS for a specific button etc. You will still have global styles. Styled Components is one of the packages that you can use to help out.
When using styled components, you basically replace whatever element that you want to style with a component that is already styled.
Tag template literals can be used to create the styled components. They are basically just strings that have been tagged with a specific identifier.
import styled from 'styled-components';
const MyButton = styled.button`
background: red;
font-size: 100px;
`;
The above basically just creates a button
element with the styles listed in the backticks. This allows you to reuse the styles for multiples of the same element. These styled elements are converted to React components. You can also target inner elments.
Don't go overboard creating individual styled components unless it makes sense to reuse the styled components. Otherwise, it's ok to use nested selectors.
06 - Themes and Layout with Styled Components
How to organize your styled components:
-
you can put the styled components in the same component in which you're going to use them. That works well when the only thing you're going to be using that styled component for is in that file.
-
Another format is to create a styles directory and put all of your styled components in that directory.
-
Make it contextual. i.e. have a Header folder with a
styles.js
andindex.js
.
React Context API: it allows you to specify values up high and any child can access those values without having to pass down values from component to component and its via context.
07 - Global Styling and Typography with Styled Components
One of the benefits of css in js is that it scopes things. The alternative argument is that CSS is cascading. It's good to use the cascading nature through global styles.
Import packages first and local components second. Styled components by default don't render on the server.
08 - Visualizing Route Changes
In production, next.js prebuilds pages. In development, it builds them on demand. You can also prefetch the data.
Next.js has route lifecycle events. Use next/router
and NProgress
for routing. NProgress is for showing routing transition updates.
09 - Fixing Styled Components Flicker on Server Render
Flash of unstyled content. With server side rendering, everything needs to be prepped and ready to go before it is sent to the browser. Otherwise, the css might arrive late. With Next JS, you need to use a custom document (and while using styled components) so that it renders on the server side. collectStyles
trawls your components to compile your css files before the render.
10 - An Intro To GraphQL
GraphQL is language agnostic. GraphQL is a single endpoint that you hit. RPC-esque. Self-documenting. GraphQL is a typed language. Requests are nested structures. As long as you have relations between your data you can pull back related structures as far as you want.
You don't have filters etc by default with GraphQL. Your server implements resolvers where you interact with MySQL etc.
11 - Getting Setup with Prisma
Prisma is an abstraction layer that sits on top of multiple databases to allow for easy interaction with GraphQL.
npm -i -g prisma
, prisma login
, prisma init
.
When defining the graphql schema, use an !
to define if the field is required: email: String!
. Or an array: email: [String!]!
. Directives in GraphQL can do pretty much anything you want. @unique
, @relation
, @default
.
A simple insert would look like:
mutation {
createUser(data: {
name: "Wes Bos"
email: "hey@cool.com"
}) {
name
email
}
}
Where query:
query {
users(where: {
name_contains: "wes"
}) {
id
name
}
}
12 - Getting our GraphQL Yoga Server Running
When you need to expose an "endpoint" or the ability to run a query in GraphQL, you either need to write a resolver or a mutator. Resolvers are for retrieving data and mutators are for changing data.
13 - Our first query and mutation
You must define types in GraphQL. Required types are marked by !
:
type Dog {
name: String!
}
type Query {
dogs: [Dog!]!
}
[Dog!]!
means that the array is required and all elements inside the array must not be null.
Whenever you create a query, you must create a resolver which answers the question of how to get the data to the end user.
Inside of your resolvers and mutators files, you must match the queries and mutations defined in the schema.graphql
file. For example, dogs
above would reference dogs(parent, args, context, info)
in the Query.js
module. The context
parameter refers to the context in createServer()
which includes access to the db and information about the request.
A query for dogs would look like:
query {
dogs {
name
}
}
That would require an array of dogs with only the name parameter added in:
{
"data": {
"dogs": [
{
"name": "Snickers"
},
{
"name": "Sunny"
}
]
}
}
Resolvers and mutators can retrieve or send their data from or to anywhere. It can be a API, a flat file, or any other data source.
If you have something defined in your resolvers that isn't in your schema then it will error out.
Mutations are commonly written like the following:
mutation {
createDog(name: "snickers") {
name
}
}
You can name queries and mutations:
mutation myReallyCompellingName {
createDog(name: "snickers") {
name
}
}
14 - Items Creation and Prisma Yoga Flow
The steps for adding a new piece of data to an API are:
- data model and deploy
- edit schema
- write resolver
When you have more parameters to a mutator than what is reasonable, you can migrate it to a single parameter called data
which would be a new data type.
type Mutation {
createItem(title: String, description: String, price: Int, image: String, largeImage: String): Item!
}
# to
type Mutation {
createItem(data: ItemCreateInput!): Item!
}
15 - Setting Up Apollo Client with React
With Apollo you don't need to use Redux since it does all of the data management stuff that Redux does. withData
higher order Apollo component exposes Apollo as a prop. To add Apollo, just wrap the application in an Apollo provider. Wrap your app with the withData
to add in Apollo.
getInitialProps
is a special NextJS lifecycle method that's added to React by NextJS.
Context needs to be defined as ctx
in order for the import to work.
16 - React Meets GraphQL
Recommended by the Apollo team to locate the query in the file that you are going to be using it in. If it will be used by multiple files, use it in one of the files and then export it to the rest.
It's a best practice to make queries variable names all caps and name queries after the variable names:
const ALL_ITEMS_QUERY = gql`
query ALL_ITEMS_QUERY {
}
`;
Wrap your component with a high-order component (Apollo has one) to expose a list of props. However, render props are becoming more popular. You don't get the render and loading states when using high-order components.
Render props look like the following (they must be passed a function as the children):
export default class Items extends Component {
render() {
return (
<div>
<p>Items!</p>
<Query query={ALL_ITEMS_QUERY}>
{({data, error, loading}) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return <p>Found {data.items.length} items</p>;
}}
</Query>
</div>
)
}
}
REMEMBER: keys in react MUST be unique.
In react, when passing a value to a prop, the first set of curly's refers to an object reference wheres a second would refer to an object literal:
<Link href={{
pathname: '/item',
query: { id: item.id }
}}>
<a>{item.title}</a>
</Link>
17 - Creating Items with CRUD
Use htmlFor
in React instead of for
on a label tag since for
is a reserved word in Javascript.
If you try to retrieve values directly from an input, it won't work since the values are then being managed in two different places and React won't allow that. The input thus become read only. You need to add an onChange handler.
On change handlers need to be instance properties so that they can access this
. ES6 classes do not bind regular methods to the instance. Event handlers receive an event
object.
Use this.setState({})
to set the state of a value. You can use computed property names like so: this.setState({ [name]: val })
.
Normally in html, textarea
cannot have a value prop nor can they be self-closing. But in React they can.
Use the Mutation component to use a mutation:
<Mutation query={CREATE_ITEM_MUTATION} variables={this.state}></Mutation>
Use aria-busy
to let users know if the fieldset is busy.