Doing the React Task Tracker tutorial in TypeScript and adding Redux.
Install nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
Install nodejs v16+ with nvm:
nvm install 16.13.2
Enable corepack and install yarn:
corepack enable
corepack prepare yarn@1.22.17 --activate
Execute yarn create vite and select react, react_ts template.
You can debug vite projects simply from Chrome, vite makes the original TypeScript code available via source maps. Also install React Developer Tools in Chrome!
It's worth later to investigate how debug can be done directly from IntelliJ.
- if you run the vite project by creating an npm configuration (see package.json for the proper script!) you can debug the vite runtime only!
Plugins for IntelliJ:
- Vite Integration plugin for JetBrains IDEs are only support scaffolding projects at the moment
When using corepack in Node v16+, follow these steps:
In Preferences / Languages & Features / Node.js:
- set Node interpreter to the installed Node under nvm directory
- create a symlink for yarn or wait for the Jetbrains fix
- TypeScript grants compile time type checking while PropTypes is for runtime type checking in React
- To use destructured properties ({tile, color} instead of one props parameter/attribute) in TypeScript:
- inline declaration:
const Header = ({title, color}: {title: string, color: string}) => {... - define interface:
const Header = ({title, color}: HeaderProps) => {...
- inline declaration:
- use ? operator to define optional properties
- use proper event handler types (see later in Event Handling!)
Example:
interface Props {
color?: string;
text?: string;
onClick?: MouseEventHandler<HTMLButtonElement>;
}
const Button = ({ color, text, onClick }: Props) => {
return (
<button
style={{ backgroundColor: color }}
className='btn'
onClick={onClick}
>
{text ?? 'Button'}
</button>
)
}- declare models and DTOs as interfaces (we don't need them transpiled as js classes)
- interface names can easily interfere with component names (Task component with Task model interface)
- idea #1: add purpose of the interface instead of prefixing everything with 'I' (ITask is bad, TaskModel is good)
- idea #2: use Impl postfix for implementations => this won't work for components!
- no cross-import is supported in TS, best practice: keep them in models.tsx or models/ directory
- interface names can easily interfere with component names (Task component with Task model interface)
- declare state variables in App.tsx (declare global state objects) -> redux will do this differently
const tasks: Tasks[] = useState<Task[]>({defaultState...})(useState with generics!)
- if you want to access element-specific data in event handler code
- declare events in component props with proper type (e.g.
MouseEventHandler<SVGElement>)
- declare events in component props with proper type (e.g.
- otherwise, you can use EventHandler by providing a parent type in generic as a type:
EventHandler<any>- note:
anywould irritate the linter, however React use any in the definition of EventHandler as well
- note:
- axios library supports exception handling out of the box, while fetch() only rejects the promise
- add ...Async postfix to functions which are declared with the async keyword
- call them with .then() from sync methods
- useEffect might end in infinite loop
- due to that useEffect is scheduled after every DOM changes (and DOM changes are triggered by observables change).
- in the async function always check whether data has been loaded earlier
- provide dependency or dependencies as a second parameter for useEffect
- due to that useEffect is scheduled after every DOM changes (and DOM changes are triggered by observables change).
const [tasks, setTasks] = useState<TaskModel[]>([]);
const fetchTasksAsync = async () => {
try {
const url = new URL(API_URL + '/tasks');
const resp = await axios.get(url.toString());
setTasks(resp.data);
} catch (ex) {
console.error(ex);
}
}
useEffect(() => {
if (tasks.length === 0) fetchTasksAsync().then();
}, [tasks])Fake REST API server to develop and test frontend code:
- Install json-server package as a dev depency:
yarn add --dev json-server - Add a new run script to package json:
"mock-backend": "json-server --watch db.json --port 5000"
yarn mock-backend starts the mock BE server. The endpoints and responses is described in db.json.
See the json-server GitHub page for the documentation!