Skip to content
This repository has been archived by the owner on Sep 17, 2022. It is now read-only.

Commit

Permalink
feat: selector can be a string or a function
Browse files Browse the repository at this point in the history
  • Loading branch information
mihar-22 committed Oct 18, 2019
1 parent ab3b1d3 commit 5bb754f
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 3 deletions.
47 changes: 46 additions & 1 deletion src/__tests__/useSelector.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,52 @@ describe('useSelector', () => {
store = createStore({})
})

describe('core subscription behavior', () => {
describe('core select by string subscription behaviour ', () => {
let tester

beforeEach(() => {
store.setState({
foo: 'foo',
bar: 'bar'
})

const Comp = () => {
const { foo, bar } = useSelector('foo,bar')

return (
<div>
<div data-testid='foo'>{foo}</div>
<div data-testid='bar'>{bar}</div>
</div>
)
}

tester = ptl.render(
<StoreProvider value={store}>
<Comp />
</StoreProvider>
)
})

it('selects the state on intial render', () => {
expect(tester.getByTestId('foo')).toHaveTextContent('foo')
expect(tester.getByTestId('bar')).toHaveTextContent('bar')
})

it('selects the state and renders the component when the store updates', () => {
ptl.act(() => {
store.setState({
foo: 'fooB',
bar: 'barB'
})
})

expect(tester.getByTestId('foo')).toHaveTextContent('fooB')
expect(tester.getByTestId('bar')).toHaveTextContent('barB')
})
})

describe('core selector function subscription behavior', () => {
let tester

beforeEach(() => {
Expand Down
20 changes: 18 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,41 @@ const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffec

const refEquality = (a, b) => a === b

// select('foo,bar') creates a function of the form: ({ foo, bar }) => ({ foo, bar })
const select = (properties) => {
properties = properties.split(/\s*,\s*/)

return state => {
const selected = {}
for (let i = 0; i < properties.length; i++) {
selected[properties[i]] = state[properties[i]]
}
return selected
}
}

export const StoreContext = createContext(null)

export const StoreProvider = StoreContext.Provider

export const useStore = () => useContext(StoreContext)

// selector can be a string 'foo,bar' or a function (state => state.foo)
export const useSelector = (selector, equalityFn = refEquality) => {
const store = useStore()
const [, forceRerender] = useReducer(s => s + 1, 0)

const selectorRef = useRef(null)
const selectedStateRef = useRef(null)
const onChangeErrorRef = useRef(null)
const isSelectorStr = (typeof selector === 'string')

let selectedState

try {
if (selectorRef.current !== selector || onChangeErrorRef.current) {
selectedState = selector(store.getState())
const state = store.getState()
selectedState = isSelectorStr ? select(selector)(state) : selector(state)
} else {
selectedState = selectedStateRef.current
}
Expand All @@ -43,7 +59,7 @@ export const useSelector = (selector, equalityFn = refEquality) => {
}

useIsomorphicLayoutEffect(() => {
selectorRef.current = selector
selectorRef.current = isSelectorStr ? select(selector) : selector
selectedStateRef.current = selectedState
onChangeErrorRef.current = null
})
Expand Down

0 comments on commit 5bb754f

Please sign in to comment.