- Using my favorite tools altogether
- Fast loadtime and advanced pagination
- Synced types with my backend schema (mutations,queries, formMutations ..)
- Using react-hooks for everything
- Fast and enyoable component styling with tailwincss
- React
- Nextjs
- Typescript
- tailwindcss
- Apollo/URQL
- GraphQL
- React Hook Forms
- graphql-codegen to sync types with my backend schema
Step 1
Add a new Entity and decorate them with type-graphql and typeorm to tell PostgreSQL how
the new table looks like.
Files need to be Uppercase like User.tsx
.
Step 2
Add Resolver to make actions on an Entity (e.g. actions, validation, authentification)
Resolvers are written in lowercase like user.tsx
, and need the same name as the entity they are assigned to.
💡 Some basic decorator and types whe use:
- Resolvers: query, mutation
- Types: InputType and ObjectType
🕮 InputTypes are used for inputs on forms etc. 🕮 ObjectTypes are used for describing the Response Object of a Response etc.
Step 3 Run Mutations with typeorm when one of your Entity needs a change (or revert a migration)
🕮 For breaking changes for example adding a required field to an existing entity, we need to wipe out the database table or make the field nullable (optional)
Step 1 Add graphql mutations, queries depending on the new Resolver.
Step 2
run npm run gen
to autogenerate types from the backend schema.
This generates types from the schema defined on our server (all entities, resolvers) On Top of that we can add our own types to the schema in the graphQL folder on the client (fragments, mutations, querys, urql-mutation-hooks) Urql mutation hooks are based on our schema types and so we dont need to write our own hooks to update our component state.
To get intellisense depending on your graphql schema Schema can be found on your graphql endpoint for example: http://localhost:4000/graphql
- install vscode extension locally
- name in vscode extension store: graphql.vscode-graphql
Video Tutorial 5h13 https://www.youtube.com/watch?v=I6ypD7qv3Z8&t=16577s
npx create-next-app --example with-tailwindcss little-reddit-web --use-npm npm install -D stylelint stylelint-config-standard npm install -D typescript @types/react @types/node npm install react-hook-form
npm install -D tailwindcss@^1.0 npm install @tailwindcss/ui npm install -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react prettier eslint-config-prettier eslint-plugin-prettier npm install tailwindcss-classnames (optional)
npm install urql graphql npm install -D @graphql-codegen/cli
npx graphql-codegen init 1: What type of application are you building? Application built with React 2: Where is your schema?: (path or url) http://localhost:4000/graphql 3: Where are your operations and fragments?: src/graphql/**/*.graphql 3: Pick plugins: TypeScript (required by other typescript plugins), TypeScript Operations (operations and fragments), TypeScript React Apollo (typed componen ts and HOCs) SKIP apollo if using urql ! 4: Where to write the output: src/generated/graphql.tsx 5: Do you want to generate an introspection file? No 6: How to name the config file? codegen.yml 7: What script in package.json should run the codegen? gen
Info: If we want urql instead of apollo we can edit yaml file(optional):
- For urql we need to edit codegen.yml and change
"typescript-react-apollo"
to"typescript-urql"
- also remove
"@graphql-codegen/typescript-react-apollo": "1.17.8"
from package.json and use urqlnpm install -D @graphql-codegen/typescript-urql
instead
Info: For syntax highlighting graphql in .graphql files use GraphQL for VSCode
vscode-extension
npm install @urql/exchange-graphcache npm install next-urql react-is isomorphic-unfetch
tsx
import React, { FC, ReactElement } from 'react';
import { useForm } from 'react-hook-form';
import { CombinedError, useMutation } from 'urql';
import * as styles from '../page-styles/styles';
import { toErrorMap } from '../utils/toErrorMap';
type FormValues = {
username: string;
password: string;
};
type ResponseObject = {
data?: {
register: {
errors?: [
{
field: string;
message: string;
},
];
user?: {
id: number;
username: string;
updatedAt: string;
createdAt: string;
};
};
};
error?: CombinedError;
};
const REGISTER_MUT = `
mutation Register($username: String !, $password: String! ) {
register (options: {username: $username, password: $password }){
errors{
field
message
}
user {
id
username
createdAt
updatedAt
}
}
}
`;
const Register: FC = (): ReactElement => {
const [, registerMut] = useMutation(REGISTER_MUT); // without code-generated customHook
const { register, handleSubmit, formState, setError, errors } = useForm<FormValues>();
const { isSubmitting } = formState;
const onSubmit = async (data: FormValues) => {
const response: ResponseObject = await registerMut(data);
// if no connection
if (!response) console.log('Promise unresolved, check connection');
if (response.error) console.log('Error occured in onSubmit:', response.error);
// set error message on form inputField
if (response.data?.register.errors) {
console.log(toErrorMap(response.data.register.errors));
const errorObj = toErrorMap(response.data.register.errors);
if ('username' in errorObj) setError('username', { message: errorObj.username });
if ('password' in errorObj) setError('password', { message: errorObj.password });
}
// successfull registered
if (response && !response.error && !response.data?.register.errors && response.data)
console.log('Successfull registered:', response.data);
};
return (
<div className={styles.container}>
<div className={styles.headerContainer}>
<img
className={styles.headerLogo}
src="https://tailwindui.com/img/logos/workflow-mark-on-white.svg"
alt="Workflow"
/>
<h2 className={styles.headerTitle}>Sign in to your account</h2>
</div>
<div className={styles.formContainer}>
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label htmlFor="username" className={styles.usernameLabel}>
Username
</label>
<div className={styles.usernameInputContainer}>
<input name="username" ref={register({ required: true })} className={styles.usernameInputField} />
{errors.username && <div className="text-red-500 font-bold text-sm">{errors.username.message}</div>}
</div>
</div>
<div className="mt-6">
<label htmlFor="password" className={styles.passwordLabel}>
Password
</label>
<div className={styles.passwordInputContainer}>
<input name="password" ref={register({ required: true })} className={styles.passwordInputField} />
{errors.password && <div className="text-red-500 font-bold text-sm">{errors.password.message}</div>}
</div>
</div>
<div className="mt-6">
{isSubmitting ? (
<button type="submit" disabled={isSubmitting} className={styles.submitButton(isSubmitting)}>
Sign in
</button>
) : (
<button type="submit" className={styles.submitButton(isSubmitting)}>
Sign in
</button>
)}
</div>
</form>
</div>
</div>
);
};
export default Register;
the tailwindcss-classnames package is the same as the original classnames pkg (node_modules/classnames/README.md) but with types onTop
The classNames
function takes any number of arguments which can be a string or object.
The argument 'foo'
is short for { foo: true }
. If the value associated with a given key is falsy, that key won't be included in the output.
classNames('foo', 'bar'); // => 'foo bar'
classNames('foo', { bar: true }); // => 'foo bar'
classNames({ 'foo-bar': true }); // => 'foo-bar'
classNames({ 'foo-bar': false }); // => ''
classNames({ foo: true }, { bar: true }); // => 'foo bar'
classNames({ foo: true, bar: true }); // => 'foo bar'
// lots of arguments of various types
classNames('foo', { bar: true, duck: false }, 'baz', { quux: true }); // => 'foo bar baz quux'
// other falsy values are just ignored
classNames(null, false, 'bar', undefined, 0, 1, { baz: null }, ''); // => 'bar 1'
Arrays will be recursively flattened as per the rules above:
var arr = ['b', { c: true, d: false }];
classNames('a', arr); // => 'a b c'
let buttonType = 'primary';
classNames({ [`btn-${buttonType}`]: true });
border-width: 1px 1px 1px 10px; border-style: solid; border-color: rgb(191, 22, 80) rgb(191, 22, 80) rgb(191, 22, 80) rgb(236, 89, 144); border-image: initial
This is an example of using Tailwind CSS in a Next.js project.
Deploy the example using Vercel:
Execute create-next-app
with npm or Yarn to bootstrap the example:
npx create-next-app --example with-tailwindcss with-tailwindcss-app
# or
yarn create next-app --example with-tailwindcss with-tailwindcss-app
Deploy it to the cloud with Vercel (Documentation).
This example is a basic starting point for using Tailwind CSS with Next.js. It includes the following PostCSS plugins
- postcss-preset-env - Adds stage 2+ features and autoprefixes
To control the generated stylesheet's filesize, this example uses Tailwind CSS' purge
option to remove unused CSS.