Comparing version 0.1.8 to 0.1.9
@@ -6,4 +6,3 @@ "use strict"; | ||
typeof document.visibilityState !== 'undefined') { | ||
return (document.visibilityState === 'visible' || | ||
document.visibilityState === 'prerender'); | ||
return document.visibilityState !== 'hidden'; | ||
} | ||
@@ -10,0 +9,0 @@ // always assume it's visible |
@@ -22,6 +22,6 @@ export interface ConfigInterface<Data = any, Error = any> { | ||
declare type keyFunction = () => string; | ||
export declare type keyInterface = string | keyFunction; | ||
export declare type keyInterface = string | keyFunction | any[] | null; | ||
export declare type updaterInterface<Data = any, Error = any> = (shouldRevalidate?: boolean, data?: Data, error?: Error) => boolean | Promise<boolean>; | ||
export declare type triggerInterface = (key: string, shouldRevalidate?: boolean) => void; | ||
export declare type mutateInterface<Data = any> = (key: string, data: Data, shouldRevalidate?: boolean) => void; | ||
export declare type triggerInterface = (key: keyInterface, shouldRevalidate?: boolean) => void; | ||
export declare type mutateInterface<Data = any> = (key: keyInterface, data: Data, shouldRevalidate?: boolean) => void; | ||
export declare type broadcastStateInterface<Data = any, Error = any> = (key: string, data: Data, error?: Error) => void; | ||
@@ -41,3 +41,3 @@ export declare type responseInterface<Data, Error> = { | ||
export declare type pageComponentType<Offset, Data, Error> = (props: pagesPropsInterface<Offset, Data, Error>) => any; | ||
export declare type pageOffsetMapperType<Offset, Data, Error> = (data: any, pageSWRs: responseInterface<Data, Error>[]) => Offset; | ||
export declare type pageOffsetMapperType<Offset, Data, Error> = (SWR: responseInterface<Data, Error>, index: number) => Offset; | ||
export declare type pagesResponseInterface = { | ||
@@ -44,0 +44,0 @@ pages: any; |
import { pagesResponseInterface, pageComponentType, pageOffsetMapperType } from './types'; | ||
export declare function useSWRPages<OffsetType, Data, Error>(pageKey: string, pageFn: pageComponentType<OffsetType, Data, Error>, swrDataToOffset: pageOffsetMapperType<OffsetType, Data, Error>, deps?: any[]): pagesResponseInterface; | ||
export declare function useSWRPages<OffsetType, Data, Error>(pageKey: string, pageFn: pageComponentType<OffsetType, Data, Error>, SWRToOffset: pageOffsetMapperType<OffsetType, Data, Error>, deps?: any[]): pagesResponseInterface; |
@@ -91,3 +91,3 @@ "use strict"; | ||
*/ | ||
function useSWRPages(pageKey, pageFn, swrDataToOffset, deps = []) { | ||
function useSWRPages(pageKey, pageFn, SWRToOffset, deps = []) { | ||
const pageCountKey = `_swr_page_count_` + pageKey; | ||
@@ -139,16 +139,16 @@ const pageOffsetKey = `_swr_page_offset_` + pageKey; | ||
_swrs[id] = swr; | ||
if (typeof swr.data !== 'undefined') { | ||
// set next page's offset | ||
const newPageOffset = swrDataToOffset(swr.data, _swrs); | ||
if (pageOffsets[id + 1] !== newPageOffset) { | ||
setPageOffsets(arr => { | ||
const _arr = [...arr]; | ||
_arr[id + 1] = newPageOffset; | ||
config_1.cacheSet(pageOffsetKey, _arr); | ||
return _arr; | ||
}); | ||
} | ||
} | ||
return _swrs; | ||
}); | ||
if (typeof swr.data !== 'undefined') { | ||
// set next page's offset | ||
const newPageOffset = SWRToOffset(swr, id); | ||
if (pageOffsets[id + 1] !== newPageOffset) { | ||
setPageOffsets(arr => { | ||
const _arr = [...arr]; | ||
_arr[id + 1] = newPageOffset; | ||
config_1.cacheSet(pageOffsetKey, _arr); | ||
return _arr; | ||
}); | ||
} | ||
} | ||
} | ||
@@ -155,0 +155,0 @@ return swr; |
@@ -15,3 +15,3 @@ "use strict"; | ||
const reactBatchedUpdates_1 = require("./libs/reactBatchedUpdates"); | ||
const lodash_throttle_1 = __importDefault(require("lodash.throttle")); | ||
const throttle_1 = __importDefault(require("lodash/throttle")); | ||
const fast_deep_equal_1 = __importDefault(require("fast-deep-equal")); | ||
@@ -22,6 +22,33 @@ const config_1 = __importStar(require("./config")); | ||
const use_hydration_1 = __importDefault(require("./libs/use-hydration")); | ||
const hash_1 = __importDefault(require("./libs/hash")); | ||
const IS_SERVER = typeof window === 'undefined'; | ||
// TODO: introduce namepsace for the cache | ||
const getErrorKey = key => (key ? 'err@' + key : ''); | ||
const trigger = (key, shouldRevalidate = true) => { | ||
const getKeyArgs = _key => { | ||
let key; | ||
let args = null; | ||
if (typeof _key === 'function') { | ||
try { | ||
key = _key(); | ||
} | ||
catch (err) { | ||
// dependencies not ready | ||
key = ''; | ||
} | ||
} | ||
else if (Array.isArray(_key)) { | ||
// args array | ||
key = hash_1.default(_key); | ||
args = _key; | ||
} | ||
else { | ||
// convert null to '' | ||
key = String(_key || ''); | ||
} | ||
return [key, args]; | ||
}; | ||
const trigger = (_key, shouldRevalidate = true) => { | ||
const [key] = getKeyArgs(_key); | ||
if (!key) | ||
return; | ||
const updaters = config_1.CACHE_REVALIDATORS[key]; | ||
@@ -45,3 +72,4 @@ if (key && updaters) { | ||
}; | ||
const mutate = (key, data, shouldRevalidate = true) => { | ||
const mutate = (_key, data, shouldRevalidate = true) => { | ||
const [key] = getKeyArgs(_key); | ||
if (!key) | ||
@@ -79,16 +107,3 @@ return; | ||
// (because `revalidate` only depends on `key`) | ||
let key; | ||
if (typeof _key === 'function') { | ||
try { | ||
key = _key(); | ||
} | ||
catch (err) { | ||
// dependencies not ready | ||
key = ''; | ||
} | ||
} | ||
else { | ||
// convert null to '' | ||
key = String(_key || ''); | ||
} | ||
const [key, fnArgs] = getKeyArgs(_key); | ||
// `keyErr` is the cache key for error objects | ||
@@ -143,3 +158,8 @@ const keyErr = getErrorKey(key); | ||
} | ||
config_1.CONCURRENT_PROMISES[key] = fn(key); | ||
if (fnArgs !== null) { | ||
config_1.CONCURRENT_PROMISES[key] = fn(...fnArgs); | ||
} | ||
else { | ||
config_1.CONCURRENT_PROMISES[key] = fn(key); | ||
} | ||
config_1.CONCURRENT_PROMISES_TS[key] = startAt = Date.now(); | ||
@@ -257,3 +277,3 @@ setTimeout(() => { | ||
// and tabs being switched quickly | ||
onFocus = lodash_throttle_1.default(softRevalidate, config.focusThrottleInterval); | ||
onFocus = throttle_1.default(softRevalidate, config.focusThrottleInterval); | ||
if (!config_1.FOCUS_REVALIDATORS[key]) { | ||
@@ -260,0 +280,0 @@ config_1.FOCUS_REVALIDATORS[key] = [onFocus]; |
{ | ||
"name": "swr", | ||
"version": "0.1.8", | ||
"version": "0.1.9", | ||
"description": "React Hooks library for remote data fetching", | ||
@@ -38,3 +38,3 @@ "main": "./dist/index.js", | ||
"@types/jest": "24.0.20", | ||
"@types/lodash.throttle": "4.1.6", | ||
"@types/lodash": "4.14.145", | ||
"@types/node": "11.12.0", | ||
@@ -58,3 +58,3 @@ "@types/react": "16.9.11", | ||
"fast-deep-equal": "2.0.1", | ||
"lodash.throttle": "4.1.1", | ||
"lodash": "4.17.15", | ||
"ms": "2.1.2" | ||
@@ -61,0 +61,0 @@ }, |
357
README.md
[![SWR](https://assets.zeit.co/image/upload/v1572289618/swr/banner.png)](https://swr.now.sh) | ||
<p align="center"> | ||
<a aria-label="SWR website" href="https://swr.now.sh">swr.now.sh<a> | ||
</p> | ||
<p align="center"> | ||
<a aria-label="ZEIT logo" href="https://github.com/zeit"> | ||
@@ -22,7 +18,9 @@ <img src="https://img.shields.io/badge/MADE%20BY%20ZEIT-000000.svg?logo=ZEIT&labelColor=000000&logoWidth=12"> | ||
## Intro | ||
## Introduction | ||
[swr.now.sh](https://swr.now.sh) | ||
SWR is a React Hooks library for remote data fetching. | ||
The name “**SWR**” is derived from `stale-while-revalidate`, a HTTP cache invalidation strategy popularized by [RFC 5861](https://tools.ietf.org/html/rfc5861). | ||
The name “**SWR**” is derived from `stale-while-revalidate`, a HTTP cache invalidation strategy popularized by [RFC 5861](https://tools.ietf.org/html/rfc5861). | ||
**SWR** first returns the data from cache (stale), then sends the fetch request (revalidate), and finally comes with the up-to-date data again. | ||
@@ -41,8 +39,8 @@ | ||
With SWR, components will get a stream of data updates constantly and automatically, Thus, the UI will be always fast and reactive. | ||
...and a lot more. | ||
With SWR, components will get **a stream of data updates constantly and automatically**. Thus, the UI will be always **fast** and **reactive**. | ||
## Quick Start | ||
To install, run `yarn add swr` or `npm install swr` in your React project. | ||
```js | ||
@@ -61,3 +59,3 @@ import useSWR from 'swr' | ||
In this example, the React Hook `useSWR` accepts a `key` and a `fetcher` function. | ||
`key` is a unique identifier of the data, normally a URL of the API. And the `fetcher` accepts | ||
`key` is a unique identifier of the request, normally the URL of the API. And the `fetcher` accepts | ||
`key` as its parameter and returns the data asynchronously. | ||
@@ -72,80 +70,134 @@ | ||
--- | ||
Check out [swr.now.sh](https://swr.now.sh) for more demos of SWR. | ||
- API | ||
- [`useSWR`](#useswr) | ||
- [`SWRConfig`](#swrconfig) | ||
- [`mutate`](#mutate) | ||
- [`trigger`](#trigger) | ||
- Examples | ||
- [Suspense Mode](#suspense-mode) | ||
- [Subscription (e.g.: socket.io)](#subscription-eg-socketio) | ||
- [Dependent Fetching](#dependent-fetching) | ||
## Usage | ||
## API | ||
Inside your React project directory, run the following: | ||
### `useSWR` | ||
``` | ||
yarn add swr | ||
``` | ||
```js | ||
const { | ||
data, // data for the given key (or undefined) | ||
error, // error (or undefined) | ||
isValidating, // if the request is loading | ||
revalidate // function to trigger a validate manually | ||
} = useSWR( | ||
key, // a unique key for the data (or a function, see below) | ||
fetcher, // Promise returning function to load your data | ||
swrOptions? = { | ||
suspense: false, // enabled React Suspense mode | ||
revalidateOnFocus: true, // auto revalidate when window gets focused | ||
refreshWhenHidden: false, // refresh while the window is invisible | ||
shouldRetryOnError: true, // retry when fetcher has an error | ||
refreshInterval: 0, // polling interval (disabled by default) | ||
errorRetryInterval: 5000, // error retry interval (10s on slow network) | ||
focusThrottleInterval: 5000, // keep focus revalidate requests in a time window | ||
dedupingInterval: 2000, // deduping requests | ||
loadingTimeout: 3000, // timeout for triggering the onLoadingSlow event | ||
Or with npm: | ||
onLoadingSlow, // event handlers | ||
onSuccess, | ||
onError, | ||
onErrorRetry, | ||
``` | ||
npm install swr | ||
``` | ||
fetcher // default fetcher function | ||
} | ||
) | ||
### API | ||
```js | ||
const { data, error, isValidating, revalidate } = useSWR(key, fetcher, options) | ||
``` | ||
#### `key` as a function | ||
#### Parameters | ||
Pass a function as the `key` to `useSWR` to conditionally fetch data. If the functions throws an error or returns a falsy value, SWR will cancel the request. | ||
- `key`: a unique key string for the request (or a function / null) [(advanced usage)](#conditional-fetching) | ||
- `fetcher`: (_optional_) a Promise returning function to fetch your data [(details)](#data-fetching) | ||
- `options`: (_optional_) an object of options for this SWR hook | ||
#### Return Values | ||
- `data`: data for the given key resolved by `fetcher` (or undefined if not loaded) | ||
- `error`: error thrown by `fetcher` (or undefined) | ||
- `isValidating`: if there's a request or revalidation loading | ||
- `revalidate`: function to trigger the validation manually | ||
#### Options | ||
- `suspense = false`: enable React Suspense mode [(details)](#suspense-mode) | ||
- `fetcher = undefined`: the default fetcher function | ||
- `revalidateOnFocus = true`: auto revalidate when window gets focused | ||
- `refreshInterval = 0`: polling interval (disabled by default) | ||
- `refreshWhenHidden = false`: polling when the window is invisible (if `refreshInterval` is enabled) | ||
- `shouldRetryOnError = true`: retry when fetcher has an error [(details)](#error-retries) | ||
- `dedupingInterval = 2000`: dedupe requests with the same key in this time span | ||
- `focusThrottleInterval = 5000`: only revalidate once during a time span | ||
- `loadingTimeout = 3000`: timeout to trigger the onLoadingSlow event | ||
- `errorRetryInterval = 5000`: error retry interval [(details)](#error-retries) | ||
- `onLoadingSlow`: callback function when a request takes too long to load (`loadingTimeout`) | ||
- `onSuccess`: callback function when a request finishs successfully | ||
- `onError`: callback function when a request returns an error | ||
- `onErrorRetry`: handler for [error retry](#error-retries) | ||
When under a slow network (2G, <= 70Kbps), `errorRetryInterval` will be 10s, and | ||
`loadingTimeout` will be 5s by default. | ||
You can also use [global configuration](#global-configuration) to provide default options. | ||
## Examples | ||
- [Global Configuration](#global-configuration) | ||
- [Data Fetching](#data-fetching) | ||
- [Conditional Fetching](#conditional-fetching) | ||
- [Dependent Fetching](#dependent-fetching) | ||
- [Manually Revalidate](#manually-revalidate) | ||
- [Local Mutation](#local-mutation) | ||
- [Suspense Mode](#suspense-mode) | ||
- [Error Retries](#error-retries) | ||
### Global Configuration | ||
You can use `SWRConfig` to provide global configurations (`options`) for all SWR hooks. | ||
In this example, all `useSWR` hooks will use the same fetcher provided to load JSON data, and refresh every 3 seconds (except the user API): | ||
```js | ||
// key returns a falsy value | ||
const { data } = useSWR(() => shouldFetch ? '/api/data' : null, fetcher) | ||
import useSWR, { SWRConfig } from 'swr' | ||
// key throws an error when user.id is not defined | ||
const { data } = useSWR(() => '/api/data?uid=' + user.id, fetcher) | ||
function Dashboard () { | ||
const { data: events } = useSWR('/api/events') | ||
const { data: projects } = useSWR('/api/projects') | ||
const { data: user } = useSWR('/api/user', { refreshInterval: 0 }) | ||
// ... | ||
} | ||
function App () { | ||
return ( | ||
<SWRConfig | ||
value={{ | ||
refreshInterval: 3000, | ||
fetcher: (...args) => fetch(...args).then(res => res.json()) | ||
}} | ||
> | ||
<Dashboard /> | ||
</SWRConfig> | ||
) | ||
} | ||
``` | ||
### `SWRConfig` | ||
### Data Fetching | ||
A context to provide global configurations (`swrOptions`) for SWR. | ||
`fetcher` is a function **accepts the `key`** of SWR, and returns a value or a Promise. | ||
You can use any library you to handle data fetching, for example: | ||
```js | ||
import useSWR, { SWRConfig } from 'swr' | ||
import fetch from 'unfetch' | ||
const fetcher = url => fetch(url).then(r => r.json()) | ||
function App () { | ||
// all the SWRs inside will use `refreshInterval: 1000` | ||
// and the native `fetch` implementation | ||
return <SWRConfig value={{ | ||
refreshInterval: 1000, | ||
fetcher: (...args) => fetch(...args).then(res => res.json()) | ||
}}> | ||
<Profile/> | ||
</SWRConfig> | ||
const { data } = useSWR('/api/data', fetcher) | ||
// ... | ||
} | ||
``` | ||
function Profile () { | ||
const { data, error } = useSWR('/api/user') | ||
Or using GraphQL: | ||
```js | ||
import { request } from 'graphql-request' | ||
const API = 'https://api.graph.cool/simple/v1/movies' | ||
const fetcher = query => request(API, query) | ||
function App () { | ||
const { data, error } = useSWR( | ||
`{ | ||
Movie(title: "Inception") { | ||
releaseDate | ||
actors { | ||
name | ||
} | ||
} | ||
}`, | ||
fetcher | ||
) | ||
// ... | ||
@@ -155,27 +207,38 @@ } | ||
### `mutate` | ||
Note that `fetcher` can be skipped from the parameters if it's provided gloablly. | ||
With `mutate`, you can update your local data programmatically, while | ||
revalidating and finally replace it. | ||
### Conditional Fetching | ||
Use `null` or pass a function as the `key` to `useSWR` to conditionally fetch data. If the functions throws an error or returns a falsy value, SWR will cancel the request. | ||
```js | ||
import useSWR, { mutate } from 'swr' | ||
// conditionally fetch | ||
const { data } = useSWR(shouldFetch ? '/api/data' : null, fetcher) | ||
function Profile () { | ||
const { data } = useSWR('/api/user', fetcher) | ||
// ...or return a falsy value | ||
const { data } = useSWR(() => shouldFetch ? '/api/data' : null, fetcher) | ||
return <div> | ||
<h1>My name is {data.name}.</h1> | ||
<button onClick={async () => { | ||
const newName = data.name.toUpperCase() | ||
// send a request to the API to update the data | ||
await requestUpdateUsername(newName) | ||
// update the local data immediately and revalidate (refetch) | ||
mutate('/api/user', { ...data, name: newName }) | ||
}}>Uppercase my name!</button> | ||
</div> | ||
// ... or throw an error when user.id is not defined | ||
const { data } = useSWR(() => '/api/data?uid=' + user.id, fetcher) | ||
``` | ||
### Dependent Fetching | ||
SWR also allows you to fetch data that depends on other data. It ensures the maximum possible parallelism (avoiding waterfalls), as well as serial fetching when a piece of dynamic data is required for the next data fetch to happen. | ||
```js | ||
function MyProjects () { | ||
const { data: user } = useSWR('/api/user') | ||
const { data: projects } = useSWR(() => '/api/projects?uid=' + user.id) | ||
// When passing a function, SWR will use the | ||
// return value as `key`. If the function throws, | ||
// SWR will know that some dependencies are not | ||
// ready. In this case it is `user`. | ||
if (!projects) return 'loading...' | ||
return 'You have ' + projects.length + ' projects' | ||
} | ||
``` | ||
### `trigger` | ||
### Manually Revalidate | ||
@@ -185,2 +248,5 @@ You can broadcast a revalidation message to all SWR data inside any component by calling | ||
This example shows how to automatically refetch the login info (e.g.: inside `<Profile/>`) | ||
when the user clicks the “Logout” button. | ||
```js | ||
@@ -190,21 +256,51 @@ import useSWR, { trigger } from 'swr' | ||
function App () { | ||
return <div> | ||
<Profile /> | ||
<button onClick={() => { | ||
// set the cookie as expired | ||
document.cookie = 'token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;' | ||
// tell all SWRs with this key to revalidate | ||
trigger('/api/user') | ||
}}> | ||
Logout | ||
</button> | ||
</div> | ||
return ( | ||
<div> | ||
<Profile /> | ||
<button onClick={() => { | ||
// set the cookie as expired | ||
document.cookie = 'token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;' | ||
// tell all SWRs with this key to revalidate | ||
trigger('/api/user') | ||
}}> | ||
Logout | ||
</button> | ||
</div> | ||
) | ||
} | ||
``` | ||
## Examples | ||
### Local Mutation | ||
In many cases, applying local mutations to data is a good way to make changes | ||
feel faster — no need to wait for the remote source of data. | ||
With `mutate`, you can update your local data programmatically, while | ||
revalidating and finally replace it with the latest data. | ||
```js | ||
import useSWR, { mutate } from 'swr' | ||
function Profile () { | ||
const { data } = useSWR('/api/user', fetcher) | ||
return ( | ||
<div> | ||
<h1>My name is {data.name}.</h1> | ||
<button onClick={async () => { | ||
const newName = data.name.toUpperCase() | ||
// send a request to the API to update the data | ||
await requestUpdateUsername(newName) | ||
// update the local data immediately and revalidate (refetch) | ||
mutate('/api/user', { ...data, name: newName }) | ||
}}>Uppercase my name!</button> | ||
</div> | ||
) | ||
} | ||
``` | ||
### Suspense Mode | ||
You can enable the `suspense` option to use `useSWR` with React Suspense. | ||
You can enable the `suspense` option to use SWR with React Suspense: | ||
@@ -221,62 +317,31 @@ ```js | ||
function App () { | ||
return <Suspense fallback={<div>loading...</div>}> | ||
<Profile/> | ||
</Suspense> | ||
return ( | ||
<Suspense fallback={<div>loading...</div>}> | ||
<Profile/> | ||
</Suspense> | ||
) | ||
} | ||
``` | ||
### Subscription (e.g.: socket.io) | ||
Note in Suspense mode, `data` is always the fetch response (so you don't need to check if it's `undefined`). But if there's an error occurred, you need to use an [error boundary](https://reactjs.org/docs/concurrent-mode-suspense.html#handling-errors) to catch it. | ||
You can use SWR with socket.io (generally any subscription pattern) like this: | ||
### Error Retries | ||
```js | ||
// fetch-data.js | ||
By default, SWR uses the [exponential backoff algorithm](https://en.wikipedia.org/wiki/Exponential_backoff) to handle error retries. | ||
You can read more from the source code. | ||
import { mutate } from 'swr' | ||
It's also possible to override the behavior: | ||
let latestData = null | ||
```js | ||
useSWR(key, fetcher, { | ||
onErrorRetry: (error, key, option, revalidate, { retryCount }) => { | ||
if (retryCount >= 10) return | ||
if (error.status === 404) return | ||
// setup ws and broadcast to all SWRs | ||
... | ||
socket.on('data', data => { | ||
latestData = data | ||
mutate('/api/data', data, false) | ||
// retry after 5 seconds | ||
setTimeout(() => revalidate({ retryCount: retryCount + 1 }), 5000) | ||
} | ||
}) | ||
export default () => latestData | ||
``` | ||
and your component: | ||
```js | ||
import useSWR from 'swr' | ||
import fetchData from './fetch-data' | ||
function App () { | ||
const { data } = useSWR('/api/data', fetchData) | ||
// ... | ||
} | ||
``` | ||
### Dependent Fetching | ||
SWR allows you to fetch data that depends on other data. It ensures the maximum possible parallelism (avoiding waterfalls), as well as serial fetching when a piece of dynamic data is required for the next data fetch to happen. | ||
```js | ||
import useSWR from 'swr' | ||
function MyProjects () { | ||
const { data: user } = useSWR('/api/user') | ||
const { data: projects } = useSWR( | ||
() => '/api/projects?uid=' + user.id | ||
) | ||
// When passing a function, SWR will use the | ||
// return value as `key`. If the function throws, | ||
// SWR will know that some dependencies are not | ||
// ready. In this case it is `user`. | ||
if (!projects) return 'loading...' | ||
return 'You have ' + projects.length + ' projects' | ||
} | ||
``` | ||
## Authors | ||
@@ -283,0 +348,0 @@ - Shu Ding ([@shuding_](https://twitter.com/shuding_)) – [ZEIT](https://zeit.co) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
47955
27
844
349
+ Addedlodash@4.17.15
+ Addedlodash@4.17.15(transitive)
- Removedlodash.throttle@4.1.1
- Removedlodash.throttle@4.1.1(transitive)