Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
coopwire-hypothesis
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
孙灵跃 Leon Sun
coopwire-hypothesis
Commits
d9e99916
Commit
d9e99916
authored
Dec 10, 2020
by
Robert Knight
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Remove old `useStore` hook
All consumers now use the replacement `useStoreProxy` hook.
parent
d2e9f195
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
1 addition
and
191 deletions
+1
-191
use-store-test.js
src/sidebar/store/test/use-store-test.js
+1
-99
use-store.js
src/sidebar/store/use-store.js
+0
-92
No files found.
src/sidebar/store/test/use-store-test.js
View file @
d9e99916
import
{
mount
}
from
'enzyme'
;
import
{
createElement
}
from
'preact'
;
import
{
act
}
from
'preact/test-utils'
;
import
{
createStore
as
createReduxStore
}
from
'redux'
;
import
createStore
from
'../create-store'
;
import
useStore
,
{
useStoreProxy
,
$imports
}
from
'../use-store'
;
// Plain Redux reducer used by `useStore` tests. Remove once `useStore` is removed.
const
initialState
=
{
value
:
10
,
otherValue
:
20
};
const
reducer
=
(
state
=
initialState
,
action
)
=>
{
if
(
action
.
type
===
'INCREMENT'
)
{
return
{
...
state
,
value
:
state
.
value
+
1
};
}
else
if
(
action
.
type
===
'INCREMENT_OTHER'
)
{
return
{
...
state
,
otherValue
:
state
.
otherValue
+
1
};
}
else
{
return
state
;
}
};
import
{
useStoreProxy
,
$imports
}
from
'../use-store'
;
// Store module for use with `createStore` in tests.
const
thingsModule
=
{
...
...
@@ -57,91 +44,6 @@ describe('sidebar/store/use-store', () => {
$imports
.
$restore
();
});
// Tests for deprecated `useStore` function.
describe
(
'useStore'
,
()
=>
{
let
renderCount
;
let
testStore
;
let
TestComponent
;
beforeEach
(()
=>
{
renderCount
=
0
;
// eslint-disable-next-line react/display-name
TestComponent
=
()
=>
{
renderCount
+=
1
;
const
aValue
=
useStore
(
store
=>
store
.
getState
().
value
);
return
<
div
>
{
aValue
}
<
/div>
;
};
testStore
=
createReduxStore
(
reducer
);
$imports
.
$mock
({
'../util/service-context'
:
{
useService
:
name
=>
(
name
===
'store'
?
testStore
:
null
),
},
});
});
it
(
'returns result of `callback(store)`'
,
()
=>
{
const
wrapper
=
mount
(
<
TestComponent
/>
);
assert
.
equal
(
wrapper
.
text
(),
'10'
);
});
it
(
're-renders when the store changes and result of `callback(store)` also changes'
,
()
=>
{
// An update which changes result of `callback(store)` should cause a re-render.
const
wrapper
=
mount
(
<
TestComponent
/>
);
act
(()
=>
{
testStore
.
dispatch
({
type
:
'INCREMENT'
});
});
wrapper
.
update
();
assert
.
equal
(
wrapper
.
text
(),
'11'
);
// The new result from `callback(store)` should be remembered so that another
// update which doesn't change the result doesn't cause a re-render.
const
prevRenderCount
=
renderCount
;
act
(()
=>
{
testStore
.
dispatch
({
type
:
'INCREMENT_OTHER'
});
});
wrapper
.
update
();
assert
.
equal
(
renderCount
,
prevRenderCount
);
});
it
(
'does not re-render if the result of `callback(store)` did not change'
,
()
=>
{
mount
(
<
TestComponent
/>
);
const
originalRenderCount
=
renderCount
;
act
(()
=>
{
testStore
.
dispatch
({
type
:
'INCREMENT_OTHER'
});
});
assert
.
equal
(
renderCount
,
originalRenderCount
);
});
it
(
'warns if the callback always returns a different value'
,
()
=>
{
const
warnOnce
=
sinon
.
stub
();
$imports
.
$mock
({
'../../shared/warn-once'
:
warnOnce
,
});
const
BuggyComponent
=
()
=>
{
// The result of the callback is an object with an `aValue` property
// which is a new array every time. This causes unnecessary re-renders.
useStore
(()
=>
({
aValue
:
[]
}));
return
null
;
};
mount
(
<
BuggyComponent
/>
);
assert
.
called
(
warnOnce
);
assert
.
match
(
warnOnce
.
firstCall
.
args
[
0
],
/changes every time/
);
});
it
(
'unsubscribes when the component is unmounted'
,
()
=>
{
const
unsubscribe
=
sinon
.
stub
();
testStore
.
subscribe
=
sinon
.
stub
().
returns
(
unsubscribe
);
const
wrapper
=
mount
(
<
TestComponent
/>
);
assert
.
calledOnce
(
testStore
.
subscribe
);
wrapper
.
unmount
();
assert
.
calledOnce
(
unsubscribe
);
});
});
describe
(
'useStoreProxy'
,
()
=>
{
let
store
;
let
renderCount
;
...
...
src/sidebar/store/use-store.js
View file @
d9e99916
/* global process */
import
{
useEffect
,
useRef
,
useReducer
}
from
'preact/hooks'
;
import
shallowEqual
from
'shallowequal'
;
import
warnOnce
from
'../../shared/warn-once'
;
import
{
useService
}
from
'../util/service-context'
;
/** @typedef {import("redux").Store} Store */
/** @typedef {import("./index").SidebarStore} SidebarStore */
/**
* @template T
* @callback StoreCallback
* @param {SidebarStore} store
* @return {T}
*/
/**
* Hook for accessing state or actions from the store inside a component.
*
* This hook fetches the store using `useService` and returns the result of
* passing it to the provided callback. The callback will be re-run whenever
* the store updates and the component will be re-rendered if the result of
* `callback(store)` changed.
*
* This ensures that the component updates when relevant store state changes.
*
* @example
* function MyWidget({ widgetId }) {
* const widget = useStore(store => store.getWidget(widgetId));
* const hideWidget = useStore(store => store.hideWidget);
*
* return (
* <div>
* {widget.name}
* <button onClick={() => hideWidget(widgetId)}>Hide</button>
* </div>
* )
* }
*
* @template T
* @param {StoreCallback<T>} callback -
* Callback that receives the store as an argument and returns some state
* and/or actions extracted from the store.
* @return {T} - The result of `callback(store)`
*/
export
default
function
useStore
(
callback
)
{
const
store
=
useService
(
'store'
);
// Store the last-used callback in a ref so we can access it in the effect
// below without having to re-subscribe to the store when it changes.
const
lastCallback
=
useRef
(
/** @type {StoreCallback<T>|null} */
(
null
));
lastCallback
.
current
=
callback
;
const
lastResult
=
useRef
(
/** @type {T|undefined} */
(
undefined
));
lastResult
.
current
=
callback
(
store
);
// Check for a performance issue caused by `callback` returning a different
// result on every call, even if the store has not changed.
if
(
process
.
env
.
NODE_ENV
!==
'production'
)
{
if
(
!
shallowEqual
(
lastResult
.
current
,
callback
(
store
)))
{
warnOnce
(
'The output of a callback passed to `useStore` changes every time. '
+
'This will lead to a component updating more often than necessary.'
);
}
}
// Abuse `useReducer` to force updates when the store state changes.
const
[,
forceUpdate
]
=
useReducer
(
x
=>
x
+
1
,
0
);
// Connect to the store, call `callback(store)` whenever the store changes
// and re-render the component if the result changed.
useEffect
(()
=>
{
function
checkForUpdate
()
{
const
result
=
lastCallback
.
current
(
store
);
if
(
shallowEqual
(
result
,
lastResult
.
current
))
{
return
;
}
lastResult
.
current
=
result
;
// Force this function to ignore parameters and just force a store update.
/** @type {()=>any} */
(
forceUpdate
)();
}
// Check for any changes since the component was rendered.
checkForUpdate
();
// Check for updates when the store changes in future.
const
unsubscribe
=
store
.
subscribe
(
checkForUpdate
);
// Remove the subscription when the component is unmounted.
return
unsubscribe
;
},
[
forceUpdate
,
store
]);
return
lastResult
.
current
;
}
/**
* Result of a cached store selector method call.
*/
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment