https://github.com/lemoncode/react-from-classes-to-hooks-typescript
Sample simple applications migrations from class based components to hooks
https://github.com/lemoncode/react-from-classes-to-hooks-typescript
Last synced: 3 months ago
JSON representation
Sample simple applications migrations from class based components to hooks
- Host: GitHub
- URL: https://github.com/lemoncode/react-from-classes-to-hooks-typescript
- Owner: Lemoncode
- License: mit
- Created: 2019-02-12T11:51:23.000Z (almost 7 years ago)
- Default Branch: master
- Last Pushed: 2019-02-13T09:06:17.000Z (almost 7 years ago)
- Last Synced: 2025-06-17T22:11:45.780Z (7 months ago)
- Language: TypeScript
- Size: 38.1 KB
- Stars: 13
- Watchers: 3
- Forks: 3
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# react-from-classes-to-hooks-typescript
Simple applications examples: migrations from class based components to hooks.
## Examples
Under each example folder you will find two subfolders:
- 00_start: Starting point (using state + classes).
- 01_migrated: Application fully migrated to hooks.
## 00_login-page
This application is composed by a login page (class based + state) and a second page that shows the logged in user (making use of context + hoc to keep the login name as a field available globally).
Migration process:
- Migrate the login page from class component to function compon ent using hooks and access the context using the _useContext_ effect.
- Migrate page B (just displays the name of the user logged in), remove the usage of an HOC to inject the login context and use the effect _useContext_.
### LoginPage
For the login page:
- Use the _useContext_ effect to store in the context the user name (once the user has successfully logged in).
- We have created two custom hooks:
- One to store the login information that the user is entering.
- Another to store the form error information.
> About why creating two custom hooks splitting the state instead of having one (like we use to do in _class components_), you can read the entry
> _Should I use one or many state variable_ from the [reactjs hooks-faq](https://reactjs.org/docs/hooks-faq.html#should-i-use-one-or-many-state-variables)
**Original LoginPage class based component (extract)**
_./00_login/00_start/src/pages/login/loginPage.tsx_
```typescript
interface State {
loginInfo: LoginEntity;
showLoginFailedMsg: boolean;
loginFormErrors: LoginFormErrors;
}
interface Props extends RouteComponentProps, WithStyles {
updateLogin: (value) => void;
}
class LoginPageInner extends React.Component {
constructor(props) {
super(props);
this.state = {
loginInfo: createEmptyLogin(),
showLoginFailedMsg: false,
loginFormErrors: createDefaultLoginFormErrors()
};
}
onLogin = () => {
loginFormValidation
.validateForm(this.state.loginInfo)
.then(formValidatinResult => {
if (formValidatinResult.succeeded) {
if (isValidLogin(this.state.loginInfo)) {
this.props.updateLogin(this.state.loginInfo.login);
this.props.history.push("/pageB");
} else {
this.setState({ showLoginFailedMsg: true });
}
} else {
alert("error, review the fields");
}
});
};
onUpdateLoginField = (name: string, value) => {
this.setState({
loginInfo: {
...this.state.loginInfo,
[name]: value
}
});
loginFormValidation
.validateField(this.state.loginInfo, name, value)
.then(fieldValidationResult => {
this.setState({
loginFormErrors: {
...this.state.loginFormErrors,
[name]: fieldValidationResult
}
});
});
};
render() {
const { classes } = this.props;
return (
<>
this.setState({ showLoginFailedMsg: false })}
/>
>
);
}
}
export const LoginPage = withSessionContext(
withStyles(styles)(withRouter(LoginPageInner))
);
```
**Migrated LoginPage class based component (extract)**
_./00_login/01_migrated/src/pages/login/loginPage.tsx_
```typescript
function useLogin() {
const [loginInfo, setLoginInfo] = React.useState(createEmptyLogin());
return {
loginInfo,
setLoginInfo
};
}
function useErrorHandling() {
const [showLoginFailedMessage, setShowLoginFailedMessage] = React.useState(
false
);
const [loginFormErrors, setLoginFormErrors] = React.useState(
createDefaultLoginFormErrors()
);
return {
showLoginFailedMessage,
setShowLoginFailedMessage,
loginFormErrors,
setLoginFormErrors
};
}
interface Props extends RouteComponentProps, WithStyles {}
const LoginPageInner = (props: Props) => {
const { loginInfo, setLoginInfo } = useLogin();
const {
showLoginFailedMessage,
setShowLoginFailedMessage,
loginFormErrors,
setLoginFormErrors
} = useErrorHandling();
const loginContext = React.useContext(SessionContext);
const onLogin = () => {
loginFormValidation.validateForm(loginInfo).then(formValidationResult => {
if (formValidationResult.succeeded) {
if (isValidLogin(loginInfo)) {
loginContext.updateLogin(loginInfo.login);
props.history.push("/pageB");
} else {
setShowLoginFailedMessage(true);
}
} else {
alert("error, review the fields");
}
});
};
const onUpdateLoginField = (name: string, value) => {
setLoginInfo({
...loginInfo,
[name]: value
});
loginFormValidation
.validateField(loginInfo, name, value)
.then(fieldValidationResult => {
setLoginFormErrors({
...loginFormErrors,
[name]: fieldValidationResult
});
});
};
const { classes } = props;
return (
<>
setShowLoginFailedMessage(false)}
/>
>
);
};
export const LoginPage = withStyles(styles)(withRouter(LoginPageInner));
```
### Page B
Instead of using an HOC to inject the propery username from the _context_, we use the _useContext_ effect.
**Original code\***
_./00_login/00_start/src/pages/b/pageB.tsx_
```typescript
import * as React from "react";
import { Link } from "react-router-dom";
import { SessionContext, withSessionContext } from "../../common/";
interface Props {
login: string;
}
const PageBInner = (props: Props) => (
<>
Hello from page B
Login: {props.login}
Navigate to Login
>
);
export const PageB = withSessionContext(PageBInner);
```
**Migrated code**
_./00_login/01_migrated/src/pages/b/pageB.tsx_
```typescript
import * as React from "react";
import { Link } from "react-router-dom";
import { SessionContext } from "../../common/";
interface Props {}
export const PageB = (props: Props) => {
const loginContext = React.useContext(SessionContext);
return (
<>
Hello from page B
Login: {loginContext.login}
Navigate to Login
>
);
};
```
> you can find an additioanl sample called _02_migrated_reducer_ that uses the _userReducer_ effect to store
> the login form errors.
## 01_fetch
In this application we just fetch from the Github api the list of members
belonging to a given organization (lemoncode).
The original sample uses a class component that keeps in it's state
the list of the members (the fetch list is triggered in the _componentDidMount_ event from the class component).
Migration process:
- Refactor _MemberTable_ component to be a function component.
- Create a custom hook to hold the member list and expose the
_loadmemberlist_ function.
- Call _useEffect_ passing as a second argument an empty array (This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run).
**Original class based component**
_./01_fetch/00_start/component/memberTable.tsx_
```typescript
interface Props {}
// We define members as a state (the compoment holding this will be a container
// component)
interface State {
members: MemberEntity[];
}
export class MemberTableComponent extends React.Component {
constructor(props: Props) {
super(props);
this.state = { members: [] };
}
public componentDidMount() {
memberAPI.getAllMembers().then(members => this.setState({ members }));
}
public render() {
return (
{this.state.members.map((member: MemberEntity) => (
))}
);
}
}
```
**Migrated to hooks + function component**
_./01_fetch/01_migrated/component/memberTable.tsx_
```typescript
function useMembers() {
const [members, setMembers] = React.useState([]);
const loadMembers = () => {
memberAPI.getAllMembers().then(members => setMembers(members));
};
return { members, loadMembers };
}
export const MemberTableComponent = () => {
const { members, loadMembers } = useMembers();
React.useEffect(() => {
loadMembers();
}, []);
return (
{members.map((member: MemberEntity) => (
))}
);
};
```
> Passing an empty array as a second argument, tells React that your effect doesn't depende on any
> values from props or state, so it never needs to re-run (you get a similar behavior like in _componentDidMount_),
> more info: https://reactjs.org/docs/hooks-effect.html.
# About Basefactor + Lemoncode
We are an innovating team of Javascript experts, passionate about turning your ideas into robust products.
[Basefactor, consultancy by Lemoncode](http://www.basefactor.com) provides consultancy and coaching services.
[Lemoncode](http://lemoncode.net/services/en/#en-home) provides training services.
For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend