User.js
// using functional component
const User = (props) => {
return <li className={classes.user}> {props.name} </li>;
}
export default User;
// using class-based component
import { Component } from 'react'
class User extends Component {
render() {
return <li className={classes.user}> {this.props.name} </li>;
}
}
export default User;
Users.js
const DUMMY_USERS = [
{ id: 'u1', name : 'Max' },
{ id: 'u2', name : 'Manuel' },
{ id: 'u3', name : 'Julie' },
]
class Users extends Component {
// defining state ... use constructor()
constructor() {
// need to use super()
// because it is extending a component
super();
// the property name should be "state"
// have to group all the states into one object
this.state = {
showUsers : true,
more : 'Test',
};
}
// instead of implementing as function
// use method(메서드)
toggleUsersHandler() {
// this.state.showUsers = false // NOT!
// use this.setState
// it always takes an object
// react will merge it to the original state
// it won't override the original state, different to functional state
this.setState((curState) => {
return {showUsers : !curState.showUsers}
});
}
render() {
const usersList = (
<ul>
{DUMMY_USERS.map((user) => (
<User key = {user.id} name ] {user.name} />
))}
</ul>
)
// to use method and state
// use this.state, this.props, this.method ...
// .bind ... the this keyword inside the method(toggleUsersHanlder)
// is now set to have the same context or the same value
// when the below code is evaluated
return(
<div className = {classes.users}>
<button onClick = {this.toggleUsersHanlder.bind(this)}>
{this.state.showUsers ? 'Hide' : 'Show'} Users
</button>
{this.state.showUsers && usersList}
</div>
)
}
}
UserFinder.js
const DUMMY_USERS = [
{ id: 'u1', name : 'Max' },
{ id: 'u2', name : 'Manuel' },
{ id: 'u3', name : 'Julie' },
]
class UserFinder extends Component {
constructor() {
super();
this.state = {
filteredUsers : [],
searchTerm : ''
};
}
// same as useEffect(() => {}, [])
⭐ componentDidMount() {
// Send http request...
this.setState({filteredUsers : DUMMY_USERS})
}
// same as useEffect(()=>{setfilteredUsers()}, [searchTerm])
⭐ componentDidUpdate(prevProps, prevState) {
if (prevState.searchTerm !== this.state.searchTerm) {
this.setState({
filteredUsers : DUMMY_USERS.filter((user) =>
user.name.includes(this.state.searchTerm)
),
});
}
this.setState({
filteredUsers : DUMMY_USERS.filter((user) =>
user.name.includes(searchTerm)
),
});
}
searchChangeHandler(event) {
this.setState({searchTerm : event.target.value})
}
render() {
return (
<Fragment>
<div className = {classes.finder}>
<input type = 'search'
onChange = {this.searchChangeHandler.bind(this)}/>
</div>
<Users users = {this.state.filteredUsers} />
</Fragment>
)
}
}
User.js
class User extends Component {
// it executes when the component is unmounted
⭐ componentWillUnmount() {
console.log('User will unmount')
}
render() {
return <li className={classes.user}> {this.props.name} </li>;
}
}
the way to provide a context is same
const UserContext = React.createContext({
users : []
});
const DUMMY_USERS = [
{ id: 'u1', name : 'Max' },
{ id: 'u2', name : 'Manuel' },
{ id: 'u3', name : 'Julie' },
]
function App () {
const userContext = {
users : DUMMY_USERS
}
return(
<UsersContext.Provider value = {usersContext}>
<UserFinder />
</UsersContext.Provider>
)
}
the way to use a context changes
// First way : use .Consumer
class UserFinder extends Component {
⭐ static contextType = UsersContext
constructor() {
super();
this.state = {
filteredUsers : [],
searchTerm : ''
};
}
// same as useEffect(() => {}, [])
componentDidMount() {
this.setState({filteredUsers : ⭐ this.context.users})
}
// same as useEffect(()=>{setfilteredUsers()}, [searchTerm])
componentDidUpdate(prevProps, prevState) {
if (prevState.searchTerm !== this.state.searchTerm) {
this.setState({
filteredUsers : ⭐ this.context.users.filter((user) =>
user.name.includes(this.state.searchTerm)
),
});
}
this.setState({
filteredUsers : DUMMY_USERS.filter((user) =>
user.name.includes(searchTerm)
),
});
}
searchChangeHandler(event) {
this.setState({searchTerm : event.target.value})
}
render() {
return (
<Fragment>
<UserContext.Consumer>
</UserContext.Consumer>
<div className = {classes.finder}>
<input type = 'search'
onChange = {this.searchChangeHandler.bind(this)}/>
</div>
<Users users = {this.state.filteredUsers} />
</Fragment>
)
}
}
sometimes there are errors which you can't prevent
(Ex) Http request being sent
if the server is temporarily not responding, the request can't complete
and you will likely end up with an error being generated
if the error is triggered in the component and can't handle it in that component(when it should be handled in a parent component), we can't use try-catch syntax. We have to use error boundaries.
by using Error boundaries, you can ensure that not your entire application crashes if something goes wrong, but that instead you can catch those errors and then handle them in an elegant way
❗❗ Error boundaries only works with class-based components, not with functional components
UserFinder.js
class UserFinder extends Component {
componentDidUpdate() {
// triggers Error (in the child component)
if (this.props.users.length === 0) {
throw new Error('No users provided!');
}
}
render() {
return (
<Fragment>
...
</Fragment>
)
}
}
ErrorBoundary.js
class ErrorBoundary extends Component {
// using constructor is optional
// just a example that it can be coded
// like a regular class-based-component
constructor() {
super()
this.state = { hasError : false };
}
⭐ componentDidCatch(error) {
console.log(error);
this.setState({ hasError : true });
}
render() {
if (this.state.hasError) {
// can render a error message when error occurs
return <p> Something went wrong! </p>
}
return ⭐ this.props.children;
}
}
export default ErrorBoundary;
UserFinder.js
class UserFinder extends Component {
// some codes...
render() {
return (
<Fragment>
<div className = {classes.finder}>
<input type = 'search'
onChange = {this.searchChangeHandler.bind(this)}/>
</div>
⭐ <ErrorBoundary>
<Users users = {this.state.filteredUsers} />
</ErrorBoundary>
</Fragment>
)
}
}