diff --git a/doc/HooksAPI.xmind b/doc/HooksAPI.xmind new file mode 100644 index 0000000..54bcb1b Binary files /dev/null and b/doc/HooksAPI.xmind differ diff --git a/package.json b/package.json index db6436c..c67b7b5 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,10 @@ "dependencies": { "react": "^16.8.6", "react-dom": "^16.8.6", - "react-scripts": "3.0.1" + "react-redux": "^7.1.0", + "react-router-dom": "^5.0.1", + "react-scripts": "3.0.1", + "redux": "^4.0.4" }, "scripts": { "start": "react-scripts start", @@ -27,5 +30,14 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "jooks": "^0.0.16" + }, + "jest": { + "collectCoverageFrom": [ + "**/*.{js,jsx}", + "!**/node_modules/**" + ] } -} +} \ No newline at end of file diff --git a/src/App.css b/src/App.css deleted file mode 100644 index b41d297..0000000 --- a/src/App.css +++ /dev/null @@ -1,33 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - animation: App-logo-spin infinite 20s linear; - height: 40vmin; - pointer-events: none; -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/src/App.js b/src/App.js index ce9cbd2..eaa0c05 100644 --- a/src/App.js +++ b/src/App.js @@ -1,25 +1,217 @@ -import React from 'react'; -import logo from './logo.svg'; -import './App.css'; +import React from "react"; +import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom"; + +import BasicState from "./useState/BasicState"; +import MultiStates from "./useState/MultiStates"; +import ObjectState from "./useState/ObjectState"; +import InitialState from "./useState/InitialState"; +import SameState from "./useState/SameState"; +import PrevState from './useRef/PrevState'; + +import DidEffect from "./useEffect/DidEffect"; +import UpdateEffect from "./useEffect/UpdateEffect"; +import ConditionEffect from "./useEffect/ConditionEffect"; +import ConditionEffectFromProps from "./useEffect/ConditionEffectFromProps"; +import UnmountEffect from "./useEffect/UnmountEffect"; +import MultiEffects from "./useEffect/MultiEffects"; + +import BasicContext from "./useContext/BasicContext"; +import PropFuncContext from "./useContext/PropFuncContext"; + +import BasicReducer from './useReducer/BasicReducer'; +import InitReducer from './useReducer/InitReducer'; +import SameReducer from './useReducer/SameReducer'; + +import BasicCallback from "./useMemo/BasicCallback"; + +import BasicRef from "./useRef/BasicRef"; +import StaticValueWithRef from "./useRef/StaticValueWithRef"; +import MeasureDOM from "./useRef/MeasureDom"; + +import HOCStepOne from "./customHooks/hoc/StepOne"; +import HOCStepTwo from "./customHooks/hoc/StepTwo"; + +import RenderPropsStepOne from './customHooks/renderProps/StepOne'; +import RenderPropsStepTwo from './customHooks/renderProps/StepTwo'; + +import HooksStepOne from './customHooks/hooks/StepOne'; +import HooksStepTwo from './customHooks/hooks/StepTwo'; + +import AwesomeHooks from './customHooks/awesomeHooks'; +import WithRedux from './customHooks/withLibraries/WithRedux'; function App() { return ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
); } diff --git a/src/App.test.js b/src/App.test.js deleted file mode 100644 index a754b20..0000000 --- a/src/App.test.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import App from './App'; - -it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); -}); diff --git a/src/customHooks/awesomeHooks/__test__/useValue.test.js b/src/customHooks/awesomeHooks/__test__/useValue.test.js new file mode 100644 index 0000000..26a7740 --- /dev/null +++ b/src/customHooks/awesomeHooks/__test__/useValue.test.js @@ -0,0 +1,15 @@ +import init from 'jooks'; +import useValue from '../useValue'; + + +describe('useValue hook', () => { + const jooks = init(() => useValue(3)); + + it('should return the times with additional text', () => { + // Act + const text = jooks.run(); + + // Assert + expect(text).toEqual(`You clicked 3 times`); + }); +}) \ No newline at end of file diff --git a/src/customHooks/awesomeHooks/index.js b/src/customHooks/awesomeHooks/index.js new file mode 100644 index 0000000..241679b --- /dev/null +++ b/src/customHooks/awesomeHooks/index.js @@ -0,0 +1,20 @@ +import React, { useState } from 'react'; +import useValue from './useValue'; +import useFunction from './useFunction'; +import useComponent from './useComponent'; + +const AwesomeHooks = () => { + const [state, setState] = useState(0); + const value = useValue(state); + const action = useFunction(setState); + const Component = useComponent(() => action(state + 1)); + + return ( +
+

Awesome hooks demo

+ +
+ ) +} + +export default AwesomeHooks; \ No newline at end of file diff --git a/src/customHooks/awesomeHooks/useComponent.js b/src/customHooks/awesomeHooks/useComponent.js new file mode 100644 index 0000000..7cdab92 --- /dev/null +++ b/src/customHooks/awesomeHooks/useComponent.js @@ -0,0 +1,11 @@ +import React from 'react'; + +const useComponent = (action) => { + const Component = ({ children }) => ( +
{children}
+ ); + + return Component; +} + +export default useComponent; \ No newline at end of file diff --git a/src/customHooks/awesomeHooks/useFunction.js b/src/customHooks/awesomeHooks/useFunction.js new file mode 100644 index 0000000..9efc9c4 --- /dev/null +++ b/src/customHooks/awesomeHooks/useFunction.js @@ -0,0 +1,6 @@ +const useFunction = action => { + console.log('loaded'); + return action; +} + +export default useFunction; \ No newline at end of file diff --git a/src/customHooks/awesomeHooks/useValue.js b/src/customHooks/awesomeHooks/useValue.js new file mode 100644 index 0000000..f68518e --- /dev/null +++ b/src/customHooks/awesomeHooks/useValue.js @@ -0,0 +1,5 @@ +const useValue = (value) => { + return `You clicked ${value} times`; +} + +export default useValue; \ No newline at end of file diff --git a/src/customHooks/hoc/StepOne.js b/src/customHooks/hoc/StepOne.js new file mode 100644 index 0000000..f1cffe8 --- /dev/null +++ b/src/customHooks/hoc/StepOne.js @@ -0,0 +1,33 @@ +import React from 'react'; +import withHOC from './withHOC'; + +class StepOne extends React.Component { + constructor(...args) { + super(...args); + this.state = { + input: '', + } + } + + render() { + return ( +
+

Step one

+ this.handleOnChange(e)} value={this.state.input} /> + +
+ ) + } + + handleOnChange = (e) => { + this.setState({ input: e.target.value }); + } + + handleOnSubmit = () => { + const payload = { value: this.state.input }; + this.props.onSubmit(payload); + this.props.history.push('/custom-hooks/hoc/step-two'); + } +} + +export default withHOC(StepOne, 'step one'); \ No newline at end of file diff --git a/src/customHooks/hoc/StepTwo.js b/src/customHooks/hoc/StepTwo.js new file mode 100644 index 0000000..a6f1c9a --- /dev/null +++ b/src/customHooks/hoc/StepTwo.js @@ -0,0 +1,34 @@ +import React from 'react'; +import withHOC from './withHOC'; + +class StepTwo extends React.Component { + constructor(...args) { + super(...args); + this.state = { + checked: false, + } + } + + render() { + return ( +
+

Step two

+ + + +
+ ) + } + + handleOnClicked = () => { + this.setState({ checked: !this.state.checked }); + } + + handleOnSubmit = () => { + const payload = { value: this.state.checked ? 'checked' : 'non-checked' }; + this.props.onSubmit(payload); + this.props.history.push('/custom-hooks/hoc/step-one'); + } +} + +export default withHOC(StepTwo, 'step two'); \ No newline at end of file diff --git a/src/customHooks/hoc/withHOC.js b/src/customHooks/hoc/withHOC.js new file mode 100644 index 0000000..cdb6f4c --- /dev/null +++ b/src/customHooks/hoc/withHOC.js @@ -0,0 +1,15 @@ +import React from 'react'; + +function withHOC(WrappedComponent, title) { + return class Component extends React.Component { + render() { + return + } + + handleOnSubmit = payload => { + alert("title: " + title + " \npayload: " + JSON.stringify(payload)); + } + } +} + +export default withHOC; \ No newline at end of file diff --git a/src/customHooks/hooks/StepOne.js b/src/customHooks/hooks/StepOne.js new file mode 100644 index 0000000..fa37c86 --- /dev/null +++ b/src/customHooks/hooks/StepOne.js @@ -0,0 +1,28 @@ +import React, { useState } from 'react'; +import useSubmit from './useSubmit'; + + +const StepOne = ({ history }) => { + const [state, setState] = useState(''); + const onSubmit = useSubmit('step one'); + + const handleOnChange = (e) => { + setState(e.target.value); + } + + const handleOnSubmit = () => { + const payload = { value: state }; + onSubmit(payload); + history.push('/custom-hooks/hooks/step-two'); + } + + return ( +
+

Step one

+ handleOnChange(e)} value={state} /> + +
+ ) +} + +export default StepOne; \ No newline at end of file diff --git a/src/customHooks/hooks/StepTwo.js b/src/customHooks/hooks/StepTwo.js new file mode 100644 index 0000000..e1766cc --- /dev/null +++ b/src/customHooks/hooks/StepTwo.js @@ -0,0 +1,28 @@ +import React, { useState } from 'react'; +import useSubmit from './useSubmit'; + +const StepTwo = ({ history }) => { + const [state, setState] = useState(false); + const onSubmit = useSubmit('step two'); + + const handleOnClicked = () => { + setState(!state.checked); + } + + const handleOnSubmit = () => { + const payload = { value: state.checked ? 'checked' : 'non-checked' }; + onSubmit(payload); + history.push('/custom-hooks/hooks/step-one'); + } + + return ( +
+

Step two

+ + + +
+ ) +} + +export default StepTwo; diff --git a/src/customHooks/hooks/useSubmit.js b/src/customHooks/hooks/useSubmit.js new file mode 100644 index 0000000..e7a63dc --- /dev/null +++ b/src/customHooks/hooks/useSubmit.js @@ -0,0 +1,6 @@ + +const useSubmit = (title) => { + return payload => alert("title: " + title + " \npayload: " + JSON.stringify(payload)); +} + +export default useSubmit; \ No newline at end of file diff --git a/src/customHooks/renderProps/FormWrapper.js b/src/customHooks/renderProps/FormWrapper.js new file mode 100644 index 0000000..43928d3 --- /dev/null +++ b/src/customHooks/renderProps/FormWrapper.js @@ -0,0 +1,14 @@ + +import React from 'react'; + +class FormWrapper extends React.Component { + render() { + return this.props.children(this.handleOnSubmit) + } + + handleOnSubmit = payload => { + alert("title: " + this.props.title + " \npayload: " + JSON.stringify(payload)); + } +} + +export default FormWrapper; \ No newline at end of file diff --git a/src/customHooks/renderProps/StepOne.js b/src/customHooks/renderProps/StepOne.js new file mode 100644 index 0000000..6b8beb1 --- /dev/null +++ b/src/customHooks/renderProps/StepOne.js @@ -0,0 +1,37 @@ +import React from 'react'; +import FormWrapper from './FormWrapper'; + +class StepOne extends React.Component { + constructor(...args) { + super(...args); + this.state = { + input: '', + } + } + + render() { + return ( + + {onSubmit => ( +
this.handleOnSubmit(onSubmit)}> +

Step one

+ this.handleOnChange(e)} value={this.state.input} /> + +
+ )} +
+ ) + } + + handleOnChange = (e) => { + this.setState({ input: e.target.value }); + } + + handleOnSubmit = (onSubmit) => { + const payload = { value: this.state.input }; + onSubmit(payload); + this.props.history.push('/custom-hooks/render-props/step-two'); + } +} + +export default StepOne; \ No newline at end of file diff --git a/src/customHooks/renderProps/StepTwo.js b/src/customHooks/renderProps/StepTwo.js new file mode 100644 index 0000000..1268bc5 --- /dev/null +++ b/src/customHooks/renderProps/StepTwo.js @@ -0,0 +1,38 @@ +import React from 'react'; +import FormWrapper from './FormWrapper'; + +class StepTwo extends React.Component { + constructor(...args) { + super(...args); + this.state = { + checked: false, + } + } + + render() { + return ( + + {onSubmit => ( +
this.handleOnSubmit(onSubmit)}> +

Step two

+ + + +
+ )} +
+ ) + } + + handleOnClicked = () => { + this.setState({ checked: !this.state.checked }); + } + + handleOnSubmit = (onSubmit) => { + const payload = { value: this.state.checked ? 'checked' : 'non-checked' }; + onSubmit(payload); + this.props.history.push('/custom-hooks/render-props/step-one'); + } +} + +export default StepTwo; \ No newline at end of file diff --git a/src/index.css b/src/index.css deleted file mode 100644 index 4a1df4d..0000000 --- a/src/index.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", - "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", - monospace; -} diff --git a/src/index.js b/src/index.js index 87d1be5..b597a44 100644 --- a/src/index.js +++ b/src/index.js @@ -1,12 +1,5 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import './index.css'; import App from './App'; -import * as serviceWorker from './serviceWorker'; ReactDOM.render(, document.getElementById('root')); - -// If you want your app to work offline and load faster, you can change -// unregister() to register() below. Note this comes with some pitfalls. -// Learn more about service workers: https://bit.ly/CRA-PWA -serviceWorker.unregister(); diff --git a/src/logo.svg b/src/logo.svg deleted file mode 100644 index 6b60c10..0000000 --- a/src/logo.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/serviceWorker.js b/src/serviceWorker.js deleted file mode 100644 index f8c7e50..0000000 --- a/src/serviceWorker.js +++ /dev/null @@ -1,135 +0,0 @@ -// This optional code is used to register a service worker. -// register() is not called by default. - -// This lets the app load faster on subsequent visits in production, and gives -// it offline capabilities. However, it also means that developers (and users) -// will only see deployed updates on subsequent visits to a page, after all the -// existing tabs open on the page have been closed, since previously cached -// resources are updated in the background. - -// To learn more about the benefits of this model and instructions on how to -// opt-in, read https://bit.ly/CRA-PWA - -const isLocalhost = Boolean( - window.location.hostname === 'localhost' || - // [::1] is the IPv6 localhost address. - window.location.hostname === '[::1]' || - // 127.0.0.1/8 is considered localhost for IPv4. - window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ - ) -); - -export function register(config) { - if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { - // The URL constructor is available in all browsers that support SW. - const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); - if (publicUrl.origin !== window.location.origin) { - // Our service worker won't work if PUBLIC_URL is on a different origin - // from what our page is served on. This might happen if a CDN is used to - // serve assets; see https://github.com/facebook/create-react-app/issues/2374 - return; - } - - window.addEventListener('load', () => { - const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; - - if (isLocalhost) { - // This is running on localhost. Let's check if a service worker still exists or not. - checkValidServiceWorker(swUrl, config); - - // Add some additional logging to localhost, pointing developers to the - // service worker/PWA documentation. - navigator.serviceWorker.ready.then(() => { - console.log( - 'This web app is being served cache-first by a service ' + - 'worker. To learn more, visit https://bit.ly/CRA-PWA' - ); - }); - } else { - // Is not localhost. Just register service worker - registerValidSW(swUrl, config); - } - }); - } -} - -function registerValidSW(swUrl, config) { - navigator.serviceWorker - .register(swUrl) - .then(registration => { - registration.onupdatefound = () => { - const installingWorker = registration.installing; - if (installingWorker == null) { - return; - } - installingWorker.onstatechange = () => { - if (installingWorker.state === 'installed') { - if (navigator.serviceWorker.controller) { - // At this point, the updated precached content has been fetched, - // but the previous service worker will still serve the older - // content until all client tabs are closed. - console.log( - 'New content is available and will be used when all ' + - 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' - ); - - // Execute callback - if (config && config.onUpdate) { - config.onUpdate(registration); - } - } else { - // At this point, everything has been precached. - // It's the perfect time to display a - // "Content is cached for offline use." message. - console.log('Content is cached for offline use.'); - - // Execute callback - if (config && config.onSuccess) { - config.onSuccess(registration); - } - } - } - }; - }; - }) - .catch(error => { - console.error('Error during service worker registration:', error); - }); -} - -function checkValidServiceWorker(swUrl, config) { - // Check if the service worker can be found. If it can't reload the page. - fetch(swUrl) - .then(response => { - // Ensure service worker exists, and that we really are getting a JS file. - const contentType = response.headers.get('content-type'); - if ( - response.status === 404 || - (contentType != null && contentType.indexOf('javascript') === -1) - ) { - // No service worker found. Probably a different app. Reload the page. - navigator.serviceWorker.ready.then(registration => { - registration.unregister().then(() => { - window.location.reload(); - }); - }); - } else { - // Service worker found. Proceed as normal. - registerValidSW(swUrl, config); - } - }) - .catch(() => { - console.log( - 'No internet connection found. App is running in offline mode.' - ); - }); -} - -export function unregister() { - if ('serviceWorker' in navigator) { - navigator.serviceWorker.ready.then(registration => { - registration.unregister(); - }); - } -} diff --git a/src/useContext/BasicContext.js b/src/useContext/BasicContext.js new file mode 100644 index 0000000..552681b --- /dev/null +++ b/src/useContext/BasicContext.js @@ -0,0 +1,42 @@ +import React, { useState, useContext } from "react"; +import { MyContext } from "./theme-context"; + +const AnotherButton = ({ changeColor }) => { + const color = useContext(MyContext); + return ( + + ); +}; + +const ThemedButton = ({ changeColor }) => { + const color = useContext(MyContext); + return ( +
+ +
+
+ +
+ ); +}; +const BasicContext = () => { + const [color, setColor] = useState("blue"); + + return ( + + + + ); +}; + +export default BasicContext; diff --git a/src/useContext/PropFuncContext.js b/src/useContext/PropFuncContext.js new file mode 100644 index 0000000..007e1e2 --- /dev/null +++ b/src/useContext/PropFuncContext.js @@ -0,0 +1,42 @@ +import React, { useState, useContext } from "react"; +import { MyContext } from "./theme-context"; + +const AnotherButton = () => { + const { color, setColor } = useContext(MyContext); + return ( + + ); +}; + +const ThemedButton = () => { + const { color, setColor } = useContext(MyContext); + return ( +
+ +
+
+ +
+ ); +}; +const PropFuncContext = () => { + const [color, setColor] = useState("blue"); + + return ( + + + + ); +}; + +export default PropFuncContext; diff --git a/src/useContext/theme-context.js b/src/useContext/theme-context.js new file mode 100644 index 0000000..c635d37 --- /dev/null +++ b/src/useContext/theme-context.js @@ -0,0 +1,3 @@ +import React from "react"; + +export const MyContext = React.createContext(); diff --git a/src/useEffect/ConditionEffect.js b/src/useEffect/ConditionEffect.js new file mode 100644 index 0000000..68ee173 --- /dev/null +++ b/src/useEffect/ConditionEffect.js @@ -0,0 +1,25 @@ +import React, { useState, useEffect } from 'react'; + +const ConditionEffect = () => { + const [fruit, setFruit] = useState(0); + const [vegetable, setVegetable] = useState(1); + + useEffect(() => { + alert(`you clicked ${fruit} times`); + }, [fruit]); + + return ( +
+

Condition Effect example

+

You clicked fruit {fruit} times

+ +

You clicked vegetable {vegetable} times

+ +
); +} + +export default ConditionEffect; \ No newline at end of file diff --git a/src/useEffect/ConditionEffectFromProps.js b/src/useEffect/ConditionEffectFromProps.js new file mode 100644 index 0000000..556fa70 --- /dev/null +++ b/src/useEffect/ConditionEffectFromProps.js @@ -0,0 +1,29 @@ +import React, { useState, useEffect } from 'react'; + +const EffectItem = ({ fruit }) => { + useEffect(() => { + alert(`you clicked ${fruit} times`); + }, [fruit]); + + return

You clicked fruit {fruit} times

; +} + +const ConditionEffectFromProps = () => { + const [fruit, setFruit] = useState(0); + const [vegetable, setVegetable] = useState(1); + + return ( +
+

Condition Effect From Props example

+ + +

You clicked vegetable {vegetable} times

+ +
); +} + +export default ConditionEffectFromProps; \ No newline at end of file diff --git a/src/useEffect/DidEffect.js b/src/useEffect/DidEffect.js new file mode 100644 index 0000000..794e4b7 --- /dev/null +++ b/src/useEffect/DidEffect.js @@ -0,0 +1,21 @@ +import React, { useState, useEffect } from 'react'; + +const DidEffect = () => { + const [count, setCount] = useState(0); + + useEffect(() => { + alert(`page loaded`); + }, []); + + + return ( +
+

Did effect example

+

You clicked {count} times

+ +
); +} + +export default DidEffect; \ No newline at end of file diff --git a/src/useEffect/MultiEffects.js b/src/useEffect/MultiEffects.js new file mode 100644 index 0000000..48260b1 --- /dev/null +++ b/src/useEffect/MultiEffects.js @@ -0,0 +1,25 @@ +import React, { useState, useEffect } from "react"; + +const MultiEffects = () => { + const [count, setCount] = useState(0); + + useEffect(() => { + alert(`page loaded 1`); + }); + useEffect(() => { + alert(`page loaded 2`); + }); + useEffect(() => { + alert(`page loaded 3`); + }); + + return ( +
+

Basic effect example

+

You clicked {count} times

+ +
+ ); +}; + +export default MultiEffects; diff --git a/src/useEffect/UnmountEffect.js b/src/useEffect/UnmountEffect.js new file mode 100644 index 0000000..af8d08b --- /dev/null +++ b/src/useEffect/UnmountEffect.js @@ -0,0 +1,15 @@ +import React, { useEffect } from "react"; + +const UnmountEffect = () => { + useEffect(() => { + return () => alert("page is about to unmount"); + }); + + return ( +
+

Unmount state example

+
+ ); +}; + +export default UnmountEffect; diff --git a/src/useEffect/UpdateEffect.js b/src/useEffect/UpdateEffect.js new file mode 100644 index 0000000..9633c79 --- /dev/null +++ b/src/useEffect/UpdateEffect.js @@ -0,0 +1,22 @@ +import React, { useState, useEffect } from 'react'; + +const UpdateEffect = () => { + const [count, setCount] = useState(0); + + useEffect(() => { + document.title = `You clicked ${count} times`; + }); + + + return ( +
+

Basic effect example

+

You clicked {count} times

+ +
+ ); +} + +export default UpdateEffect; \ No newline at end of file diff --git a/src/useEffect/__test__/updataEffect.test.js b/src/useEffect/__test__/updataEffect.test.js new file mode 100644 index 0000000..a350b27 --- /dev/null +++ b/src/useEffect/__test__/updataEffect.test.js @@ -0,0 +1,34 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { act } from 'react-dom/test-utils'; +import UpdateEffect from '../UpdateEffect'; + +let container; + +beforeEach(() => { + container = document.createElement('div'); + document.body.appendChild(container); +}); + +afterEach(() => { + document.body.removeChild(container); + container = null; +}); + +it('can render and update a counter', () => { + // 测试首次渲染和 effect + act(() => { + ReactDOM.render(, container); + }); + const button = container.querySelector('button'); + const label = container.querySelector('p'); + expect(label.textContent).toBe('You clicked 0 times'); + expect(document.title).toBe('You clicked 0 times'); + + // 测试第二次渲染和 effect + act(() => { + button.dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + expect(label.textContent).toBe('You clicked 1 times'); + expect(document.title).toBe('You clicked 1 times'); +}); \ No newline at end of file diff --git a/src/useMemo/BasicCallback.js b/src/useMemo/BasicCallback.js new file mode 100644 index 0000000..efcd340 --- /dev/null +++ b/src/useMemo/BasicCallback.js @@ -0,0 +1,23 @@ +import React, { useState, useCallback, useMemo } from 'react'; + +const BasicCallback = () => { + const [state, changeState] = useState({}); + const memorizedValue = useMemo(() => Math.random(), []); + const memorizedCallback = useCallback(() => console.log(memorizedValue), [memorizedValue]); + const unMemorizedCallback = () => console.log(memorizedValue); + const { prevMemorizedCallback, prevUnMemorizedCallback } = state; + + return ( +
+

Memorized value: {memorizedValue}

+

New update {Math.random()}

+

is prevMemorizedCallback === to memorizedCallback: {String(prevMemorizedCallback === memorizedCallback)}

+

is prevUnMemorizedCallback === to unMemorizedCallback: {String(prevUnMemorizedCallback === unMemorizedCallback)}

+

+

+

+
+ ); +}; + +export default BasicCallback; \ No newline at end of file diff --git a/src/useReducer/BasicReducer.js b/src/useReducer/BasicReducer.js new file mode 100644 index 0000000..de21e73 --- /dev/null +++ b/src/useReducer/BasicReducer.js @@ -0,0 +1,28 @@ +import React, { useReducer } from 'react'; + +const initialState = { count: 0 }; + +const reducer = (state, action) => { + switch (action.type) { + case 'increment': + return { count: state.count + 1 }; + case 'decrement': + return { count: state.count - 1 }; + default: + throw new Error(); + } +} + +const BasicReducer = () => { + const [state, dispatch] = useReducer(reducer, initialState); + return ( +
+

Basic reducer demo

+ Count: {state.count} + + +
+ ); +} + +export default BasicReducer; \ No newline at end of file diff --git a/src/useReducer/InitReducer.js b/src/useReducer/InitReducer.js new file mode 100644 index 0000000..0635433 --- /dev/null +++ b/src/useReducer/InitReducer.js @@ -0,0 +1,39 @@ +import React, { useReducer } from 'react'; + +const init = (initialCount) => { + return initialCount; +} + +const initialCount = { count: 0 }; + +const reducer = (state, action) => { + switch (action.type) { + case 'increment': + return { count: state.count + 1 }; + case 'decrement': + return { count: state.count - 1 }; + case 'reset': + return init(action.payload); + default: + throw new Error(); + } +} + +const InitReducer = () => { + const [state, dispatch] = useReducer(reducer, initialCount, init); + return ( +
+

Basic reducer demo

+ Count: {state.count} + + + +
+ ); +} + +export default InitReducer; \ No newline at end of file diff --git a/src/useReducer/SameReducer.js b/src/useReducer/SameReducer.js new file mode 100644 index 0000000..c794136 --- /dev/null +++ b/src/useReducer/SameReducer.js @@ -0,0 +1,32 @@ +import React, { useReducer, useEffect } from 'react'; + +const initialState = { count: 0 }; + +const reducer = (state, action) => { + switch (action.type) { + case 'increment': + return { count: state.count + 1 }; + case 'decrement': + return { count: state.count - 1 }; + case 'sameState': + return state; + default: + throw new Error(); + } +} + +const SameReducer = () => { + const [state, dispatch] = useReducer(reducer, initialState); + useEffect(() => alert('page reloaded')); + return ( +
+

same reducer demo

+ Count: {state.count} + + + +
+ ); +} + +export default SameReducer; \ No newline at end of file diff --git a/src/useRef/BasicRef.js b/src/useRef/BasicRef.js new file mode 100644 index 0000000..422ff53 --- /dev/null +++ b/src/useRef/BasicRef.js @@ -0,0 +1,18 @@ +import React, { useRef } from 'react'; + +const BasicRef = () => { + const inputEl = useRef(null); + const onButtonClick = () => { + inputEl.current.focus(); + }; + console.log('inputEl: ', inputEl); + + return ( +
+ + +
+ ); +} + +export default BasicRef; \ No newline at end of file diff --git a/src/useRef/MeasureDom.js b/src/useRef/MeasureDom.js new file mode 100644 index 0000000..7eff665 --- /dev/null +++ b/src/useRef/MeasureDom.js @@ -0,0 +1,24 @@ +import React, { useRef, useState, useCallback } from 'react'; + +function MeasureDOM() { + const [height, setHeight] = useState(0); + const domRef = useRef(); + + const measuredRef = useCallback(node => { + if (node !== null) { + setHeight(node.getBoundingClientRect().height); + } + }, []); + + return ( +
+

Hello, world

+

The above header is {Math.round(height)}px tall

+

Hello

+

height: {domRef.current && domRef.current.style.height}

+

width: {domRef.current && domRef.current.style.width}

+
+ ); +} + +export default MeasureDOM; \ No newline at end of file diff --git a/src/useRef/PrevState.js b/src/useRef/PrevState.js new file mode 100644 index 0000000..1d1744b --- /dev/null +++ b/src/useRef/PrevState.js @@ -0,0 +1,22 @@ +import React, { useState, useRef, useEffect } from 'react'; + +const PrevState = () => { + const [count, setCount] = useState(0); + const prevCountRef = useRef(); + useEffect(() => { + prevCountRef.current = count; + }); + const prevCount = prevCountRef.current; + + return ( +
+

previous state example

+

You clicked {count} times

+ +

Now: {count}, before: {prevCount}

+
); +} + +export default PrevState; \ No newline at end of file diff --git a/src/useRef/StaticValueWithRef.js b/src/useRef/StaticValueWithRef.js new file mode 100644 index 0000000..6e9b658 --- /dev/null +++ b/src/useRef/StaticValueWithRef.js @@ -0,0 +1,28 @@ +import React, { useState, useEffect, useRef } from 'react'; + +const StaticValueWithRef = () => { + const [state, setState] = useState(0); + const intervalRef = useRef(); + + useEffect(() => { + intervalRef.current = setInterval(() => { + setState(state + 1); + }, 30); + + return () => { + clearInterval(intervalRef.current); + }; + }); + + return
+
{state}
+
+ +
+
+} + +export default StaticValueWithRef; \ No newline at end of file diff --git a/src/useState/BasicState.js b/src/useState/BasicState.js new file mode 100644 index 0000000..be1c536 --- /dev/null +++ b/src/useState/BasicState.js @@ -0,0 +1,16 @@ +import React, { useState } from 'react'; + +const BasicState = () => { + const [count, setCount] = useState(0); + + return ( +
+

Basic state example

+

You clicked {count} times

+ +
); +} + +export default BasicState; \ No newline at end of file diff --git a/src/useState/InitialState.js b/src/useState/InitialState.js new file mode 100644 index 0000000..7471f29 --- /dev/null +++ b/src/useState/InitialState.js @@ -0,0 +1,18 @@ +import React, { useState } from "react"; + +const InitialState = () => { + const [count, setCount] = useState(() => { + alert("page loaded"); + return 0; + }); + + return ( +
+

Initial state example

+

You clicked {count} times

+ +
+ ); +}; + +export default InitialState; diff --git a/src/useState/MultiStates.js b/src/useState/MultiStates.js new file mode 100644 index 0000000..b2b8c33 --- /dev/null +++ b/src/useState/MultiStates.js @@ -0,0 +1,26 @@ +import React, { useState } from 'react'; + +const MultiStateExample = () => { + const [fruit, setFruit] = useState(0); + const [vegetable, setVegetable] = useState(1); + const [cereals, setCereals] = useState(2); + + return ( +
+

Multi state example

+

You clicked fruit {fruit} times

+ +

You clicked vegetable {vegetable} times

+ +

You clicked cereals {cereals} times

+ +
); +} + +export default MultiStateExample; \ No newline at end of file diff --git a/src/useState/ObjectState.js b/src/useState/ObjectState.js new file mode 100644 index 0000000..128af75 --- /dev/null +++ b/src/useState/ObjectState.js @@ -0,0 +1,21 @@ +import React, { useState } from "react"; + +const ObjectState = () => { + const [state, setState] = useState({ fruit: 0, vegetable: 1 }); + + return ( +
+

Multi state example

+

You clicked fruit {state.fruit} times

+ +

You clicked vegetable {state.vegetable} times

+ +
+ ); +}; + +export default ObjectState; diff --git a/src/useState/SameState.js b/src/useState/SameState.js new file mode 100644 index 0000000..294e5a3 --- /dev/null +++ b/src/useState/SameState.js @@ -0,0 +1,27 @@ +import React, { useState, useEffect } from "react"; + +const EffectItem = () => { + useEffect(() => { + alert(`component loaded`); + }); + + return

You clicked fruit

; +}; + +const SameState = () => { + const [fruit, setFruit] = useState(0); + + useEffect(() => { + alert(`page loaded`); + }); + + return ( +
+

Multi state example

+ + +
+ ); +}; + +export default SameState; diff --git a/yarn.lock b/yarn.lock index 25b6936..e558886 100644 --- a/yarn.lock +++ b/yarn.lock @@ -859,6 +859,20 @@ dependencies: regenerator-runtime "^0.13.2" +"@babel/runtime@^7.1.2", "@babel/runtime@^7.4.0": + version "7.5.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.4.tgz#cb7d1ad7c6d65676e66b47186577930465b5271b" + integrity sha512-Na84uwyImZZc3FKf4aUF1tysApzwf3p2yuFBIyBfbzT5glzKTdvYI4KVW4kcgjrzoGUjC7w3YyCHcJKaRxsr2Q== + dependencies: + regenerator-runtime "^0.13.2" + +"@babel/runtime@^7.4.5": + version "7.5.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132" + integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ== + dependencies: + regenerator-runtime "^0.13.2" + "@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237" @@ -4268,6 +4282,11 @@ growly@^1.3.0: resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= +gud@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" + integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== + gzip-size@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.0.0.tgz#a55ecd99222f4c48fd8c01c625ce3b349d0a0e80" @@ -4422,6 +4441,18 @@ hex-color-regex@^1.1.0: resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== +history@^4.9.0: + version "4.9.0" + resolved "https://registry.yarnpkg.com/history/-/history-4.9.0.tgz#84587c2068039ead8af769e9d6a6860a14fa1bca" + integrity sha512-H2DkjCjXf0Op9OAr6nJ56fcRkTSNrUiv41vNJ6IswJjif6wlpZK0BTfFbi7qK9dXLSYZxkq5lBsj3vUjlYBYZA== + dependencies: + "@babel/runtime" "^7.1.2" + loose-envify "^1.2.0" + resolve-pathname "^2.2.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + value-equal "^0.4.0" + hmac-drbg@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -4431,6 +4462,13 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz#b09178f0122184fb95acf525daaecb4d8f45958b" + integrity sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA== + dependencies: + react-is "^16.7.0" + hosted-git-info@^2.1.4: version "2.7.1" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" @@ -5041,6 +5079,11 @@ is-wsl@^1.1.0: resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -5497,6 +5540,13 @@ jest@24.7.1: import-local "^2.0.0" jest-cli "^24.7.1" +jooks@^0.0.16: + version "0.0.16" + resolved "https://registry.yarnpkg.com/jooks/-/jooks-0.0.16.tgz#0b5c47e3bbf3f6014b7b72a8e8dc3ad3da901006" + integrity sha512-8cYg1y14Raweo1F85rZ8DvAKGYckGn+YGJ+RIpj6njSj4V+Wqbv7qvmxEeDmXO0/S6fJ+e7owFBtIe0Nfbh2sQ== + dependencies: + lodash "^4.17.11" + js-levenshtein@^1.1.3: version "1.1.6" resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" @@ -5876,7 +5926,7 @@ loglevel@^1.4.1: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa" integrity sha1-4PyVEztu8nbNyIh82vJKpvFW+Po= -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -6065,6 +6115,15 @@ mimic-fn@^2.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mini-create-react-context@^0.3.0: + version "0.3.2" + resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz#79fc598f283dd623da8e088b05db8cddab250189" + integrity sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw== + dependencies: + "@babel/runtime" "^7.4.0" + gud "^1.0.0" + tiny-warning "^1.0.2" + mini-css-extract-plugin@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz#ac0059b02b9692515a637115b0cc9fed3a35c7b0" @@ -6813,6 +6872,13 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= +path-to-regexp@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" + integrity sha1-Wf3g9DW62suhA6hOnTvGTpa5k30= + dependencies: + isarray "0.0.1" + path-type@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" @@ -7645,7 +7711,7 @@ prompts@^2.0.1: kleur "^3.0.2" sisteransi "^1.0.0" -prop-types@^15.6.2: +prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -7846,7 +7912,7 @@ react-dev-utils@^9.0.1: strip-ansi "5.2.0" text-table "0.2.0" -react-dom@16.8.6: +react-dom@^16.8.6: version "16.8.6" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.6.tgz#71d6303f631e8b0097f56165ef608f051ff6e10f" integrity sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA== @@ -7861,11 +7927,52 @@ react-error-overlay@^5.1.6: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-5.1.6.tgz#0cd73407c5d141f9638ae1e0c63e7b2bf7e9929d" integrity sha512-X1Y+0jR47ImDVr54Ab6V9eGk0Hnu7fVWGeHQSOXHf/C2pF9c6uy3gef8QUeuUiWlNb0i08InPSE5a/KJzNzw1Q== -react-is@^16.8.1, react-is@^16.8.4: +react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: version "16.8.6" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA== +react-redux@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.1.0.tgz#72af7cf490a74acdc516ea9c1dd80e25af9ea0b2" + integrity sha512-hyu/PoFK3vZgdLTg9ozbt7WF3GgX5+Yn3pZm5/96/o4UueXA+zj08aiSC9Mfj2WtD1bvpIb3C5yvskzZySzzaw== + dependencies: + "@babel/runtime" "^7.4.5" + hoist-non-react-statics "^3.3.0" + invariant "^2.2.4" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^16.8.6" + +react-router-dom@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.0.1.tgz#ee66f4a5d18b6089c361958e443489d6bab714be" + integrity sha512-zaVHSy7NN0G91/Bz9GD4owex5+eop+KvgbxXsP/O+iW1/Ln+BrJ8QiIR5a6xNPtrdTvLkxqlDClx13QO1uB8CA== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + loose-envify "^1.3.1" + prop-types "^15.6.2" + react-router "5.0.1" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react-router@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.0.1.tgz#04ee77df1d1ab6cb8939f9f01ad5702dbadb8b0f" + integrity sha512-EM7suCPNKb1NxcTZ2LEOWFtQBQRQXecLxVpdsP4DW4PbbqYWeRiLyV/Tt1SdCrvT2jcyXAXmVTmzvSzrPR63Bg== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + hoist-non-react-statics "^3.1.0" + loose-envify "^1.3.1" + mini-create-react-context "^0.3.0" + path-to-regexp "^1.7.0" + prop-types "^15.6.2" + react-is "^16.6.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + react-scripts@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-3.0.1.tgz#e5565350d8069cc9966b5998d3fe3befe3d243ac" @@ -7926,7 +8033,7 @@ react-scripts@3.0.1: optionalDependencies: fsevents "2.0.6" -react@16.8.6: +react@^16.8.6: version "16.8.6" resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe" integrity sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw== @@ -8015,6 +8122,14 @@ recursive-readdir@2.2.2: dependencies: minimatch "3.0.4" +redux@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.4.tgz#4ee1aeb164b63d6a1bcc57ae4aa0b6e6fa7a3796" + integrity sha512-vKv4WdiJxOWKxK0yRoaK3Y4pxxB0ilzVx6dszU2W8wLxlb2yikRph4iV/ymtdJ6ZxpBLFbyrxklnT5yBbQSl3Q== + dependencies: + loose-envify "^1.4.0" + symbol-observable "^1.2.0" + regenerate-unicode-properties@^8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.0.2.tgz#7b38faa296252376d363558cfbda90c9ce709662" @@ -8220,6 +8335,11 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve-pathname@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879" + integrity sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg== + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -8979,6 +9099,11 @@ svgo@^1.0.0, svgo@^1.2.1: unquote "~1.1.1" util.promisify "~1.0.0" +symbol-observable@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== + symbol-tree@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" @@ -9085,6 +9210,16 @@ timsort@^0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= +tiny-invariant@^1.0.2: + version "1.0.6" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.6.tgz#b3f9b38835e36a41c843a3b0907a5a7b3755de73" + integrity sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA== + +tiny-warning@^1.0.0, tiny-warning@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -9443,6 +9578,11 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +value-equal@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7" + integrity sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw== + vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"