Reactjs
Reactjs
Reactjs Training Materials
Copyright Notice
Copyright © 2004-2023 by NobleProg Limited All rights reserved.
This publication is protected by copyright, and permission must be obtained from the publisher prior to any prohibited reproduction, storage in a retrieval system, or transmission in any form or by any means, electronic, mechanical, photocopying, recording, or likewise.
Introduction
- Developed by engineers at Facebook
- "A JavaScript library for building user interfaces"
- React enables websites to display
- Complex animations
- Large volumes of data
- Other memory-heavy tasks without slowing down
Intro Con't
- Declarative
- Component-Based
- Reusable and independent
Declarative
- Declarative views make the code more predictable and easier to debug
- Very easy way to create interactive UIs
- Simple views for each state in the application
- Efficient update and render of the right components when the data changes
Component-Based
- Encapsulated components that manage their own state
- Compose them to make complex UIs
- Component logic is written in JavaScript instead of templates
- Easy to pass rich data through the app
- In the same time keeps state out of the DOM
Reusable
- Independent from technology stack used in the whole project
- We can develop new features without rewriting existing code
- Can render on the server (for example using Nodejs)
- Can power mobile apps using React Native
Design principles behind React
- JSX
- Rendering Elements
- Components and props
JSX
- Is not valid JavaScript (web browsers can't read it)
- Syntax extension for JavaScript
- Was written to be used with React
- JSX code looks a lot like HTML
const h1 = <h1>Hi universe</h1>;
- Also XML-like
- Have to be compiled into regular JavaScript
- Is optional and not required to use in React
- https://react.dev/learn/writing-markup-with-jsx
Rendering Elements
- The smallest building block of React apps
- Rendering an element into the DOM
- Updating the rendered element
- Updated will be only that what's necessary
- https://react.dev/learn/add-react-to-an-existing-project#step-2-render-react-components-anywhere-on-the-page
Building your first component
- Component lets split the UI into independent, reusable and isolated pieces
- Components are like JavaScript functions (conceptually)
- Accept arbitrary inputs (called "props")
- Props are read-only (function can't change it's own props)
- Return React elements describing what should appear on the screen
- Accept arbitrary inputs (called "props")
- The simple one-page app example
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello World</title>
<script crossorigin>
// const process = { 'env': { 'NODE_ENV' : 'development' } };
</script>
<script crossorigin src="https://unpkg.com/react@18.3.1/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.js"></script>
</head>
<body>
<div id="rooty"></div>
<script type="text/babel">
ReactDOM.createRoot(
document.getElementById('rooty')
).render(<h1>Hi Universe!</h1>);
</script>
</body>
</html>
How components work in React
- Components implement a render() method
- Takes input data and returns what to display
- Often uses an XML-like syntax called JSX
- Can access input data passed into the component via this.props
- Input data passed into the component can be accessed by render() via this.props
Example 1
//// JSX code
class HelloMessage extends React.Component {
render() {
// JSX element treated as JavaScript expression:
// can be saved in a variable, passed to a function, stored in an object or array, etc
return <div>Hello {this.props.name}</div>;
}
}
ReactDOM.createRoot(document.getElementById('rooty')).render(<HelloMessage name="Robert" />);
//// COMPILED JS
// class HelloMessage extends React.Component {
// render() {
// return React.createElement(
// "div",
// null,
// "Hello ",
// this.props.name
// );
// }
// }
// ReactDOM.createRoot(document.getElementById('rooty')).render(React.createElement(HelloMessage, { name: "Robert" }));
Exercise 1
Rewrite it into React function (-:
The component life cycle
- In application with many components
- Components are destroyed
- Resources taken by the components have to be free up
- Mounting
- Whenever the component is rendered to the DOM for the first time
- Unmounting
- Whenever the DOM produced by the component is removed
- Special(our own) methods called lifecycle hooks
- setState() schedules updates to the component local state
- "Common lifecycles"
Handling state in React
- State is similar to props
- But it is private and fully controlled by the component
- Local state is a feature available only to classes
- Component
- Takes input data (accessed via this.props)
- Can maintain internal state data (accessed via this.state)
- When a component's state data changes, the rendered markup will be updated by re-invoking render()
Example 2
//// JSX part
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {secondsElapsed: 0};
}
tick() {
this.setState((prevState) => ({
secondsElapsed: prevState.secondsElapsed + 1
}));
}
// Lifecycle hooks
componentDidMount() {
this.interval = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return (
<div>Seconds Elapsed: {this.state.secondsElapsed}</div>
);
}
}
//// 'createRoot()' replaced 'ReactDOM.render()', below previous way (for React 17 and older)
// ReactDOM.render(<Timer />, document.getElementById('rooty'));
ReactDOM.createRoot(document.getElementById('rooty')).render( <Timer /> );
//// COMPILED JS, rendering part only
// ( ... )
// render() {
// return React.createElement(
// "div",
// null,
// "Seconds Elapsed: ",
// this.state.secondsElapsed
// );
// }
// ( ... )
Exercise 2
Guess what? (-!
- Write function'ish version of it - of course!
Managing state
- React on its own does not provide built-in support for state management
- The React community uses libraries
- Redux
- MobX
- React state vs. Redux state
Redux
- Relies on synchronizing data through a centralized and immutable store of data
- Updates to that data will trigger a re-render of the application
- State is updated in an immutable fashion
- Sending explicit action messages which must be handled by functions called reducers
- Because of the explicit nature, it is often easier to reason about how an action will affect the state of our program
MobX
- Relies on functional reactive patterns
- State is wrapped through observables and passed through as props
- Keeps state fully synchronized for observers
- Simply marks state as observable.
- Already written in TypeScript
React state vs. Redux state
- Use React state for things that don’t matter globally
- The form is one-off
- The form’s data isn’t needed outside of it
- The form’s state is so complex, managing all of that with Redux would be even worse
- Use Redux for state that does matter globally
- We want the input value to outlive the component
- We have to have several instances of the form, synced
- We want to share particular input between different forms
- We want certain form state to have the impact on the rest of our app
Integrating React with other frameworks and plugins
- React is flexible
- Provides hooks that allow to interface with other libraries and frameworks
- For example we could use an external Markdown library and its remarkable class
- to convert the textarea's value in real time
- https://github.com/jonschlinkert/remarkable
- Or as an alternative - do the same by installing an existing component
Example 3, with ext plugin
// <script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/remarkable/1.7.1/remarkable.js"></script>
//
// JSX code
class MarkdownEditor extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {value: 'Type some *markdown* here!'};
}
handleChange(e) {
this.setState({value: e.target.value});
}
getRawMarkup() {
var md = new Remarkable();
return { __html: md.render(this.state.value) };
}
render() {
return (
<div className="MarkdownEditor">
<h3>Input</h3>
<textarea
onChange={this.handleChange}
defaultValue={this.state.value} />
<h3>Output</h3>
<div
className="content"
dangerouslySetInnerHTML={this.getRawMarkup()}
/>
</div>
);
}
}
ReactDOM.createRoot(document.getElementById('rooty')).render(<MarkdownEditor />);
Exercise 3
Yup, you know what to do now, for sure (-'
- You do, right? (-,
- Function, obviously!
Bringing it all together - an application
- Using props and state usually is enough to create the whole application
- Below TODO list example
- uses state to track the current list of items
- Also tracks the text that the user has entered
- Event handlers appear to be rendered inline
- But they will be collected and implemented using event delegation
Example 4, app example
// JSX code
class TodoApp extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {items: [], text: ''};
}
render() {
return (
<div>
<h3>TODO</h3>
<TodoList items={this.state.items} />
<form onSubmit={this.handleSubmit}>
<input onChange={this.handleChange} value={this.state.text} />
<button>{'Add #' + (this.state.items.length + 1)}</button>
</form>
</div>
);
}
handleChange(e) {
this.setState({text: e.target.value});
}
handleSubmit(e) {
e.preventDefault();
var newItem = {
text: this.state.text,
id: Date.now()
};
this.setState((prevState) => ({
items: prevState.items.concat(newItem),
text: ''
}));
}
}
class TodoList extends React.Component {
render() {
return (
<ul>
{this.props.items.map(item => (
<li key={item.id}>{item.text}</li>
))}
</ul>
);
}
}
ReactDOM.createRoot(document.getElementById('rooty')).render(<TodoApp />);
Exercise 4
Yeah, again - I know (---;
- Make it hooked!
Setting up your development environment
- Create new app
- Add React to an existing app
Create new app
- FB incubator - the source is always the best
- Common way also at the beginning is to use one of available starter-kits
Create new app Con't
- Later we can adjust it and move to production
- change the configuration
- provide security (handling users via login and passwd, etc)
- setup the db (mysql, mongodb, etc)
- wire our existing web services, write new
Add React to an Existing App
- No need to rewrite your app to start using React.
- Start with small part of application (small widget), and see if it works well for our use case
- React can be used without a build pipeline
- It's recommended to set it up to be more productive
- Build pipeline
- Package manager (Yarn, npm, etc)
- Vast ecosystem of third-party packages, easy to install or update them
- Bundler (webpack, Browserify, etc).
- Lets to write modular code and bundle it together into small packages to optimize load time
- Compiler (Babel, etc)
- Lets to write modern JavaScript code that still works in older browsers
- Package manager (Yarn, npm, etc)
Exercise 5, Full envi
We will create simple application with webpack and nodejs. We will do it in steps, to cover most important elements of Reactjs. Each step will be explained separately. Envi preparation steps (command line, in linux you might gonna need also 'sudo ' before the 'npm') npx create-react-app my-app cd my-app npm start
How to use webpack with nodejs goodies
# enabling app in dev envi
npm start
# run tests
npm test
# builds the app for production to the `build` folder
npm run build
- Step 1
- Looking at the template code
- Step 2
- change the file src/App.js
import './App.css'; function App() { return ( <div className="App"> <p>Hi Universe!</p> </div> ); } export default App;
- change the file src/App.js
- Step 3
- Create new component here components/Text.js
const Text = () => { return ( <p className="text">Lorem ipsum</p> ); } export default Text;
- Create new component here components/Text.js
- Step 4
- Modify main component src/App.js
import './App.css'; import Text from './components/Text'; function App() { return ( <div className="App"> <p>Hi Universe!</p> <Text /> </div> ); } export default App;
- Modify main component src/App.js
- Step 5
- Understanding state. Change main component file again src/App.js
import './App.css'; import { React, useState } from 'react'; import Text from './components/Text'; function App() { const [text, setText] = useState('Not clicked!'); function onButtonClick() { setText('Clicked!'); } return ( <div className="App"> <p>Hi Universe!</p> <Text /> <p>{text}</p> <button onClick={onButtonClick}>Click</button> </div> ); } export default App;
- Understanding state. Change main component file again src/App.js
- Step 6
- Passing parameters(also state) to components.
- Step 6.1. Modify file src/components/Text.js
import PropTypes from 'prop-types'; const Text = (props) => { return ( <div> <p className="text">{props.staticText}</p> <p className="text"> {`Text from parent: ${props.clickText}`} </p> </div> ); } Text.propTypes = { staticText: PropTypes.string.isRequired, clickText: PropTypes.string.isRequired } export default Text;
- Step 6.2. Modify main component file src/App.js
import './App.css'; import { React, useState } from 'react'; import Text from './components/Text'; function App() { const [text, setText] = useState('Not clicked!'); function onButtonClick() { setText('Clicked!'); } return ( <div className="App"> <p>Hi Universe!</p> {/* <Text /> */} <Text // Comment out the line below and check the 'console' staticText="Text from child component" clickText={text} /> <p>{text}</p> <button onClick={onButtonClick}>Click</button> </div> ); } export default App;
- Step 6.1. Modify file src/components/Text.js
- Passing parameters(also state) to components.
Defining our components' parent/child relationships
- Containment
- Specialization
- https://react.dev/learn/understanding-your-ui-as-a-tree
- Legacy https://reactjs.org/docs/composition-vs-inheritance.html
Lifting state up
- A single "source of truth" for any data that changes
- The state is first added to the component that needs it for rendering
- Then, if other components also need it, we can lift it up to their closest common ancestor
- Don't try to sync the state between different components, rely on the top-down data flow
- More "boilerplate" code than in two-way binding approaches
- But it takes less work to find and isolate bugs
- any state "lives" in some component and that component alone can change it
- the surface area for bugs is greatly reduced
- We can also implement any custom logic to reject or transform user input
- But it takes less work to find and isolate bugs
- If something can be derived from either props or state, it probably shouldn’t be in the state
Containment
- To reuse code between components
- Composition model is recommended instead of inheritance
- For not known children AOT in generic components
- pass children elements directly with
{props.children}
- by nesting the JSX
- For multiple generic "barrels" in a component use our own convention
- pass our own React elements as props like any other data
- pass children elements directly with
Specialization
- Components as special cases of other components
- PublicCourseEvent and ConsultancyEvent are special cases of CourseEvent
- Use composition
- more "specific" component renders a more “generic” one
- and configures it with props
- To reuse non-UI functionality between components
- extract it into a separate JavaScript module
- the components may import it and use its constructs without extending it
Event handling and conditional rendering
- Event handling
- Conditional rendering
Container vs Presentational Components
"Remember, components don’t have to emit DOM. They only need to provide composition boundaries between UI concerns" Dan Abramov (Co-author of Redux and Create React App)
- Presentational and Container Components
- it is a distinction in their purpose, not a technical one
- https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0
- use this approach only if it fits our business (processes, flow, etc)
- it can be also achieved with custom Hooks instead, so choose wisely - do not treat it as a golden hammer (-;
- Higher-Order Components
- rarely used in modern react code
- Legacy https://reactjs.org/docs/higher-order-components.html
Presentational Components
Dan Abramov's presentational components:
- Are concerned with how things look
- May contain both presentational and container components** inside, and usually have some DOM markup and styles of their own
- Often allow containment via this.props.children
- Have no dependencies on the rest of the app, such as Flux actions or stores
- Don’t specify how the data is loaded or mutated
- Receive data and callbacks exclusively via props
- Rarely have their own state (when they do, it’s UI state rather than data)
- Are written as functional components unless they need state, lifecycle hooks, or performance optimizations
- Examples: Page, Sidebar, Story, UserInfo, List
Container Components
Dan's container components:
- Are concerned with how things work
- May contain both presentational and container components inside
- but usually don’t have any DOM markup of their own except for some wrapping divs,
- and never have any styles
- Provide the data and behavior to presentational or other container components
- Call Flux actions and provide these as callbacks to the presentational components
- Are often stateful, as they tend to serve as data sources
- Are usually generated using higher order components
- such as connect() from React Redux, createContainer() from Relay, or Container.create() from Flux Utils,
- rather than written by hand
- Examples: UserPage, FollowersSidebar, StoryContainer, FollowedUserList
What are the benefits
- Better separation of concerns
- We understand our app and our UI better by writing components this way
- Better reusability
- We can use the same presentational component with completely different state sources
- and turn those into separate container components that can be further reused
- Presentational components are essentially our app’s palette
- We can put them on a single page and let the designer tweak all their variations without touching the app’s logic
- We can run screenshot regression tests on that page
- This forces us to extract layout components such as Sidebar, Page, ContextMenu and use this.props.children
- instead of duplicating the same markup and layout in several container components
Designing our React app
- Prepare dry dummy interface
- Break the UI into a Component Hierarchy
- Build a static version in React (just props, no state yet)
- Identify complete and minimal representation of UI state
- Identify where our state should Live
- Add inverse data Flow
Identify the minimal representation of UI state
Criteria for each piece of data (yes = probably not state)
- Passed in from a parent via props?
- Remain unchanged over time?
- Computable based on any other state or props in our component?
Identify where our state should Live
For each piece of state in our application
- Identify every component that renders something based on that state
- Find a common owner component
- a single component above all the components that need the state in the hierarchy
- Either the common owner or another component higher up in the hierarchy should own the state
- If we can’t find a component where it makes sense to own the state
- create a new component simply for holding the state
- and add it somewhere in the hierarchy above the common owner component
Main Exercises
- Pesel app
- Migrate from jQuery to React
- do it with React functions (-----:
- Separate validations into JS module
- Migrate from jQuery to React
- Jewel app
- First migrate it from Angular to React - yes, functioooooooons ruuuleeeeezzzzzz (--;
- follow the best practices - design first, then code it (check again this Approach)
- Improve it with form element
- Extend it with adding new jewel to model
- Add custom validation - adding same jewel not allowed
- Make sure gamers can see the list of available jewels
- Wrap it into simple navigation - use react-router module
- Home page - shows only description of the game
- Play it - redirects to the game itself
- Show jewels - view with all the gems
- Add Jewel - new jewel form
- Pesel - form from the previous exercise
- Simple - our first full example (clicked or not clicked)
- Make a view with single jewel
- Improve our jewel - add description field
- Allow to remove one jewel
- Extend it - removal of all jewels
- Make one jewel editable
- First migrate it from Angular to React - yes, functioooooooons ruuuleeeeezzzzzz (--;
Handling Forms in React
- Controlled vs uncontrolled inputs
- From uncontrolled to controlled
- Field level validations
- Disabling submit button
- Instant fields validation
- Validation on submit
- Validating several fields
- Default values
- Wizard form
- Migrating to RedUX
- Dynamic form inputs
- Context vs props
Controlled vs uncontrolled
Feature/descr | Parameters | One-time value retrieval | Validating on submit | Field validation | Disabling submit button | Enforcing input format | Several inputs | Dynamic inputs |
---|---|---|---|---|---|---|---|---|
Uncontrolled | ref | Yes | Yes | No | No | No | No | No |
Controlled | props + state | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
Uncontrolled inputs
- They are like traditional HTML form inputs
- They remember what we typed
- Their value can be reached via ref
- We have to 'pull' the value from the field when we need it
Uncontrolled inputs example
class myFormy extends Component {
handleSubmitClick = () => {
const myName = this._myName.value;
// use somehow the `myName`
}
render() {
return (
<div>
<input type="text" ref={input => this._myName = input} />
<button onClick={this.handleSubmitClick}>Give me fever</button>
</div>
);
}
}
Controlled inputs
- Accept their current value as a prop
- also a callback to change that value
- It’s a more "React way" of approaching this
- The value of input has to live in the state somewhere
- The form component that renders the input saves that in its state
- It also can be in the state of another component
- or even in the separate state store, like Redux
Controlled inputs example
class myFormy extends Component {
constructor() {
super();
this.state = {
myPropy: '',
};
}
handleMyPropyChange = (event) => {
this.setState({ myPropy: event.target.value });
};
render() {
return (
<div>
<input
type="text"
value={this.state.myPropy}
onChange={this.handleMyPropyChange}
/>
</div>
);
}
}
From uncontrolled to controlled
Steps
- Identify all form controls — textboxes, selects, checkboxes
- Initialize the state for each of them
- Make them "get" their value from this.state.
- And "set" new values to this.state (onChange prop)
- Change submit handler to get values from the state, instead of refs
Before
class UncontrForm extends Component {
handleMySubmit = () => {
const emailAddr = this._emailAddrInp.value;
const yesCheckx = this._yesCheckx.checked;
// ...
};
render() {
return (
<form onSubmit={this.handleMySubmit}>
<input type="email" ref={inset => this._emailAddrInp = inset} />
<input type="checkbox" ref={inset => this._yesCheckx = inset} />
</form>
);
}
}
After
class ContrForm extends Component {
constructor() {
super();
this.state = {
emailA: '',
yesOrNo: false,
};
}
handleMySubmit = () => {
const emailAddr = this.state.emailA;
const yesCheckx = this.state.yesOrNo;
// ...
};
handleEmailaChange = (e) => {
this.setState({ emailA: e.target.value });
};
handleYesornoChange = (e) => {
this.setState({ yesOrNo: e.target.checked });
};
render() {
return (
<form onSubmit={this.handleMySubmit}>
<input onChange={this.handleEmailaChange} />
<input type="email" value={this.state.emailA} onChange={this.handleEmailaChange} />
<input type="checkbox" checked={this.state.yesOrNo} onChange={this.handleYesornoChange} />
</form>
);
}
}
No Field level validations
- Validation status isn’t "local" to fields
- Example of "solution" http://react-reform.codecks.io/
- but implicit APIs are harder to understand and the data flow is reversed
- Example of "solution" http://react-reform.codecks.io/
- Not all validations are field-centric
- Data flow is reversed (see later examples with instant and redux validations)
- All fields have to be on-screen (multi-step forms, etc)
Disabling submit button
class SignUpForm extends React.Component {
constructor() {
super();
this.state = {
email: '',
password: '',
};
}
handleEmailChange = (evt) => {
this.setState({ email: evt.target.value });
}
handlePasswordChange = (evt) => {
this.setState({ password: evt.target.value });
}
handleSubmit = (evt) => {
if (!this.canBeSubmitted()) {
evt.preventDefault();
return;
}
const { email, password } = this.state;
alert(`Signed up with email: ${email} password: ${password}`);
}
canBeSubmitted() {
const { email, password } = this.state;
return (
email.length > 0 &&
password.length > 0
);
}
render() {
const isEnabled = this.canBeSubmitted();
return (
<form onSubmit={this.handleSubmit}>
<input
type="text"
placeholder="Enter email"
value={this.state.email}
onChange={this.handleEmailChange}
/>
<input
type="password"
placeholder="Enter password"
value={this.state.password}
onChange={this.handlePasswordChange}
/>
<button disabled={!isEnabled}>Sign up</button>
</form>
)
}
}
Instant fields validation
function validate(email, password) {
// true means invalid, so our conditions got reversed
return {
email: email.length === 0,
password: password.length === 0,
};
}
class SignUpFormInstVal extends React.Component {
constructor() {
super();
this.state = {
email: '',
password: '',
everFocusedEmail: false,
everFocusedPassword: false,
inFocus: '',
};
}
handleEmailChange = (evt) => {
this.setState({ email: evt.target.value });
}
handlePasswordChange = (evt) => {
this.setState({ password: evt.target.value });
}
handleSubmit = (evt) => {
if (!this.canBeSubmitted()) {
evt.preventDefault();
return;
}
const { email, password } = this.state;
alert(`Signed up with email: ${email} password: ${password}`);
}
canBeSubmitted() {
const errors = validate(this.state.email, this.state.password);
const isDisabled = Object.keys(errors).some(x => errors[x]);
return !isDisabled;
}
render() {
const errors = validate(this.state.email, this.state.password);
const isDisabled = Object.keys(errors).some(x => errors[x]);
return (
<form onSubmit={this.handleSubmit}>
<input
className={errors.email ? "error" : ""}
type="text"
placeholder="Enter email"
value={this.state.email}
onChange={this.handleEmailChange}
/>
<input
className={errors.password ? "error" : ""}
type="password"
placeholder="Enter password"
value={this.state.password}
onChange={this.handlePasswordChange}
/>
<button disabled={isDisabled}>Sign up</button>
</form>
)
}
}
CSS file
* {
box-sizing: border-box;
}
body {
padding-top: 0;
font-family: Helvetica Neue, Helvetica;
background: #f7f7f7;
}
h3 {
font-size: 18px;
margin-top: 20px;
margin-bottom: 5px;
}
form {
padding: 0 40px;
}
form input {
display: block;
width: 100%;
font-size: 20px;
padding: 5px 10px;
margin: 10px 0;
border-radius: 5px;
border: 1px solid #ddd;
}
form input.error {
border-color: red;
}
form button {
display: block;
width: 100%;
appearance: none;
border-radius: 5px;
background-color: #84c00c;
color: #fff;
border: none;
font-size: 16px;
height: 40px;
margin-top: 30px;
}
form button:hover {
background-color: #669509;
}
form button:disabled {
background-color: #dbf99f;
color: #333;
}
Instant Fields Exercises
- Make it more user-friendly and wait for the user's first try, before it shows any error
(Hint: use on-blur event)
- Force display of errors on all fields,
- regardless of whether they have been in focus,
- when the user hovers or clicks a disabled submit button
Validation on submit
function validate(name, email, password) {
// we are going to store errors for all fields
// in a signle array
const errors = [];
if (name.length === 0) {
errors.push("Name can't be empty");
}
if (email.length < 5) {
errors.push("Email should be at least 5 charcters long");
}
if (email.split('').filter(x => x === '@').length !== 1) {
errors.push("Email should contain a @");
}
if (email.indexOf('.') === -1) {
errors.push("Email should contain at least one dot");
}
if (password.length < 6) {
errors.push("Password should be at least 6 characters long");
}
return errors;
}
class SignUpFormSubmVal extends React.Component {
constructor() {
super();
this.state = {
errors: [],
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(e) {
e.preventDefault();
const name = ReactDOM.findDOMNode(this._nameInput).value;
const email = ReactDOM.findDOMNode(this._emailInput).value;
const password = ReactDOM.findDOMNode(this._passwordInput).value;
const errors = validate(name, email, password);
if (errors.length > 0) {
this.setState({ errors });
return;
}
// submit the data...
}
render() {
const { errors } = this.state;
return (
<form onSubmit={this.handleSubmit}>
{errors.map(error => (
<p key={error}>Error: {error}</p>
))}
<input
ref={nameInput => this._nameInput = nameInput}
type="text"
placeholder="Name"
/>
<input
ref={emailInput => this._emailInput = emailInput}
type="text"
placeholder="Email"
/>
<input
ref={passwordInput => this._passwordInput = passwordInput}
type="password"
placeholder="Password"
/>
<button type="submit">Submit</button>
</form>
);
}
}
Validation Submit Exercise
- Rewrite it in a controlled way
- Improve validation function and the component
- Change them in such a way that we would know which error is about which field
Validating several fields
function validate(email, password, passwordConfirm) {
// true means invalid, so our conditions got reversed
return {
email: email.length === 0,
password: password.length === 0,
passwordConfirm: passwordConfirm !== password,
};
}
class SignUpFormSev extends React.Component {
constructor() {
super();
this.state = {
email: '',
password: '',
passwordConfirm: '',
touched: {
email: false,
password: false,
passwordConfirm: false,
},
};
}
handleEmailChange = (evt) => {
this.setState({ email: evt.target.value });
}
handlePasswordChange = (evt) => {
this.setState({ password: evt.target.value });
}
handlePasswordConfirmChange = (evt) => {
this.setState({ passwordConfirm: evt.target.value });
}
handleBlur = (field) => (evt) => {
this.setState({
touched: { ...this.state.touched, [field]: true },
});
}
handleSubmit = (evt) => {
if (!this.canBeSubmitted()) {
evt.preventDefault();
return;
}
const { email, password } = this.state;
alert(`Signed up with email: ${email} password: ${password}`);
}
canBeSubmitted() {
const errors = validate(this.state.email, this.state.password, this.state.passwordConfirm);
const isDisabled = Object.keys(errors).some(x => errors[x]);
return !isDisabled;
}
render() {
const errors = validate(this.state.email, this.state.password, this.state.passwordConfirm);
const isDisabled = Object.keys(errors).some(x => errors[x]);
const shouldMarkError = (field) => {
const hasError = errors[field];
const shouldShow = this.state.touched[field];
return hasError ? shouldShow : false;
};
return (
<form onSubmit={this.handleSubmit}>
<input
className={shouldMarkError('email') ? "error" : ""}
type="text"
placeholder="Enter email"
value={this.state.email}
onChange={this.handleEmailChange}
onBlur={this.handleBlur('email')}
/>
<input
className={shouldMarkError('password') ? "error" : ""}
type="password"
placeholder="Enter password"
value={this.state.password}
onChange={this.handlePasswordChange}
onBlur={this.handleBlur('password')}
/>
<input
className={shouldMarkError('passwordConfirm') ? "error" : ""}
type="password"
placeholder="Confirm password"
value={this.state.passwordConfirm}
onChange={this.handlePasswordConfirmChange}
onBlur={this.handleBlur('passwordConfirm')}
/>
<button disabled={isDisabled}>Sign up</button>
</form>
)
}
}
Default values
- Make the form itself fully controlled with regard to its parent (less usual one)
- It means instead of keeping its own state, the form is always going to receive field values, as well as callbacks to change them
- Initialize the form state via props (preferred method)
- The form will still have its own state with input values. It will simply use the passed field values as a starting point.
class PostForm extends React.Component {
constructor(props) {
super(props);
this.state = {
title: props.title || '',
body: props.body || '',
};
}
}
// Used as
<PostForm
title={someTitle}
body={someBody}
onSubmit={(newTitle, newBody) => { ... }}
/>
Wizard form
Case with a discussion:
- Do you see anything problematic in here?
- Is this wizard more like Harry or Neville? (--:
- What would've Albus say about it?
class CheckoutFormPersonal extends React.Component {
constructor() {
super();
this.state = {
name: '',
email: '',
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(field) {
return (evt) => this.setState({ [field]: evt.target.value });
}
handleSubmit(evt) {
evt.preventDefault();
this.props.onSubmit();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<h3>Personal info</h3>
<input
type="text"
placeholder="Enter your name"
value={this.state.name}
onChange={this.handleChange('name')}
/>
<input
type="text"
placeholder="Enter email"
value={this.state.email}
onChange={this.handleChange('email')}
/>
<button>Next</button>
</form>
);
}
}
class CheckoutFormShipping extends React.Component {
constructor() {
super();
this.state = {
shipping_line: '',
shipping_city: '',
shipping_zip: '',
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(field) {
return (evt) => this.setState({ [field]: evt.target.value });
}
handleSubmit(evt) {
evt.preventDefault();
this.props.onSubmit();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<h3>Shipping</h3>
<input
type="text"
placeholder="Address line"
value={this.state.shipping_line}
onChange={this.handleChange('shipping_line')}
/>
<input
type="text"
placeholder="City"
value={this.state.shipping_city}
onChange={this.handleChange('shipping_city')}
/>
<input
type="text"
placeholder="ZIP"
value={this.state.shipping_zip}
onChange={this.handleChange('shipping_zip')}
/>
<button>Next</button>
</form>
);
}
}
class CheckoutFormBilling extends React.Component {
constructor() {
super();
this.state = {
billing_line: '',
billing_city: '',
billing_zip: '',
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(field) {
return (evt) => this.setState({ [field]: evt.target.value });
}
handleSubmit(evt) {
evt.preventDefault();
this.props.onSubmit();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<h3>Billing</h3>
<input
type="text"
placeholder="Address line"
value={this.state.billing_line}
onChange={this.handleChange('billing_line')}
/>
<input
type="text"
placeholder="City"
value={this.state.billing_city}
onChange={this.handleChange('billing_city')}
/>
<input
type="text"
placeholder="ZIP"
value={this.state.billing_zip}
onChange={this.handleChange('billing_zip')}
/>
<button>Checkout</button>
</form>
);
}
}
class CheckoutForm extends React.Component {
constructor() {
super();
this.state = {
step: 1,
};
this.goToNext = this.goToNext.bind(this);
}
goToNext() {
const { step } = this.state;
if (step !== 3) {
this.setState({ step: step + 1 });
} else {
alert('Submitting');
}
};
render() {
switch (this.state.step) {
case 1:
return <CheckoutFormPersonal key="personal" onSubmit={this.goToNext} />;
case 2:
return <CheckoutFormShipping key="shipping" onSubmit={this.goToNext} />;
case 3:
return <CheckoutFormBilling key="billing" onSubmit={this.goToNext} />;
}
}
}
Wizard Exercise
- Rewrite it in a controlled way (--:
- Make sure that on the submit moment we will have all the data from all the steps
- Redesign it with hooks (make them React function components)
Migrating to RedUX
- Requirements change and apps evolve
- We can’t always know in advance
- What was perfectly fine to store in the local state might need to be moved to Redux
- It can be painful to migrate to Redux
- How to make it smooth?
- Make small action reducer functions
- Create a local reducer in our component
Small action reducers
- They would take current state and some details about the action, and return the new state
- They can be literally used with the functional form of setState
function changeEmail(state, newEmail) {
return { ...state, email: newEmail }
}
changeEmail(state, 'new@email.com') // => returns state with the new value of email
handleEmailChange = (evt) => {
this.setState(state => changeEmail(state, event.target.value);
};
Small action reducers con't
Migrating it to Redux
- Instead of calling
setState(state => changeEmail(state, ...))
dispatchthis.props.onChangeEmail(...);
- Getting the current values of fields: this.state goes to this.props
- Function becomes a branch in the reducer
function loginFormReducer(state, action) {
switch (action.type) {
case 'changeEmail': {
return { ...state, email: action.newEmail };
}
}
}
Local reducer
function changeEmail(newEmail) {
return { type: 'changeEmail', email: newEmail };
}
class Form extends Component {
// ...
reduce = action => this.setState(state => this.reducer(state, action));
reducer = (state, action) => {
switch (action.type) { // note we're switching by action.type
case 'changeEmail': return { ...state, email: action.email };
}
};
render() {
// ...
<input
...
onChange={evt => this.reduce(changeEmail(evt.target.value))}
/>
}
}
Local reducer con't
Switching it from local state to Redux
- changeEmail(...) would be left without changes, and would be connected by react-redux
- Instead of having reducer in the class, it would become a Redux reducer
this.reduce(changeEmail(...))
would be replaced bythis.props.onChangeEmail(...)
Dynamic form inputs
Dealing with array inputs
- add a new item
- delete an existing one
- we need to identify it
- change details of it
- have a generic function which accepts a field name
- to reduce boilerplate if there are several fields
- have several specialized functions, one for each field
- allow to execute different logic depending on the field
- have a generic function which accepts a field name
Dynamic form inputs example
class IncorporationForm extends React.Component {
constructor() {
super();
this.state = {
name: '',
shareholders: [{ name: '' }],
};
}
handleNameChange = (evt) => {
this.setState({ name: evt.target.value });
}
handleShareholderNameChange = (idx) => (evt) => {
const newShareholders = this.state.shareholders.map((shareholder, sidx) => {
if (idx !== sidx) return shareholder;
return { ...shareholder, name: evt.target.value };
});
this.setState({ shareholders: newShareholders });
}
handleSubmit = (evt) => {
const { name, shareholders } = this.state;
alert(`Incorporated: ${name} with ${shareholders.length} shareholders`);
}
handleAddShareholder = () => {
this.setState({ shareholders: this.state.shareholders.concat([{ name: '' }]) });
}
handleRemoveShareholder = (idx) => () => {
this.setState({ shareholders: this.state.shareholders.filter((s, sidx) => idx !== sidx) });
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
type="text"
placeholder="Company name, e.g. Magic Everywhere LLC"
value={this.state.name}
onChange={this.handleNameChange}
/>
<h4>Shareholders</h4>
{this.state.shareholders.map((shareholder, idx) => (
<div className="shareholder">
<input
type="text"
placeholder={`Shareholder #${idx + 1} name`}
value={shareholder.name}
onChange={this.handleShareholderNameChange(idx)}
/>
<button type="button" onClick={this.handleRemoveShareholder(idx)} className="small">-</button>
</div>
))}
<button type="button" onClick={this.handleAddShareholder} className="small">Add Shareholder</button>
<button>Incorporate</button>
</form>
)
}
}
Using context instead of props
- Passing props can become inconvenient if
- we need to pass through many components
- many components need the same info
- Context makes some information available
- to any component in the tree below the parent
- no matter how deep it is
- no need to explicitly passing through props
- The context itself does not hold the information
- it only represents the kind of information we can provide or read from components
- Related docs:
- Exercise
- Try to re-design our Wizard form with context
- What can you say?
- What could be in the custom context there and what not (or shouldn't)?
- Try to re-design our Wizard form with context
Routing in React
- Static Routing
- configuration happens before our app renders
- Dynamic Routing
- routing that takes place as our app is rendering
- Nested routes
- Responsive routes
- https://reactrouter.com/en/main
Adding Router lib
npm install react-router-dom
Dynamic routing
- Routers
- Route Matching
- Main Route Props
- Navigation
Routers
- A router component at the core
- Creates a specialized history object
- For web projects ('react-router-dom' lib)
- <BrowserRouter> - a server that responds to requests
- <HashRouter> - a static file server
- For mobiles ('react-router-native' lib)
- <NativeRouter>
Route Matching
- Comparing a <Route>'s path prop to the current location’s pathname
- When matches it will render its content, otherwise null
- A <Route> with no path will always match
- Route matching components
- <Route>
- include to render content based on the location
- list a number of possible <Route>s next to each other
- <Routes>
- used to group <Route>s together
- <Route>
Main Route Props
- element - should be used for an existing element/component
- React.Component, functional component, just element
- path - the url's part
- <Link> - component to create links
- <NavLink> - a special type of <Link>
- can style itself as "active" when its to prop matches the current location
- <Navigate> - to force navigation
Router example
import { Routes, Route, Link } from "react-router-dom";
const Home = () => (
<div>
<h2>Home</h2>
<p>Navigation example</p>
{/* <YourHomeComponent /> */}
</div>
);
const About = () => (
<div>
<h2>About</h2>
<p>What are we doing here?</p>
</div>
);
const MyApp = () => {
return (
<div>
<nav>
<li>
<Link to={"/"}>Home</Link>
</li>
<li>
<Link to={"/about"}>About</Link>
</li>
</nav>
<hr />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</div>
);
}
export default MyApp;
Nested Routes
- Router has no 'nesting' API
- Route is just a component (like div, etc)
Nested routes example
import { Routes, Route, Link, Outlet } from "react-router-dom";
import Timer from "./Timer";
function Home() {
return ( <div>HOME page</div> );
}
function Messages() {
return ( <div><p>Message 1</p><p>Message 2</p></div> );
}
function Todos() {
return (
<>
<div>
<h4>Todo 1</h4>
<p>Fix the car</p>
</div>
<div>
<h4>Todo 2</h4>
<p>Spend some time with wife</p>
</div>
</>
);
}
function Menu() {
return (
<div>
<h2>Menu</h2>
<nav>
<li><Link to={"messages"}>Messages</Link></li>
<li><Link to={"todos"}>Todos</Link></li>
</nav>
{/* This element will render either <Messages> when the URL is
"/messages" or <Todos> at "/todos"
*/}
<Outlet />
</div>
);
}
function Nested() {
return (
<div>
<h1>Main Menu</h1>
<nav>
<li><Link to={"/"}>Home</Link></li>
<li><Link to={"/sub"}>Sub</Link></li>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="sub" element={<Menu />}>
<Route path="messages" element={<Messages />} />
<Route path="todos" element={<Todos />} />
</Route>
</Routes>
</div>
);
}
export default Nested;
Router Exercises
- Replace todos component with our Todos from this Example
- Add another link on the parent level
- It should show our Timer Example
- Create 2nd level sub-menu
- It should start from Messages
- Put one link there only - showing some Contact details
Managing state with Redux
- State of our app is stored in an object tree inside a single store
- To change the state tree we emit an action, an object describing what happened
- To specify how the actions transform the state tree, we write reducers
- Doesn't support many stores
- instead of adding stores, we split the root reducer into smaller reducers
- independently operating on the different parts of the state tree
- https://redux.js.org/introduction
Example and exercises
- reactExamples_
- redux-crud
- Rewrite our dynamic form example with Redux
- Rewrite our FTJ app with Redux
Managing state with Mobx
- Mobx is not a container for the state (not like Redux)
- To make objects trackable for MobX
- Use the @observable decorator or observable(object or array) functions
- To create functions that can automatically derive their value from the state
- Use the @computed decorator
- To automatically run functions that depend on some observable state
- Use autorun
- useful for logging, making network requests, etc
- To make React components truly reactive
- Use the @observer decorator from the mobx-react package
- They will update automatically and efficiently (also in large complex applications with large amounts of data)
- https://mobx.js.org/
Example
// Define your state and make it observable
import {observable} from 'mobx';
var appState = observable({
timer: 0
});
// Create a view that responds to changes in the State
import {observer} from 'mobx-react';
@observer
class TimerView extends React.Component {
render() {
return (<button onClick={this.onReset.bind(this)}>
Seconds passed: {this.props.appState.timer}
</button>);
}
onReset () {
this.props.appState.resetTimer();
}
};
ReactDOM.render(<TimerView appState={appState} />, document.body);
// Modify the State
appState.resetTimer = action(function reset() {
appState.timer = 0;
});
setInterval(action(function tick() {
appState.timer += 1;
}), 1000);
Mobx Exercise
- Rewrite our dynamic form example with Mobx
- Use this boilerplate https://github.com/mobxjs/mobx-react-boilerplate
- TS version https://github.com/mobxjs/mobx-react-typescript-boilerplate
Hooks
- Let use: props, state, context, refs, lifecycle
- without writing a class
- provide new way to combine them
- Optional and backwards-compatible
- Work side-by-side with existing code - can be adopted gradually
- Functions that:
- let "hook into" React state and lifecycle features from function components
- don’t work inside classes
Hooks Con't
- Allow to reuse stateful logic without changing component hierarchy
- Let split one component into smaller functions based on what pieces are related
- Examples: setting up a subscription or fetching data
- Common: useState, useEffect, useContext, useRef
- Custom hooks - reusing stateful behavior between different components
- Rules about calls
- only at the top level (not in: loops, conditions, nested f)
- only from function components (not from regular js) or custom hooks
- Examples
Custom Hook
We can define our own hooks to use state and other React features without writing a class
- As a function, it takes input and returns output
- name starts with use (useQuery, useMedia.., etc)
- returns a normal, non-jsx data (not like in functional component)
- can use other hooks such as useState, useReF.. and other custom hooks (-;
- some libraries also provide hooks - useForm (React Hook Form), useMediaQuery (MUI), useQuery (React Query), etc
Custom Hook Con't
Benefits:
- Completely separate logic from user interface
- Reusable in many different components with the same processing logic
- Therefore, the logic only needs to be fixed in one place if it changes
- Share logic between components
- Hide code with complex logic in a component, make the component easier to read
When to use:
- piece of code (logic) is reused in many places
- logic is too long and complicated
- we write custom react library (-:
- ... ? (your ideas, please!)
- Example - react-ts-api
React Query
- Most state management libraries (incl Redux)
- good for working with client state
- not for server state
- persists remotely in a location the client side cannot control
- can become outdated - we need to make asynchronous APIs for fetching and updating
- React Query - one of the best libraries for managing server state
- It helps to fetch, cache, synchronize and update data without touching any global state
React Query Con't
Helps:
- to remove complicated and misunderstood code and replace with several React Query logic
- easier to maintain and build new features without worrying about wiring up new server state data sources
- make our application feel faster and more responsive
- save bandwidth and increase memory performance
- Example - reactq-axios-ts
React Hook Form
- Less code, more performant
- reduces the amount of code we need to write
- removes unnecessary re-renders
- Isolate re-renders
- ability to isolate component re-renders -> better performance on our page or app
- Subscriptions - better performance
- ability to subscribe to individual input and form State update without re-rendering the entire form
- Faster mounting (than Formik, ReduxForm, etc)
- More here https://react-hook-form.com/
- Example - react-hook-form-ts
MUI
- MUI System - CSS utilities
- BaseUI - blank canvas
- MaterialUI -
- JoyUI -
MUI System
- Set of utilities (Flexbox, Shadows, Typography, etc)
- for quickly applying styles to components from: BUI, MUI, JUI
- or to any third-party components
- Helps to build custom designs more efficiently
- and to rapidly lay out custom designs
- Set of flexible, generic wrapper components (Box, Container, Grid, Stack)
- can be quickly customized using the sx prop
- styles can be defined directly within the components themselves
- simplifies bulky and redundant definitions of styled-components
- ensures consistency in one-off styles too
- can be quickly customized using the sx prop
BaseUI
- Library of "unstyled" React components (standalone)
- Set of foundational "headless" components
- can be built with using any styling solution
- no need to override any default style engine or theme
React with Typescript
- Typescript basics
- From react docs
- From ts docs
- Best practices, use-cases, etc
- Examples
- fe_/react-ts..
- Exercises
- Create new app with ts included
npx create-react-app my-app-ts --template typescript
- Rewrite our PESEL app with TS
- Rewrite our FTJ
- Create new app with ts included
React Native
- Creates native apps for Android, iOS
- Written in JavaScript — rendered with native code
- Seamless Cross-Platform
- React components wrap existing native code
- interact with native APIs via React’s declarative UI paradigm and JavaScript
- Setup and Using
- Expo Go - quick and easy, but restricted SDK libs
- Native CLI - longer to prepare, but gives access to many external libs
- Related docs, examples
React Native Con't
npx create-expo-app nativeExample cd nativeExample npx expo start
Later on
- connect on the same Wifi with phone
- install Expo Go app on phone
- scan the QR
Full stack with React
- Examples
- https://nextjs.org/ (mern.io deprecated..)
Server side rendering and SEO
- Next.js
- Server-rendered by default
- Automatic code splitting for faster page loads
- Simple client-side routing (page based)
- Webpack-based dev environment which supports Hot Module Replacement (HMR)
- Able to implement with Express or any other Node.js HTTP server
- Customizable with our own Babel and Webpack configurations
- Out-of-the-box tools for setting HTML tags for SEO
- https://nextjs.org/learn/
- https://zeit.co/blog/next
- react-seo npm package
- react-examples
- 14-Server-Rendering
- quickfix: remove internal from package.json npm run ssr-... scripts
Why not angular?
- Why not anyVeryPopularJavaScriptFramework.js instead?
- TypeScript
- Runtime Performance
- HTML & CSS
- Scale
- Native Rendering
- Size
- Flexibility
- Learning Curve
- https://vuejs.org/v2/guide/comparison.html#React
Debugging
- Devtools
- Error Boundaries
Upgrading, Refactoring
React's Lego
- React Components & Libraries
- Forms validation with hooks
- Easier handling of forms
Testing your React web application
- Jest
- jasmine
- karma-mocha
Jest Intro
- No config
- Supports - Babel, TypeScript, Node, React, Angular, Vue, etc
- "Snapshotting"
- Isolated
- Good API
Overview of Jest
- Zippy, secure
- tests have unique global state
- reliably runs tests in parallel
- runs previously failed tests first
- re-organizes runs based on how long test files take
Overview Con't
- Code coverage (--coverage)
- No setup needed, collects from entire projects (incl untested files)
- Simple mocking
- custom resolver for imports, mocks any object outside of scope
- rich Mock Functions API with readable syntax
Overview
- Whacking exceptions
- rich context for test-fail: toStrictEqual, toHaveProperty, toMatchSnapshoot, toThrowError, etc
- Well-documented
- maintained by Christopher Pojer from Facebook and contributors within the community
- Requires little configuration
- Extendable
Benefits of testing
Why and how
- Interfaces depend on browsers, user interactions, and many other variables
- implementing an effective testing strategy is difficult
- Consistent results are often affected by false negatives due to different factors (i.e. network)
- Frequent updates in UI: improve the experience, maximize conversions, new features
- Tests are hard to write and maintain ==> developers are less prone to cover their applications
- Tests make developers more confident ==> speed and quality
- Well tested code and well written tests ==> it works and is ready to ship
- Easier to refactor the code ==> functionalities do not change during the rewrite
- Running tests using Node.js and the console ==> faster, more predictable
Benefits of testing Con't
- Is new feature affecting other parts of the application
- Tests avoid regressions ==> tell if the new code breaks the old tests
- Greater confidence in writing new features ==> faster releases
- Code base more solid ==> new bugs can be reproduced, fixed, and covered by tests
- Testing components is easy - well organised and assigned responsibilities and boundaries
- Well written components (pure, composable, reusable) ==> can be tested as simple functions
- Main goals to test
- given different sets of props, components output is always correct
- covering all the various states that a component can have
- clicking a button ==> tests to check all the related event handlers
Benefits of testing Con't - tdd, design
- Tests can verify the component's behavior on edge cases
- all the props are null, or there is an error
- With React we can mount a tree of components and test the integration between them
- Techniques - test-driven development (TDD)
- Applying TDD ==> writing the tests first and then writing the code to pass the tests
- Force to think more about the design before implementing ==> usually leads to higher quality
Setting up the Testing Environment
- Examples
- Folder "testing"
- Tests from "react-training"
- Enzyme - helps to test Components' output
- manipulate, traverse, and in some ways simulate (like with jQuery)
- Cypress - e2e framework
Installing and Configuring Jest
- Initial setup
## Installing
npm install --save-dev jest
## Config
jest --init
## The rest depends on other tools: babel, webpack, typescript, etc
- More here:
- jestjs.io/docs/getting-started#additional-configuration
Running Watch Mode to Test File Changes
live-server --cors #manual tests with real browser
jest -o #runs only with uncommited changes (git, mercurial)
jest --watch #runs jest -o by default
jest --watchAll #runs all tests
### Interactively - Watch Usage: Press w to show more.
## Watch Usage
# › Press f to run only failed tests.
# › Press o to only run tests related to changed files.
# › Press q to quit watch mode.
# › Press p to filter by a filename regex pattern.
# › Press t to filter by a test name regex pattern.
# › Press Enter to trigger a test run.
More here: jestjs.io/docs/cli
Running Browser Tests through Node
npm test #via 'create-react-app' it uses by default the extension called 'react-scripts'
Testing a Sample JavaScript Application
TDD - Test Driven Development
- Red-Green-Refactor cycle
- Triangulation
- The more specific tests get, the more general production code needs to get
- Always implement the simplest thing that will possibly work
- Hard-coding when it's possible and to get to the real implementation - add more tests
- AAA rule: Arrange(Assemble), Act, Assert
- Design principles
- DRY - Don't Repeat Yourself
- Drying up tests when refactoring
- YAGNI - You Ain't Gonna Need It
- Hold off adding anything to project until we're really sure that it's necessary (adding a user story, a customer asks for it, etc)
- For example create-react-app application template - favicon.ico, a sample logo, CSS files, etc
- DRY - Don't Repeat Yourself
TDD Con't
Good vs Great
Good test | Great test |
---|---|
Arrange: Sets up test dependencies | Short |
Act: Executes production code under test | Descriptive |
Assert: Checks expectations are met | Independent of other tests |
lol | Has no side-effects |
Examples
- Example 1 - "1oneDetailOnly"
- Example 2 - "2viewsListAndDetail"
Design upfront
Exercises
- ("ex1") Display in the UI the rest of fields from model
- Wrap data into HTML table
- Add a heading to Appointment to make it clear which appointment time is being viewed
- Prepare fake data generator (DRY)
- Prepare new Find The Jewel project (name it ftj)
- Use create-react-app ( cd ; cd Documents/reactjsMaterials ; npx create-react-app ftj )
- Make oneJewel component and test it
- Make listJewels component and test it
- Clicked jewel button should be different then the rest of jewel buttons (add css class dynamically)
Testing a React App
- Simple form and user input
- Testing React Components
- About Stateful Components
Set up, execute function, assert results
Example
- "4simpleForm"
Exercises
- Add dropdown - choosing stylist (before time slot), filtering based on required service
- reflect stylists availability and the choice (update the table)
- Make form for adding new jewel and test it
- Refactor the app into JewelGame class component with state
- Use this ToDo list example as a skeleton
- Refactor the app into JewelGame class component with state
Testing the Business Logic
Test Doubles
- Used to verify interactions with collaborating objects
- Spies and stubs
- Extracting helper code into modules (readability - shorter and clearer)
Examples
- "6testDoubles"
Exercises
- Extend 6testDoubles
- Add a test to CustomerForm - the error state is cleared when the form is submitted again
- Update the AppointmentForm tests to use jest.fn(), jest.spyOn(), and all of the helpers
- Extend AppointmentForm so that it submits an appointment using a POST request to /appointments (endpoint returns a 201 Created status without any body)
- Update the tests in AppointmentsDayView to use the helpers
- Add engine component in ftj app
- Test it with doubles
- In ftj app refactor 'adding new jewel' into another component
Testing the User Interface
- Loader and render, shallow rendering
- Forms
- Filtering and Searching Data
- Testing Router
- Testing Redux
- Testing GraphQL
Loader and render, shallow rendering
- Stubbing out components
- Loader components correctly instantiate rendering components.
- Shallow rendering
- Stubbing and spying on child components - avoids running their side effects (fetch requests, etc)
- Easily assert that we set their props correctly
- Examples - "8forms"
Forms
- Client-side validation
- Handling server errors
- Indicating submit
- Examples - "8forms"
- Exercises
- Within "8forms"
- Clear any validation errors when corrected
- Use the 'onChange' handler instead of 'onBlur' (faster interaction with user)
- Disables the Submit button after submitting
- Write tests all functions within the formValidation module
- Simplify 'handleSubmit' - extract doSave func (pulls out the main body of the if statement)
- With ftj
- Validate adding new jewel, not allow when it exists already
Filtering and Searching Data
- More complex user interactions (between UI and an API)
- Order of adding features
- Tests help admit when we're doing the wrong thing
- Examples - "10searchingPaging"
- Exercises
- With "10searchingPaging"
- Disable the 'Previous' button if the user is on the first page
- Disable the 'Next' button if the current listing has fewer than 10 records on display
- The /customers endpoint supports a limit parameter - specifies the number of records that are returned
- Provide a mechanism for the user to change the number of records returned on each page
- For ftj
- Allow to search for jewels
Testing Router
- General rules for testing React Router
- Building a root component
- Using the location query string to store component state
- Examples - "12router"
- Exercises
- For ftj
- Make router for showing list of jewels and a single jewel
Testing Redux
- Testing a Redux saga
- Asynchronous requests with saga
- Switching out component state for Redux state
- Shifting workflow to Redux
- Examples - "13redux"
- Exercises
- For todo_ts
- Add button "RemoveAll"
Testing GraphQL
- Testing the Relay environment
- Building the GraphQL reducer
- Examples - "14graphql"
- Exercises
- With "14graphql"
- Reshape the remaining fetch calls to use their GraphQL counterparts
Running Snapshot Tests
Snapshot Tests
- Useful especially when testing presentational components
- They do not manage state
- Typically used to display data passed down from parent components as props
- or to display hardcoded data directly in the component itself
- Provided by Jest
- We simply want to make sure the HTML output of a component does not change unexpectedly
- Case scenario - developer does change the component's HTML structure
- He adds another paragraph element with static text
- Snapshot test will fail and provide a visual of the changes
- Example - "rtlExamples", 2
- Exercise - in ftj make a snapshot test
- Separate game description into new presentational component
Troubleshooting
- jest-dom methods
- allow to write more descriptive test code
- provide better context-specific error messages
- Using RTL (React Testing Library)
- Approach
- render React elements into the Document Object Model (DOM)
- select resulting DOM elements
- make assertions on the expected resulting behavior
- Examples - "rtlExamples"
- Approach
RTL examples
- 1 - test-utils VS enzyme VS rtl
- 2 - presentational, debug
- 3 - user events, interacting with APIs, again TDD
- 4 - integration testing, 3rd party plugins
- 5 - updating dependencies, enzyme2rtl, test-utils2rtl, best practices
- 6 - tools and plugins, more best practices
- 7 - e2e, cypress, cucumber
Photos Sources
- kurierhistoryczny.pl/uploads/articles/237/stanczykmaly_rf89RS.jpg
- cdn.shopify.com/s/files/1/0882/5118/products/Batman-The-Killing-Joke-Deluxe-Graphic-Novel-1008696_1024x1024.jpeg?v=1461690370
- wpblog.semaphoreci.com/wp-content/uploads/2019/01/Snapshot_Testing_React_Components_with_Jest_-_Semaphore_CI-1024x575.png