*** just a little bit
- Interactive + live coding exercises
- React
- TypeScript (just a little bit)
- TSX
- States, Effects, Props
- Component composition
- Containers vs. Presentational components
- Hooks instead of classic state 🏴☠️
- TSX 💯
- StackBlitz 🚀
- Contains some inline styling 🙀
☣️ ➡️ ⏱
https://code-star.github.io/typescript-react-101
Anyone?
React is a declarative, efficient, and flexible JavaScript library for building user interfaces. It lets you compose complex UIs from small and isolated pieces of code called “components”.
- Alternative for Angular / Vue / etc etc
- Basic and compact
- Scales for bigger applications as well
- Works nicely with State Management solutions (Redux, MobX, GraphQL)
const App = () => (
<div>My App</div>
)
<App />
- XML-like syntax
- Like a regular function
- Returns JSX / TSX
- Syntax extention to JavaScript
- Like a template language, but for client side
- Produces React
Nodes
Use regular JavaScript between {
... }
:
const myVariable = "Some value"
const App = () => (
<div>
My variable uppercase: {myVariable.toUpperCase()}
</div>
)
const App = () => (
<div
style={{ textTransform: "uppercase" }}
className="myClassName">
...
</div>
)
const buttonDisabled = true
const App = () => (
<input
type="button"
value={"Click me!"} // <- both notations can be used
disabled={buttonDisabled}
/>
)
Using normal CSS
import classes from "./styles.css"
const App = () => (
<div className={classes.myClassName}>...</div>
)
Using CSS-in-JS
const App = () => (
<div
style={{
fontSize: "18px",
backgroundColor: "green",
}}
>
...
</div>
)
(not recommended, but easy to start with)
Start your project here! ️
️➡️ https://stackblitz.com/fork/react-ts
OR
...
Cheatsheets: Javascript, TypeScript
In App.tsx
:
Create static HTML / JSX elements for:
- Time (mm:ss)
- Start / stop button
<div style={{ backgroundColor: 'blue' }}></div>
<button>My button</button>
<div>
<div style={{ fontWeight: "bold" }}>00:00</div>
<div>
<button style={{ backgroundColor: "hotpink" }}>
Start
</button>
</div>
</div>
const isEnabled = true
const App = () => (
<div>
{isEnabled ? "Enabled" : "Disabled"}
</div>
)
const animals = ["cat", "dog"]
const App = () => (
<div>
{animals.map((animal, index) => (
<div key={index}>{animal}</div>
))}
</div>
)
- Prevents injection attacks
- Composable using
children
- You can use TypeScript instead of JavaScript
const animals: string[] = ["cat", "dog"]
const App = () => (
<div>
{animals.map((animal: string, index: number) => (
<div key={index}>{animal}</div>
))}
</div>
)
- Typesafety inside your views
- Definitions of your
props
andstate
- No need for
PropTypes
- Store information inside your component
- Useful for forms, loading state and much more
import { useState } from "react"
const App: FC = () => {
const [username, setUsername] = useState("")
return (
<div>
<input
type={"text"}
value={username}
onChange={(event) => setUsername(event.target.value)}
/>
</div>
)
}
import { useState } from "react"
const App = () => {
const [isLoading, setIsLoading] = useState(true)
setTimeout(() => setIsLoading(false), 5000)
if (isLoading) {
return <div>Loading...</div>
}
return <div>Loading complete!</div>
}
import { useState } from "react"
const App = () => {
const [user, setUser] = useState<User | false>(false)
fetchUser().then((user: User) => setUser(user))
if (!user) {
return <div>Loading user...</div>
}
return <div>Username: {user.name}</div>
}
Create getters and setters for:
- The elapsed time:
number
- The start time:
number
- The stopwatch running state:
boolean
const [myState, setMyState] = useState( ... )
const [elapsedTime, setElapsedTime] = useState(0)
const [startMoment, setStartMoment] = useState(0)
const [timerRunning, setTimerRunning] = useState(false)
const App = () => {
const [buttonDisabled, setButtonDisabled] = useState(false)
return <button disabled={buttonDisabled}>Click me!</button>
}
const App = () => {
const [buttonDisabled, setButtonDisabled] = useState(false)
return (
<button
onClick={() => setButtonDisabled(true)}
disabled={buttonDisabled}>
Click me!
</button>
)
}
Use the created getters and setters for:
- When the start/stop button is clicked
- The text of the start/stop button should depend on the state
<button onClick={() => setIsRunning(!isRunning)}>
{isRunning ? "Stop" : "Start"}
</button>
- Perform side effects
- Use the component lifecycle
import { useEffect } from 'react'
...
useEffect(() => {
alert('The component is mounted!')
}, [])
...
(not recommended)
...
useEffect(() => {
alert('The props and/or state has changed!')
})
...
...
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
alert('The isLoading state has changed!')
}, [isLoading])
...
let timer = 0
const App = () => {
useEffect(() => {
timer = setInterval(() => console.log("tick"), 1000)
}, [])
return <div>...</div>
}
When the stopwatch button is clicked:
- Start: store the current moment as
startMoment
- Stop: display the elapsed time
Hint: use Date.now()
inside useEffect
Update the states every time timerRunning
updates
const App = () => {
const [timerRunning, setTimerRunning] = useState(false)
const [startMoment, setStartMoment] = useState(0)
const [elapsedTime, setElapsedTime] = useState(0)
useEffect(() => {
if (timerRunning) {
setStartMoment(Date.now())
} else {
setElapsedTime(Date.now() - startMoment)
}
}, [timerRunning])
...
}
Update the states every time timerRunning
updates
const App = () => {
//initial state undefined
const [timerRunning, setTimerRunning] = useState()
const [startMoment, setStartMoment] = useState(0)
const [elapsedTime, setElapsedTime] = useState(0)
useEffect(() => {
// guard clause
if (typeof timerRunning === 'undefined') {
return;
}
if (timerRunning) {
setStartMoment(Date.now())
} else {
setElapsedTime(Date.now() - startMoment)
}
}, [timerRunning])
...
}
- Convert the milliseconds in state to minutes and seconds and display it on the screen
Hints:
Math.floor((ms / 1000) / 60)
Math.floor((ms / 1000) % 60)
Pass information to your components:
<App title="App title" />
Or using a variable:
const appTitle = 'App title'
<App title={appTitle} />
Access Props inside the receiving component:
<App title="Hi" />
const App = (props) => (
<div>{props.title}</div>
)
Define the type of your props:
type AppProps = {
title: string
isloading: boolean
}
And use inside your component:
const App: FC<AppProps> = (props) => (
<div>
<div>{props.title}</div>
<div>{props.username}</div> /* Error! */
</div>
)
FC === FunctionComponent
More about this later!
type AppProps = {
onLogin: () => void
}
const App: FC<AppProps> = (props) => (
<div>
<button onClick={props.onLogin}>Login!</button>
</div>
)
const onLogin = () => {
// some magic happens
}
<App onLogin={onLogin} />
type AppProps = {
title: string
}
<App />
What will happen here?
Type '{}' is not assignable to type 'Readonly<AppProps>'.
Property 'title' is missing in type '{}'.
type Props = {
// before React v18 this was included in the `FC` type
// now we need to be explicit about it
children?: React.ReactNode
}
const Page: FC<Props> = (props) => (
<div>{props.children}</div>
)
<Page>
<div>I'm the child!</div>
</Page>
<App>
<Header>
<Navigation />
</Header>
<Content>
<SearchForm />
</Content>
</App>
- Create new file
Button.tsx
- Create a Button component and put the button code into it
- Import the button component in the main file
- Make it reusable
// Button.tsx
type ButtonProps = {
label: string
...
}
export const Button = props => (
<button>{props.label}</button>
)
import { Button } from "./button"
- Create a new button to start a new lap
- When the New lap button is clicked:
- Use
lastLapMoment
to populatelaps
- Use
Hint: copy the existing array and add a value to it:
const [lastLapMoment, setLastLapMoment] = useState(0)
const [laps, setLaps] = useState<number[]>([])
setLaps([...laps, Date.now() - lastLapMoment])
Hint: use setLastLapMoment
on start as well
- Show all the laps on the screen
Hint: use .map()
here.
{
laps.map((lap, index) => <div>...</div>)
}
When the stopwatch is running:
- Update the elapsed time every second
Hint: use setInterval
inside useEffect
here
Update the elapsed time every second
let timer = 0 // <-- IMPORTANT!
const App = () => {
const [timerRunning, setTimerRunning] = useState(false)
// define more state here...
useEffect(() => {
if (timerRunning) {
timer = setInterval(() => {
// use your setter here...
}, 1000)
}
}, [timerRunning])
...
}
When the stopwatch stop button is clicked:
- Stop the timer
- Make sure that the button text reflects the state
Hint: use clearInterval
inside the same useEffect
here
When the stopwatch stop button is clicked:
let timer = 0
const App = () => {
...
useEffect(() => {
if (timerRunning) {
...
} else {
clearInterval(timer)
}
}, [timerRunning])
...
}
Or: smart vs. dumb
Or fat vs. skinny
Or statefull vs. pure
Or screens vs. components
https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0
- Fetches data
- Keeps track of state
- Possibly connected to a State Store (Redux / MobX)
- Passes all the data to its children
- Does NOT contain styles
- Renders UI
- Contains styling
- Easily testable
- As dumb as possible
Props
const App: FC<Props> = (props) => <div>{props.title}</div>
class App extends React.Component<Props> {
public render() {
return <div>{this.props.title}</div>
}
}
On mount & State
const App: FC<Props> = (props) => {
const [user, setUser] = useState(false)
useEffect(() => {
fetchUser().then((user) => setUser(user))
}, [])
}
On mount & State
class App extends React.Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = {
user: false
}
}
public async componentDidMount() {
const user = await fetchUser()
this.setState({
user
})
}
}
- Recommended approach
- Class Components will be removed from the core
- React + TypeScript
- TSX
- States, Effects, Props
- Component composition
- Containers vs. Presentationals
- create-react-app
- https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0
- https://reactjs.org/docs/hooks-custom.html
Questions anyone?
Refactor your code to use a Container Component which handles all the state and passes it through presentational components
Try to refactor a FunctionComponent into a ClassComponent.