diff --git a/README.md b/README.md
index 412fcf9..257c3ad 100644
--- a/README.md
+++ b/README.md
@@ -283,7 +283,6 @@
[Pull request](https://github.com/nickovchinnikov/minesweeper/pull/50/files)
-
## Redux intro
### Pure functions benifits
@@ -292,5 +291,10 @@
[Pull request](https://github.com/nickovchinnikov/minesweeper/pull/51/files)
-###
-### Redux basic example
\ No newline at end of file
+### Referential transparency
+
+[Pull request](https://github.com/nickovchinnikov/minesweeper/pull/52/files)
+
+### Redux basic example
+
+[Pull request](https://github.com/nickovchinnikov/minesweeper/pull/53/files)
diff --git a/examples/Redux/ClickCounter.test.tsx b/examples/Redux/ClickCounter.test.tsx
new file mode 100644
index 0000000..a27d2ea
--- /dev/null
+++ b/examples/Redux/ClickCounter.test.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import { render, fireEvent, screen } from '@testing-library/react';
+
+import { ClickCounterBasic } from './ClickCounterBasic';
+
+describe('ClickCounterBasic component test', () => {
+ it('should use custom step when incrementing', async () => {
+ render();
+ const decButton = screen.getByTestId('dec');
+ const incButton = screen.getByTestId('inc');
+ const count = screen.getByTestId('count');
+
+ expect(count).toHaveTextContent('Count: 0');
+
+ fireEvent.click(incButton);
+
+ expect(count).toHaveTextContent('Count: 1');
+
+ fireEvent.click(decButton);
+
+ expect(count).toHaveTextContent('Count: 0');
+ });
+});
diff --git a/examples/Redux/ClickCounterBasic.tsx b/examples/Redux/ClickCounterBasic.tsx
new file mode 100644
index 0000000..123721d
--- /dev/null
+++ b/examples/Redux/ClickCounterBasic.tsx
@@ -0,0 +1,19 @@
+import React, { FC, useReducer } from 'react';
+
+import { reducer, initialState, increment, decrement } from './counter';
+
+export const ClickCounterBasic: FC = () => {
+ const [state, dispatch] = useReducer(reducer, initialState);
+
+ return (
+ <>
+
Count: {state}
+
+
+ >
+ );
+};
diff --git a/examples/Redux/counter.test.ts b/examples/Redux/counter.test.ts
new file mode 100644
index 0000000..731a621
--- /dev/null
+++ b/examples/Redux/counter.test.ts
@@ -0,0 +1,10 @@
+import { initialState, reducer, increment, decrement } from './counter';
+
+describe('Counter redux module test', () => {
+ it('Default init with increment action', async () => {
+ expect(reducer(initialState, increment())).toBe(1);
+ });
+ it('Init with value decrement action', async () => {
+ expect(reducer(10, decrement())).toBe(9);
+ });
+});
diff --git a/examples/Redux/counter.ts b/examples/Redux/counter.ts
new file mode 100644
index 0000000..cb6b481
--- /dev/null
+++ b/examples/Redux/counter.ts
@@ -0,0 +1,22 @@
+interface Action {
+ type: string;
+}
+
+export const initialState = 0;
+
+const INCREMENT = 'examples/Redux/counter/increment';
+const DECREMENT = 'examples/Redux/counter/decrement';
+
+export function reducer(state: number, action: Action): number {
+ switch (action.type) {
+ case INCREMENT:
+ return state + 1;
+ case DECREMENT:
+ return state - 1;
+ default:
+ throw new Error();
+ }
+}
+
+export const increment = (): Action => ({ type: INCREMENT });
+export const decrement = (): Action => ({ type: DECREMENT });
diff --git a/examples/Redux/counterSlice.test.ts b/examples/Redux/counterSlice.test.ts
new file mode 100644
index 0000000..1059edd
--- /dev/null
+++ b/examples/Redux/counterSlice.test.ts
@@ -0,0 +1,10 @@
+import { reducer, actions, initialState } from './counterSlice';
+
+describe('Counter redux module test', () => {
+ it('Default init with increment action', async () => {
+ expect(reducer(initialState, actions.increment())).toBe(1);
+ });
+ it('Init with value decrement action', async () => {
+ expect(reducer(10, actions.decrement())).toBe(9);
+ });
+});
diff --git a/examples/Redux/counterSlice.ts b/examples/Redux/counterSlice.ts
new file mode 100644
index 0000000..fe2cafe
--- /dev/null
+++ b/examples/Redux/counterSlice.ts
@@ -0,0 +1,14 @@
+import { createSlice } from '@reduxjs/toolkit';
+
+export const initialState = 0;
+
+const { reducer, actions } = createSlice({
+ name: 'counter',
+ initialState,
+ reducers: {
+ increment: (state) => state + 1,
+ decrement: (state) => state - 1,
+ },
+});
+
+export { reducer, actions };
diff --git a/package-lock.json b/package-lock.json
index 0abde30..2713d17 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,8 +12,7 @@
"@emotion/styled": "^11.3.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
- "react-router-dom": "^5.3.0",
- "redux": "^4.1.1"
+ "react-router-dom": "^5.3.0"
},
"devDependencies": {
"@babel/core": "^7.15.5",
@@ -21,6 +20,7 @@
"@babel/preset-react": "^7.14.5",
"@babel/preset-typescript": "^7.15.0",
"@emotion/jest": "^11.3.0",
+ "@reduxjs/toolkit": "^1.6.2",
"@storybook/addon-actions": "^6.3.9",
"@storybook/addon-essentials": "^6.3.9",
"@storybook/addon-links": "^6.3.9",
@@ -3349,6 +3349,40 @@
"react-dom": "15.x || 16.x || 16.4.0-alpha.0911da3"
}
},
+ "node_modules/@reduxjs/toolkit": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.6.2.tgz",
+ "integrity": "sha512-HbfI/hOVrAcMGAYsMWxw3UJyIoAS9JTdwddsjlr5w3S50tXhWb+EMyhIw+IAvCVCLETkzdjgH91RjDSYZekVBA==",
+ "dev": true,
+ "dependencies": {
+ "immer": "^9.0.6",
+ "redux": "^4.1.0",
+ "redux-thunk": "^2.3.0",
+ "reselect": "^4.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.14.0 || ^17.0.0",
+ "react-redux": "^7.2.1"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@reduxjs/toolkit/node_modules/immer": {
+ "version": "9.0.6",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.6.tgz",
+ "integrity": "sha512-G95ivKpy+EvVAnAab4fVa4YGYn24J1SpEktnJX7JJ45Bd7xqME/SCplFzYFmTbrkwZbQ4xJK1xMTUYBkN6pWsQ==",
+ "dev": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/immer"
+ }
+ },
"node_modules/@samverschueren/stream-to-observable": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz",
@@ -26174,10 +26208,17 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.1.1.tgz",
"integrity": "sha512-hZQZdDEM25UY2P493kPYuKqviVwZ58lEmGQNeQ+gXa+U0gYPUBf7NKYazbe3m+bs/DzM/ahN12DbF+NG8i0CWw==",
+ "dev": true,
"dependencies": {
"@babel/runtime": "^7.9.2"
}
},
+ "node_modules/redux-thunk": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
+ "integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==",
+ "dev": true
+ },
"node_modules/refractor": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/refractor/-/refractor-3.4.0.tgz",
@@ -26706,6 +26747,12 @@
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
"dev": true
},
+ "node_modules/reselect": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz",
+ "integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==",
+ "dev": true
+ },
"node_modules/resolve": {
"version": "2.0.0-next.3",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz",
@@ -33862,6 +33909,26 @@
"react-lifecycles-compat": "^3.0.4"
}
},
+ "@reduxjs/toolkit": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.6.2.tgz",
+ "integrity": "sha512-HbfI/hOVrAcMGAYsMWxw3UJyIoAS9JTdwddsjlr5w3S50tXhWb+EMyhIw+IAvCVCLETkzdjgH91RjDSYZekVBA==",
+ "dev": true,
+ "requires": {
+ "immer": "^9.0.6",
+ "redux": "^4.1.0",
+ "redux-thunk": "^2.3.0",
+ "reselect": "^4.0.0"
+ },
+ "dependencies": {
+ "immer": {
+ "version": "9.0.6",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.6.tgz",
+ "integrity": "sha512-G95ivKpy+EvVAnAab4fVa4YGYn24J1SpEktnJX7JJ45Bd7xqME/SCplFzYFmTbrkwZbQ4xJK1xMTUYBkN6pWsQ==",
+ "dev": true
+ }
+ }
+ },
"@samverschueren/stream-to-observable": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz",
@@ -51850,10 +51917,17 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.1.1.tgz",
"integrity": "sha512-hZQZdDEM25UY2P493kPYuKqviVwZ58lEmGQNeQ+gXa+U0gYPUBf7NKYazbe3m+bs/DzM/ahN12DbF+NG8i0CWw==",
+ "dev": true,
"requires": {
"@babel/runtime": "^7.9.2"
}
},
+ "redux-thunk": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
+ "integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==",
+ "dev": true
+ },
"refractor": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/refractor/-/refractor-3.4.0.tgz",
@@ -52272,6 +52346,12 @@
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
"dev": true
},
+ "reselect": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz",
+ "integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==",
+ "dev": true
+ },
"resolve": {
"version": "2.0.0-next.3",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz",
diff --git a/package.json b/package.json
index ee3476f..88cbf3f 100644
--- a/package.json
+++ b/package.json
@@ -85,6 +85,6 @@
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^5.3.0",
- "redux": "^4.1.1"
+ "@reduxjs/toolkit": "^1.6.2"
}
}