https://github.com/thomd/on-react
Notes on React
https://github.com/thomd/on-react
javascript react
Last synced: 24 days ago
JSON representation
Notes on React
- Host: GitHub
- URL: https://github.com/thomd/on-react
- Owner: thomd
- Created: 2018-12-08T18:48:55.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2019-02-25T22:22:34.000Z (over 7 years ago)
- Last Synced: 2025-02-05T21:42:47.137Z (over 1 year ago)
- Topics: javascript, react
- Language: JavaScript
- Size: 63.5 KB
- Stars: 0
- Watchers: 3
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# On React
Some **unstructured notes** and **example code** on React
# Basic Setup with Babel & Webpack 4
npm i react react-dom
npm i -D @babel/core @babel/preset-env @babel/preset-react
npm i -D babel-loader webpack webpack-cli webpack-dev-server
Create `webpack.config.js`:
module.exports = {
entry: './src/index.js',
output: {
path: __dirname + '/dist',
publicPath: '/',
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: ['babel-loader']
}
]
},
resolve: {
extensions: ['*', '.js']
},
devServer: {
contentBase: './dist'
}
};
Create an entry file `src/index.js`:
import React from 'react'
import ReactDOM from 'react-dom'
import App from './app'
ReactDOM.render(, document.getElementById('app'))
Create `.babelrc`:
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
]
}
In order to support ES6/7/8 features, add further babel plugins, like e.g.
npm i -D @babel/plugin-proposal-class-properties
```diff
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
+ plugins: [
+ '@babel/plugin-proposal-class-properties'
+ ]
}
```
Start Development Server
webpack-dev-server --mode development
or
npm start
## ESLint
npm i -D babel-eslint eslint eslint-loader eslint-plugin-react
Add `eslint-loader` to Webpack Config:
```diff
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
- use: ['babel-loader']
+ use: ['babel-loader', 'eslint-loader']
}
]
},
```
and add a ESLint configuration `.eslintrc`:
{
parser: "babel-eslint",
plugins: ["react"],
rules: {
"react/prop-types": ["warn"]
},
extends: ["eslint:recommended", "plugin:react/recommended"],
env: {
browser: true,
node: true,
es6: true
}
}
## Accessibility (a11y) in React
Lint and audit for Accessibility issues using **ESLint** and **React-axe**.
First install
npm i -D eslint-plugin-jsx-a11y react-axe
then extend `.exlintrc` configuration
```diff
{
parser: "babel-eslint",
- plugins: ["react"],
+ plugins: ["react", "jsx-a11y"],
rules: {
"react/prop-types": ["warn"]
},
- extends: ["eslint:recommended", "plugin:react/recommended"],
+ extends: ["eslint:recommended", "plugin:react/recommended", "plugin:jsx-a11y/recommended"],
env: {
browser: true,
node: true,
es6: true
}
}
```
and add in your **entry file** a call to `axe()` for **development only**. Audit runs in the browser and results will show in the Chrome DevTools console.
```diff
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
+ if (process.env.NODE_ENV === 'development') {
+ const axe = require('react-axe')
+ axe(React, ReactDOM, 1000)
+ }
ReactDOM.render(, document.getElementById('app'))
```
# React Concepts
## Elements
React Elements are the **smallest building blocks** of React apps. Elements are used to build **components** and are rendered into the DOM using `ReactDOM.render()`.
Elements are **immutable**, are **plain objects**, and are **cheap to create**.
## Components
Conceptually, components are **pure functions**. They accept read-only **props** and return **elements** describing what should appear on the screen.
Always prefer **composition over inheritance** for components.
**Container components** don’t know their children ahead of time, for example generic boxes like dialogs:
```
const Dialog = (props) => {
return (
)
}
function App() {
return (
Hello Dialog
)
}
```
**Specialization** is achieved by composition, where a more *specific* component renders a more *generic* one and configures it with `props`:
```diff
- const Dialog = (props) => {
+ const Dialog = ({title, ...props}) => {
return (
+ {title ? {title}
: null}
)
}
+ const WelcomeDialog = props => {
+ return
+ }
function App() {
return (
- Hello Dialog
+ Hello Welcome Dialog
)
}
```
## Props
The difference between `state` and `props` is, that `state` is **owned by the component** itself while `props` is something that is passed down to the component by it's **parent**.
And the similarity (sort of) is that React **automatically re-renders** your component when either the component's `state` changes or when the component's `props` changes.
## JSX
Babel compiles JSX down to `React.createElement()` calls:
```jsx
const element = (
Hello World
)
```
is identical to:
```javascript
const element = React.createElement('h1', {className: 'greeting'}, 'Hello World')
```
## State
**Always** use `setState` function to change state and **never** mutate it directly (except in the constructor).
As updates of `setState` may be asynchronous, do not rely on it to update the state immediately.
If your new state does **not depend** on the old state, then you can use `this.setState(object)` like for
example:
```javascript
this.setState({
active: true
})
```
If your new state **depends** on the old state then use a callback `this.setState( (state, props) => {...})` like for example:
```javascript
this.setState(state => ({
counter: state.counter + 1
}))
```
State updates are **merged**. If your state contains several independent variables, then you can update them independently with separate `setState()` calls.
### Form State
In HTML, form elements such as ``, ``, and `` typically maintain their own state and update it based on user input. In React, mutable state is typically kept in the state property of components, and only updated with `setState()`.
Best practice is to make the React state be the **single source of truth**.
### Lifting State Up
If several components need to reflect the same changing data, it is recommended to **lift the shared state up** to their **closest common ancestor**.
For example a parent component can provide a **setState-callback-function** as a prop to a child component which sets state of the parent.
## Lifecycle Methods
Stateful class components inherit from `React.Component` the following methods:
1. The `componentWillMount()` method is only called one time, which is before the initial render. This method
**is deprecated** since React 16.3.
2. The `componentDidMount()` method runs after the component output has been rendered to the DOM.
3. The `componentWillUnmount()` method runs before the component output has been removed from the DOM.
# Error Boundaries
A JavaScript error in a part of the UI shouldn’t break the whole app. `try / catch` only works for **imperative** code, however React components are **declarative** and specify **what** should be rendered.
**Error boundaries** preserve the declarative nature of React.
First create a Error boundary component `./src/`
import React from 'react'
export default class DefaultErrorBoundary extends React.Component {
state = { hasError: false }
static getDerivedStateFromError() {
return { hasError: true }
}
render() {
const { hasError } = this.state
const { children } = this.props
return hasError ?
Something went wrong : children
}
}
Then wrap your component or app with it, e.g. like this:
```diff
import React from 'react'
import ReactDOM from 'react-dom'
+ import DefaultErrorBoundary from './DefaultErrorBoundary'
import App from './App'
ReactDOM.render(
+
+
,
document.getElementById('app'))
```
React doesn’t need error boundaries to recover from errors in **event handlers**. Unlike the render method and lifecycle methods, the event handlers don’t happen during rendering. So if they throw, React still knows what to display on the screen.
If you need to catch an error inside event handler, use the regular JavaScript `try / catch` statements.
# Testing React with Jest
First install **jest** test runner
npm i -D jest
npm i -D react-testing-library jest-dom
npm i -D babel-jest babel-core@bridge
then add a jest configuration `jest.config.js` for **testing globals**:
module.exports = {
setupTestFrameworkScriptFile: '/testSetup.js'
}
and create this referenced setup script `testSetup.js`:
import 'jest-dom/extend-expect'
import 'react-testing-library/cleanup-after-each'
and then create a **test file** matching this filename pattern `**/__tests__/**/*.js?(x),**/?(*.)+(spec|test).js?(x)`, e.g. `./src/App.test.js`
import React from 'react'
import { render } from 'react-testing-library'
import App from './App'
describe('App', () => {
it('should render', () => {
render()
})
})
and run test with
nps test jest
mpn t
## Tests with Dynamic Imports
For **dynamic imports**, we need **babel-plugin-dynamic-import-node**:
npm i -D babel-plugin-dynamic-import-node
and a environment specific plugin setting in `.babelrc`:
```diff
{
presets: [
"@babel/preset-env",
"@babel/preset-react"
],
plugins: [
'@babel/plugin-proposal-class-properties',
'@babel/plugin-syntax-dynamic-import'
],
+ env: {
+ test: {
+ plugins: [
+ 'dynamic-import-node'
+ ]
+ }
+ }
}
```
# Main Concepts
TODO
# Refs
Refs provide a way to **access DOM nodes or React elements** created in the render method.
Refs are attached to React elements via the `ref` attribute.
Refs are either created by using `React.createRef()` and referenced with the `current` attribute of the ref or by simply using a callback pattern `el => this.myRef = el` and a referrenc of `myRef`.
The value of the ref depends on the type of node:
1. When the ref attribute is used on an **HTML element**, the ref receives the underlying **DOM element** as its current property. You can use the `ref` attribute inside a function component as long as you refer a DOM element.
2. When the ref attribute is used on a **custom class component**, the ref object receives the **mounted instance of the component** as its current. You can not use the `ref` attribute on function components.
### Example using the callback pattern
```jsx
class TextInput extends Component {
componentDidMount() {
this.textInput.focus()
}
render() {
return (
this.textInput = el} />
)
}
}
```
### Example using createRef
```diff
class TextInput extends Component {
+ textInput = React.createRef()
componentDidMount() {
- this.textInput.focus()
+ this.textInput.current.focus()
}
render() {
return (
- this.textInput = el} />
+
)
}
}
```
# Render Props
The term **render prop** refers to a pattern for sharing code between React components using a **prop** whose value is a **function**.
### Example
Having a stateful component `WithState.jsx` which expects a function rendering this state
```jsx
const WithState = props => {
const name = 'foobar'
return (
{props.children(name)}
)
}
```
you can then use this component and implement non-dependent components which use this state like this
```jsx
const App = () => {
return (
{value => }
{value => }
)
}
const Headline = ({value}) =>
{value}
const Paragraph = ({value}) => {value}
```
This is analogue to a closure:
```javascript
const withState = fn => {
const state = 'foobar'
fn(state)
}
const app = () => {
withState(value => {
// do something with value
})
}
```
# React Ecosystem
## React Router
**React Router** provides browser features like
1. browser should **change the URL** when you navigate to a different screen.
2. **Deep linking** should work.
3. The **browser back (and forward) button** should work like expected.
**React Router** provides two different kind of routes using the **History API**:
1. `BrowserRouter` which builds classic URLs like `https://application.com/dashboard`
2. `HashRouter` which builds URLs like `https://application.com/#/dashboard`
### Install
npm install react-router-dom
### Components
1. `BrowserRouter`, usually aliased as `Router` wraps all your Route components
2. `Link` generate links to your routes
3. `Route` show - or hide - the components they contain
### BrowserRouter
A `` component can only have one child element, hence wrapps the complete application.
```diff
import React from 'react'
+ import { BrowserRouter as Router } from 'react-router-dom'
export default () => {
return (
+
// Application
+
)
}
```
### Link
The `` component is used to trigger new routes.
```diff
import React from 'react'
- import { BrowserRouter as Router } from 'react-router-dom'
+ import { BrowserRouter as Router, Link } from 'react-router-dom'
export default () => {
return (
// Application
+
+ Home
+ About
+
)
}
```
### Route
Anywhere that you want to only render content based on the location’s pathname, you should use a `` element.
```diff
import React from 'react'
- import { BrowserRouter as Router, Link } from 'react-router-dom'
+ import { BrowserRouter as Router, Link, Route } from 'react-router-dom'
+ import { Home, About } from './Pages'
export default () => {
return (
- // Application
+
+
+
+
Home
About
)
}
```
Without the `exact` attribute, `path='/'` would also match `/about`, since `/` is contained in the route.
Routes have three props that can be used to define what should be rendered when the route’s path matches. **Only one** should be provided to a `` element.
1. `component`: a React component.
2. `render`: a function that returns a React element.
3. `children`: a function that returns a React element. Unlike the prior two props, this will always be rendered, regardless of whether the route’s path matches the current location.
```jsx
(
)}/>
(
props.match ? :
)}/>
```