
Join the Conversation!
Subscribing gives you access to the comments so you can share your ideas, ask questions, and connect with others.
How do I remove the blur effect from my CSS?
I removed but the blur is still there. Any ideas?
filter: blur(5px);
Does work for removing blur from modals?
backdrop-filter: none;
Subscribing gives you access to the comments so you can share your ideas, ask questions, and connect with others.
So far all we've discussed just feels like adding restrictions to your code. So what is the real benefit and why do so many people love using Typescript? Because of the restrictions. Restrictions are good. When developing by yourself- You restrict yourself from using your variables or methods incorrectly. This means you won't have as many confusing bugs that are hard to track down. You'll be able to catch these bugs before you even run the code.
What you're really doing is describing your code and how it behaves to the typescript compiler and your code editor. By learning to describe your code accurately- you will not only write safer and less buggy code, but you'll become a stronger developer. You'll more clearly understand all code, not just your own.
The benefits really shine when working in a team. By adding all these types and 'restrictions' to the code you write- you can enforce how other people on your team use your code. If you write a function that explicitly takes a string as an argument and returns an array- no one on your team will accidentally pass in a number to the function. And they'll know the return type without even looking at your code just by hovering over your function they imported.
You'll get access to powerful autocomplete features not only in your own code, but when using code written by another teammate. You won't need to dig through their code to figure out what's happening- your code editor will just know. For example- after defining this Post type, when you create an object with that type then your editor will help you write the rest:
Or after it's already been written or imported- you will get assistance from your editor when keying into the object:
As you can see it's a powerful tool not just for you, but for collaboration.
Let's jump right in and create a new NextJS project. Don't just read- open up a code editor and follow along. Get the muscle memory working!
Run In your terminal. Follow the commands.
You should have new project all spun-up and ready to use Typescript in!
The only file that should be new to you is the tsconfig.json file in the root directory. Let's open it up and take a quick look at what NextJS generated for us.
We should have a config file with "compilerOptions", "include", and "exclude". What do all these options mean? Like I said before, typescript is just a superset of javascript- and it can't actually run in the browser or in node by itself. It actually gets "compiled" into javascript when we start our dev environment or when we build the application. These settings tell the typescript compiler exactly how to compile our Javascript. For instance- the target option tells the compiler what version and features of Javascript to build. Older versions will support more browsers, and newer versions will support more features like 'async/await' out of the box without the compiler having to create extra code to get it to work.
The docs have a comprehensive list of these options: but for the most part you won't need to change anything in a prebuilt config like this- Next (or whatever other package you use to scaffold your project like Vite, create-react-app), will include all the appropriate libraries and targets for you to get started.
We'll create a quick application that pulls data from 2 public APIs and generates fake user profiles using that data. It won't be fancy- but it will let us explore using typescript with APIs, React props and components, and event handlers.
Let's open up our 'homepage' at app/page.tsx and delete essentially everything to give us a blank slate to work from. We'll also modify the function to be 'async' so that we can fetch data inside.
export default async function Home() {
return <main></main>;
}
First we can fetch some data inside our Home() function from a free and public API. https://peoplegeneratorapi.live/api/
const userResponse = await fetch(
"https://peoplegeneratorapi.live/api/person/5"
);
const users = await userResponse.json();
This will give us an array of 5 people to work with, and we can iterate over that data and display it in a map. So our code looks like this:
export default async function Home() {
const userResponse = await fetch(
"https://peoplegeneratorapi.live/api/person/5"
);
const users = await userResponse.json();
return (
<main>
{users.map((user) => (
<div key={user.username}>{user.name}</div>
))}
</main>
);
}
This runs and works fine- but you'll notice a typescript error:
This is typescript telling you that it has no idea what type of data 'user' is. And because of that, it can't be sure if or are what you want or if they even exist. We can fix this by creating a 'User' type. This will make Typescript stop yelling at us, AND make it easier for us to key into data with autocomplete.
Let's go to the top of this file and define a 'User' interface (you'll usually use interface for objects). And inside that interface we need to tell Typescript what kind of keys a User will have, and what types those keys are. To figure this out we need to go to our API and look at a response. APIs will often include sample responses to help you figure this out, and sometimes it will be in the documentation for each endpoint. Worst case scenario you can just console log the response back and look at the keys/values.
But luckily this API well documented. If we go to
So we can just fill this out.
interface User {
id: 1;
name: string;
username: string;
email: string;
address: {
street: string;
suite: string;
city: string;
zipcode: string;
geo: {
lat: string;
lng: string;
};
};
phone: string;
website: string;
company: {
name: string;
catchPhrase: string;
bs: string;
};
}
We can break the address up if we wanted to. We can make an Address interface aswell and just assign the address key of the User to Address:
interface User {
... etc
address: Address;
}
interface Address {
street: string;
suite: string;
city: string;
zipcode: string;
geo: {
lat: string;
lng: string;
};
};
Great. Now we can explicitly tell Typescript that the response we get back will be an array of Users.
const userResponse = await fetch(
"https://jsonplaceholder.typicode.com/users"
);
const users: User[] = await userResponse.json();
Now our Typescript error is gone. AND as an added bonus- we have autocomplete!
Before we build out a user card- let's rework it into a component. Let's map around this component we're about to build instead:
{users.map((user) => (
<UserCard key={user.username} name={user.name} email={user.email} />
))}
We'll pass in props for name and email, and try rendering those.
Make a new folder called components and let's add a UserCard.tsx file.
export default function UserCard() {
return (
<article>
</article>
);
}
We want to take in the email and name props- but in typescript we can't just put them in the arguments- we need to specify what type each argument is. We can do this in a lot of ways. If there are only a couple props, we can just destructure the arguments and then give a type to the desctructured object like so:
export default function UserCard({
name,
email,
} : {
name: string;
email: string;
}) {
return (
<article>
{name} {email}
</article>
);
}
You'll often see people create a 'Props' interface instead to make it easier to modify and read:
interface Props {
name: string;
email: string;
}
export default function UserCard({ name, email }: Props) {
return (
<article>
{name} {email}
</article>
);
}
If we wanted to make the email or another field optional we could just add a ? to the name of the key.
interface Props {
name: string;
email?: string;
}
But what we really want is to get everything about the User. We could pass in our user from the map as a prop-
but then would we have to recreate the type in this file, too?
Not at all. Typescript interfaces and types can be imported and exported just like anything else. We can take our User and Address interfaces from app/page.tsx and put them in a different file to import as we need. Let's create a types folder in the root and an index.ts file inside of it:
// types/index.ts
export interface User {
... etc ...
}
export interface Address {
... etc ...
}
Notice we 'export' these types. Now we can import User at the top of our app/page.tsx
import type { User } from "@/types/";
export default async function Home() {
const userResponse = await fetch(
"https://peoplegeneratorapi.live/api/person/5"
);
const users: User[] = await userResponse.json();
return (
<main>
{users.map((user) => (
<UserCard key={user.username} user={user} />
))}
</main>
);
}
And import it into components/UserCard.tsx as well. Let's flesh out the component a bit and utilize some of the data we're getting. Try it yourself- it's incredibly easy because typescript will help us key into each value even if we don't remember the exact name.
export default function UserCard({ user }: { user: User }) {
return (
<article className="flex flex-col items-center justify-center w-[35rem] h-full p-4 space-y-4 bg-white rounded-lg shadow-md">
<section className="flex flex-row items-center justify-around w-full h-1/2 space-y-4">
<img
className="w-20 h-full rounded-full"
src={
"https://api.dicebear.com/6.x/adventurer-neutral/svg?seed=" +
user.username
}
/>
<section className="flex flex-col items-center justify-center w-1/3 h-full">
<h2 className="text-xl font-bold text-black">{user.name}</h2>
<p className="text-sm text-gray-500">{user.email}</p>
</section>
<section className="w-1/3">
<p className="text-sm text-gray-500">{user.address.phoneNumber}</p>
<p className="text-sm text-gray-500">{user.address.streetAddress}</p>
<p className="text-sm text-gray-500">{user.address.city}</p>
<p className="text-sm text-gray-500">{user.address.zip}</p>
</section>
</section>
</article>
);
}
Let's create another component and call it CardDisplay.tsx. Inside of it we'll do our mapping over the user cards and add some basic pagination.
"use client"
import { User } from "@/types/";
import UserCard from "@/components/UserCard";
export default function CardDisplay({ users }: { users: User[] }) {
return (
<article>
<section className="flex flex-col items-center gap-2">
{users.map((user) => (
<UserCard key={user.username} user={user} />
))}
</section>
{/* page selection section */}
<section>
<div className="flex justify-center gap-2 mt-4">
{[...Array(5)].map((_, i) => (
<button key={i} className="bg-gray-200 rounded-md px-2 py-1">
{i + 1}
</button>
))}
</div>
</section>
</article>
);
}
Then we can replace our app/page.tsx contents to render this cardDisplay component. This tiny restructure let's us do datafetching serverside within the page.tsx, and then use hooks and onClicks in our CardDisplay.tsx later:
import CardDisplay from "@/components/CardDisplay";
export default async function Home() {
const userResponse = await fetch(
"https://peoplegeneratorapi.live/api/person/5"
);
const users: User[] = await userResponse.json();
return (
<main className="text-black">
<CardDisplay users={users} />
</main>
);
}
This is what it looks like now: