Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions 05_controlled-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,13 +173,18 @@ However, we can agree in two things.
**One solution** for this is `Debounce` which ensures that a `handler` is not fire or called so often.
For more information (also Throttling): https://www.npmjs.com/package/react-throttle

1. Install `react-throttle` package
2. Destructure and import Debounce
1. Install `react-throttle` package: `npm install --save react-throttle`
2. Import `Debounce` (named export):

```javascript
import { Debounce } from 'react-throttle';
```

3. Add the Debounce component with the proper configuration wrapping the input
(... and remove `value={this.state.yourName}` from your input, otherwise it will not work. Don't worry, Debounce will take care of showing the proper data/value)
(... and remove `value={this.state.yourName}` from your input, otherwise it will not work. The input becomes uncontrolled while Debounce manages when the `onChange` is allowed to fire.)

```javascript
<Debounce time="400" handler="onChange">
<Debounce time={400} handler="onChange">
<input
type="text"
style={{ display: 'block' }}
Expand Down
22 changes: 17 additions & 5 deletions 08_redux.md
Original file line number Diff line number Diff line change
Expand Up @@ -844,9 +844,19 @@ Title: provident id voluptas

Now, instead of dispatching in our Component we are going to resolve the promise and dispatch from our action creator using `redux-thunk` (we were using `redux-promise` to return an action with the payload property and a promise as value).

We have to add redux-thunk middleware to our **src/index.js**
First, install `redux-thunk`:

```
npm install --save redux-thunk
```
Comment on lines +849 to +851

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language identifier to code fence.

The npm install command block should specify bash or shell as the language identifier for proper syntax highlighting and to satisfy markdown linters.

📝 Proposed fix
-```
+```bash
 npm install --save redux-thunk
</details>

As per coding guidelines, the static analysis tool flagged: "Fenced code blocks should have a language specified (MD040, fenced-code-language)".

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.22.1)</summary>

[warning] 849-849: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @08_redux.md around lines 849 - 851, The fenced code block containing the npm
install command ("npm install --save redux-thunk") lacks a language identifier;
update that fence to include a shell language (e.g., change the opening fence to
bash or shell) so the block becomes bash followed by the command and the closing to satisfy MD040 and enable proper syntax highlighting.


</details>

<!-- fingerprinting:phantom:triton:puma -->

<!-- d98c2f50 -->

<!-- This is an auto-generated comment by CodeRabbit -->


Then, in **src/index.js**, swap the middleware. Note the new import — `redux-thunk` exports a default function, so we import it as `reduxThunk`.

```javascript
import reduxThunk from 'redux-thunk';
// (you can keep the existing `import ReduxPromise from 'redux-promise';`
// commented out for now, or remove it entirely — we are no longer using it)

const store = createStore(
rootReducer,
//composeEnhancers(applyMiddleware(ReduxPromise))
Expand Down Expand Up @@ -877,19 +887,21 @@ export const fetchComments = () => dispatch => {
};
```

Go to **src/reducers/commentsReducer.js** and replace
Now go to **src/reducers/commentsReducer.js**. With `redux-promise` the reducer was reading `action.payload.data` (because `payload` was the whole axios response). With the thunk above we are dispatching `payload: response.data` directly, so the reducer no longer needs the `.data` hop.

Change your current reducer body...

```javascript
return [...state, ...action.payload];
return _.mapKeys(action.payload.data, 'id');
```

with
... to:

```javascript
return Object.assign({}, state, _.mapKeys(action.payload, 'id'));
```

<!-- TODO: Explain return Object.assign({}, state, _.mapKeys(action.payload, 'id')); -->
(`Object.assign({}, state, ...)` merges the newly normalized batch into the existing state instead of replacing it; useful if you fetch more comments later without throwing away what you already have.)

Go to your component, example: **src/App.js** and...

Expand Down
8 changes: 3 additions & 5 deletions 09_packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ npm install --save react-router-dom

Example use:

```
```javascript
import React, { Component } from 'react';
import { BrowserRouter, Route, Link } from 'react-router-dom';
import ReactDOM from 'react-dom';
Expand Down Expand Up @@ -517,8 +517,6 @@ export default (state = [], action) => {
};
```

Remember that `axios` returns a `promise` (in our example passed it as the value of payload property) that we have to resolve.

Note: In some of our examples we use `redux-promise` which, with `axios`, it "checks" the `payload` property of the `actions`, and if this payload is a `promise`, redux-promise (middleware) stops the action, waits until the request finishes and **then** dispatches a **NEW action** but with the **same type** property and for payload, the request properly resolved. This new action will follow its logic course and hit the reducers.
Remember that `axios` returns a `promise` (in our example passed it as the value of `payload`) that we have to resolve. **The reducer above assumes `redux-promise` is applied to the store** — without it, `action.payload` arrives at the reducer as the unresolved Promise, and `action.payload.data` would be `undefined`.

*Heads up*: `redux-promise` is archived/unmaintained. The standard alternative for async work in classic Redux is `redux-thunk` (covered later in the Redux chapter). For new projects, the canonical recommendation is **Redux Toolkit** (`@reduxjs/toolkit`) with `createAsyncThunk` or `RTK Query`.
How `redux-promise` works: it inspects each dispatched action's `payload`. If `payload` is a Promise, the middleware halts the action, waits for the Promise to resolve, then dispatches a **new action with the same `type`** but `payload` set to the resolved value (here, the axios response object — which is why the reducer reads `action.payload.data`). The Redux chapter walks through this end-to-end and also shows the equivalent setup with `redux-thunk`.
11 changes: 5 additions & 6 deletions 10_unit-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,19 @@ First, install enzyme and the proper adapter (we are using React 16).
CMD or terminal:

```
npm install enzyme enzyme-adapter-react-16 jest-cli@20.0.4 --save-dev
npm install enzyme enzyme-adapter-react-16 --save-dev
```

Yes... We are saving it as a dev dependency. So, if you go to your package.json you will see something like:
Yes... We are saving them as dev dependencies. So, if you go to your package.json you will see something like:

```json
"devDependencies": {
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"jest-cli": "^20.0.4"
"enzyme": "^3.8.0",
"enzyme-adapter-react-16": "^1.7.1"
}
```

Note: At the moment I'm writing this tutorial the last Jest version is 23.4.1, however, react-scripts is locked at 20.0.4 so other will not work.
Note: `create-react-app` already ships `jest` (currently in the 23/24 series, depending on your CRA version) — there's no need to install Jest yourself, and you should *not* pin `jest-cli` to an older version: the bundled Jest is what `react-scripts test` invokes.

We are going to create **src/tempPolyfills.js**

Expand Down
105 changes: 57 additions & 48 deletions 11_webpack.md
Original file line number Diff line number Diff line change
Expand Up @@ -414,18 +414,19 @@ Now... Let's install some dependencies:

* nodemon
* webpack
* babel-core
* @babel/core
* babel-loader
* babel-plugin-async-to-promises
* babel-plugin-syntax-dynamic-import
* @babel/plugin-syntax-dynamic-import
* babel-plugin-transform-async-to-promises
* babel-plugin-transform-runtime
* @babel/plugin-proposal-class-properties
* @babel/plugin-transform-runtime
* babel-plugin-universal-import
* babel-polyfill
* babel-preset-env
* babel-preset-es2015
* babel-preset-react
* babel-preset-stage-2
* @babel/polyfill
* @babel/preset-env
* @babel/preset-es2015
* @babel/preset-react
* @babel/preset-stage-2
Comment on lines +427 to +429

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚖️ Poor tradeoff

🧩 Analysis chain

🌐 Web query:

Are @babel/preset-es2015and@babel/preset-stage-2 valid packages in Babel 7?

💡 Result:

Yes for “are they valid packages to install and reference”, but no for the intent of Babel 7 compatibility: both presets are deprecated/removed in Babel 7’s recommended preset set. 1) @babel/preset-es2015 - Babel’s docs recommend using @babel/preset-env instead of the old yearly preset approach; Babel notes that if you see a preset like babel-preset-es2015 you should migrate to preset-env. [1] - Babel also has documentation pages for @babel/preset-es2015, indicating the preset package name exists and is documented. [2] - However, the “yearly presets” were deprecated in Babel 6, and the recommended replacement for Babel 7 is @babel/preset-env. [1][3] 2) @babel/preset-stage-2 - Babel’s docs state that as of Babel 7, all the “stage” presets are deprecated. [4] - Babel’s presets documentation similarly says Stage-X (experimental) presets were deprecated and stopped publishing in Babel 7; it recommends specifying proposals as plugins rather than using a catch-all preset. [5] - Babel’s migration guidance also states “We are removing the stage presets in favor of explicit proposal usage.” [3] Practical takeaway - @babel/preset-es2015 is not the recommended choice in Babel 7 (migrate to @babel/preset-env). [1][3] - @babel/preset-stage-2 is deprecated and should not be relied on in Babel 7; migrate by using the specific proposal plugins instead. [4][5][3] If you tell me your current Babel config and intended target environments, I can suggest an equivalent Babel 7 config.

Citations:


Replace deprecated Babel presets for Babel 7 compatibility.

Lines 427–429 list @babel/preset-es2015 and @babel/preset-stage-2, both of which are deprecated and removed in Babel 7. Per Babel's official migration docs:

  • @babel/preset-es2015 should be replaced with @babel/preset-env
  • @babel/preset-stage-2 was removed entirely; use explicit proposal plugins instead (e.g., @babel/plugin-proposal-class-properties for class properties)

Update the snippet and install command to reflect the correct packages for Babel 7.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@11_webpack.md` around lines 427 - 429, Replace the deprecated presets listed
(`@babel/preset-es2015`, `@babel/preset-stage-2`) with Babel 7-compatible
packages: use `@babel/preset-env` instead of `@babel/preset-es2015`, and remove
`@babel/preset-stage-2` and add the specific proposal plugins you need (for
example `@babel/plugin-proposal-class-properties` for class fields); also update
any install commands to install `@babel/preset-env` and the chosen
`@babel/plugin-proposal-*` plugins instead of the removed presets.


```
npm install --save nodemon
Expand Down Expand Up @@ -699,17 +700,18 @@ touch public/hello.js public/hello.html public/hello.h public/hi.js

On `webpack.config.prod.js`, add...

Outside the config object
Outside the config object — note we are on `clean-webpack-plugin` v3, which dropped the positional-args / `root` / `exclude` API of v1/v2 in favor of a single options object. The plugin now resolves paths from your `output.path` automatically, so we just tell it which patterns to preserve.

```javascript
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');

let pathsToClean = ['dist/', 'build/', 'public/'];
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

let cleanOptions = {
root: path.resolve(__dirname, '../'),
exclude: ['template.html', 'manifest.json', 'favicon.ico'],
const cleanOptions = {
cleanOnceBeforeBuildPatterns: [
'**/*',
'!template.html',
'!manifest.json',
'!favicon.ico'
],
verbose: true,
dry: false
};
Expand All @@ -718,19 +720,18 @@ let cleanOptions = {
Inside our `config/`

```javascript
plugins: [new CleanWebpackPlugin(pathsToClean, cleanOptions)];
plugins: [new CleanWebpackPlugin(cleanOptions)];
```

Note the named import (`{ CleanWebpackPlugin }`) — v3 switched from a default export to a named one.

And execute: `npm run build`
_Note: It could take some time._

The output will start with...
The output will look like...

```
clean-webpack-plugin: C:\practice\nocra\dist has been removed.
clean-webpack-plugin: C:\practice\nocra\build has been removed.
clean-webpack-plugin: C:\practice\nocra\public has been removed.
clean-webpack-plugin: 3 file(s) excluded - favicon.ico, manifest.json, template.html
clean-webpack-plugin: removed files inside C:\practice\nocra\public
```

And as you can see, all the dummy files were removed. Also, our bundles (\*.js) which were deleted (by clean-webpack-plugin) and re-generated (by webpack).
Expand Down Expand Up @@ -1181,15 +1182,18 @@ const webpack = require('webpack');
const config = require('../config/webpack.config.dev.js');
const compiler = webpack(config);

const webpackDevMiddleware = require('webpack-dev-middleware')(
compiler,
config.devServer
);
// webpack-dev-middleware takes its own options object (publicPath, stats,
// mimeTypes, etc.) — NOT webpack-dev-server's `devServer` block. Passing
// `config.devServer` here would silently ignore everything inside it
// (contentBase, hot, overlay are all webpack-dev-server options).
const webpackDevMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: config.output.publicPath || '/',
stats: 'minimal'
});

const webpackHotMiddleware = require('webpack-hot-middleware')(
compiler,
config.devServer
);
// webpack-hot-middleware also has its own options object (path, log, heartbeat).
// We accept the defaults here.
const webpackHotMiddleware = require('webpack-hot-middleware')(compiler);

class RouterAndMiddlewares {
constructor() {
Expand Down Expand Up @@ -1344,9 +1348,10 @@ And, in `webpack.config.js` add a new rule:
{ loader: 'style-loader' },
{
loader: 'css-loader',
query: {
modules: true,
localIdentName: '[name]__[local]__[hash:base64:5]'
options: {
modules: {
localIdentName: '[name]__[local]__[hash:base64:5]'
}
}
}
]
Expand Down Expand Up @@ -1567,10 +1572,10 @@ And wrap everything that we don't need in `production` inside the condition: `!i
let webpackDevMiddleware, webpackHotMiddleware;
if (!isProd) {
...
webpackDevMiddleware = require('webpack-dev-middleware')(
compiler,
config.devServer
);
webpackDevMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: config.output.publicPath || '/',
stats: 'minimal'
});
...
}
```
Expand Down Expand Up @@ -1779,9 +1784,10 @@ In `webpack.config.js` remove or comment:
{ loader: 'style-loader' },
{
loader: 'css-loader',
query: {
modules: true,
localIdentName: '[name]__[local]__[hash:base64:5]'
options: {
modules: {
localIdentName: '[name]__[local]__[hash:base64:5]'
}
}
}
]
Expand All @@ -1799,9 +1805,10 @@ module: {
{ loader: 'style-loader' },
{
loader: 'css-loader',
query: {
modules: true,
localIdentName: '[name]__[local]__[hash:base64:5]'
options: {
modules: {
localIdentName: '[name]__[local]__[hash:base64:5]'
}
}
}
]
Expand Down Expand Up @@ -1842,9 +1849,10 @@ const cssForDev = [
{ loader: 'style-loader' },
{
loader: 'css-loader',
query: {
modules: true,
localIdentName: '[name]__[local]__[hash:base64:5]'
options: {
modules: {
localIdentName: '[name]__[local]__[hash:base64:5]'
}
}
}
];
Expand Down Expand Up @@ -1972,12 +1980,13 @@ with this...
```javascript
this.app.use(
expressStaticGzip('public', {
enableBrotli: true,
orderPreference: ['br']
enableBrotli: true
})
);
```

With `enableBrotli: true`, `express-static-gzip` v1 serves the pre-compressed `.br` file when the request's `Accept-Encoding` includes `br`, falling back to `.gz` (when the client only accepts gzip) and then the uncompressed asset. We don't need to spell out a preference order in v1.

We can also add `gzip`
Install: compression-webpack-plugin

Expand Down