Skip to content

Commit 88ae92e

Browse files
committed
Merge branch 'main' of github.com:Mojang/ore-ui into react-18-main
2 parents e22975d + 193244d commit 88ae92e

25 files changed

+678
-270
lines changed

.eslintrc.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ module.exports = {
2828
'import/no-cycle': 'error',
2929
'no-unreachable': 'error',
3030
'no-undef': 'error',
31-
'eqeqeq': 'error',
31+
eqeqeq: 'error',
3232
'react-hooks/rules-of-hooks': 'error',
3333
'react-hooks/exhaustive-deps': 'error',
3434
'react/jsx-uses-react': 'error',

.github/workflows/benchmarking.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
node-version-file: '.nvmrc'
3737
cache: 'yarn'
3838
- run: yarn install --immutable
39-
- run: cd examples/benchmarking && yarn build && yarn compare mountFacetDomFiber mountReactDom 90
39+
- run: cd examples/benchmarking && yarn build && yarn compare mountFacetDomFiber mountReactDom 91
4040

4141
overhead:
4242
runs-on: ubuntu-latest

.nvmrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v14.20.0
1+
v20.11.1

CONTRIBUTING.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Currently the process of updating the version of the packages is mostly manual. Before you start, first you need to make sure you have the permissions to publish the packages.
44

5-
- If there is a release branch (see __Release candidate__ below) that hasn't been merged to `main` yet now is the time to do so.
5+
- If there is a release branch (see **Release candidate** below) that hasn't been merged to `main` yet now is the time to do so.
66
- Make sure that you are logged in into your `npm` account. Use the command `yarn npm login` on the project folder to do this.
77
- While on the `main` branch.
88
- Perform a search an replace on all "package.json" files from the old version to the new. (ex `0.1.4` to `0.2.0`).
@@ -25,4 +25,4 @@ Currently the process of updating the version of the packages is mostly manual.
2525
- Create an annotated git tag by running `git tag -a v0.4.0-rc.0` (replace with the version). The tag message can be the version again.
2626
- Push commit and the tag `git push --follow-tags`.
2727
- Publish the packages by running `yarn publish --tag rc` (it will also build the packages).
28-
- We are currently not doing release notes for release candidates so you are all done! 🎉
28+
- We are currently not doing release notes for release candidates so you are all done! 🎉

examples/benchmarking/package.json

+5-5
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
"compare": "ts-node compare.ts"
1010
},
1111
"dependencies": {
12-
"@react-facet/core": "0.5.3",
13-
"@react-facet/dom-fiber": "0.5.3",
14-
"@react-facet/shared-facet": "0.5.3",
15-
"@react-facet/spring": "0.5.3",
12+
"@react-facet/core": "0.6.1",
13+
"@react-facet/dom-fiber": "0.6.1",
14+
"@react-facet/shared-facet": "0.6.1",
15+
"@react-facet/spring": "0.6.1",
1616
"ramda": "^0.27.1",
1717
"react": "18.2.0",
1818
"react-dom": "^18.2.0"
@@ -28,7 +28,7 @@
2828
"ts-loader": "^9.2.5",
2929
"ts-node": "^10.3.1",
3030
"typescript": "^4.8.2",
31-
"webpack": "^5.51.1",
31+
"webpack": "^5.91.0",
3232
"webpack-cli": "^4.8.0",
3333
"webpack-dev-server": "^4.0.0"
3434
}

packages/@react-facet/core/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"homepage": "https://react-facet.mojang.com/",
3030
"bugs": "https://github.com/Mojang/ore-ui/issues",
3131
"license": "MIT",
32-
"version": "0.5.3",
32+
"version": "0.6.1",
3333
"main": "src/index.ts",
3434
"publishConfig": {
3535
"main": "dist/index.js",
@@ -44,7 +44,7 @@
4444
"react": "^18.2.0"
4545
},
4646
"devDependencies": {
47-
"@react-facet/dom-fiber-testing-library": "0.5.3",
47+
"@react-facet/dom-fiber-testing-library": "0.6.1",
4848
"@types/react": "^18.0.8",
4949
"@types/react-reconciler": "^0.28.0",
5050
"@types/rimraf": "^3",

packages/@react-facet/core/src/components/Map.spec.tsx

+18
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,21 @@ it('updates only items that have changed', () => {
149149
expect(mock).toHaveBeenCalledTimes(1)
150150
expect(mock).toHaveBeenCalledWith({ a: '6' })
151151
})
152+
153+
it('provides the length of the array', () => {
154+
const data = createFacet({
155+
initialValue: [{ a: '1' }, { a: '2' }, { a: '3' }],
156+
})
157+
158+
const ExampleContent = ({ index, length }: { index: number; length: number }) => {
159+
return <>{length === index + 1 && <div data-testid={'length'}>{length}</div>}</>
160+
}
161+
162+
const Example = () => {
163+
return <Map array={data}>{(_, index, length) => <ExampleContent index={index} length={length} />}</Map>
164+
}
165+
166+
const { getByTestId } = render(<Example />)
167+
168+
expect(getByTestId('length')).toHaveTextContent('3')
169+
})

packages/@react-facet/core/src/components/Map.tsx

+14-11
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import { EqualityCheck, Facet, NO_VALUE } from '../types'
66

77
export type MapProps<T> = {
88
array: Facet<T[]>
9-
children: (item: Facet<T>, index: number) => ReactElement | null
9+
children: (item: Facet<T>, index: number, length: number) => ReactElement | null
1010
equalityCheck?: EqualityCheck<T>
1111
}
1212

1313
export const Map = <T,>({ array, children, equalityCheck }: MapProps<T>) => {
14-
// When mounting lists, we always want to defer
15-
const countValue = useFacetUnwrap(useFacetMap((array) => array.length, [], [array]))
14+
const lengthValue = useFacetUnwrap(useFacetMap((array) => array.length, [], [array]))
15+
const lengthNumber = lengthValue !== NO_VALUE ? lengthValue : 0
1616

1717
return (
1818
<>
@@ -23,13 +23,14 @@ export const Map = <T,>({ array, children, equalityCheck }: MapProps<T>) => {
2323
key={index}
2424
arrayFacet={array}
2525
index={index}
26+
length={lengthNumber}
2627
equalityCheck={equalityCheck}
2728
children={children}
2829
/>
2930
) : (
30-
<MapChild<T> key={index} arrayFacet={array} index={index} children={children} />
31+
<MapChild<T> key={index} arrayFacet={array} index={index} length={lengthNumber} children={children} />
3132
),
32-
countValue !== NO_VALUE ? countValue : 0,
33+
lengthNumber,
3334
)}
3435
</>
3536
)
@@ -38,11 +39,12 @@ export const Map = <T,>({ array, children, equalityCheck }: MapProps<T>) => {
3839
type MapChildMemoProps<T> = {
3940
arrayFacet: Facet<T[]>
4041
index: number
41-
children: (item: Facet<T>, index: number) => ReactElement | null
42+
length: number
43+
children: (item: Facet<T>, index: number, length: number) => ReactElement | null
4244
equalityCheck: EqualityCheck<T>
4345
}
4446

45-
const MapChildMemo = <T,>({ arrayFacet, index, children, equalityCheck }: MapChildMemoProps<T>) => {
47+
const MapChildMemo = <T,>({ arrayFacet, index, length, children, equalityCheck }: MapChildMemoProps<T>) => {
4648
const childFacet = useFacetMemo(
4749
(array) => {
4850
if (index < array.length) return array[index]
@@ -52,16 +54,17 @@ const MapChildMemo = <T,>({ arrayFacet, index, children, equalityCheck }: MapChi
5254
[arrayFacet],
5355
equalityCheck,
5456
)
55-
return children(childFacet, index)
57+
return children(childFacet, index, length)
5658
}
5759

5860
type MapChildProps<T> = {
5961
arrayFacet: Facet<T[]>
6062
index: number
61-
children: (item: Facet<T>, index: number) => ReactElement | null
63+
length: number
64+
children: (item: Facet<T>, index: number, length: number) => ReactElement | null
6265
}
6366

64-
const MapChild = <T,>({ arrayFacet, index, children }: MapChildProps<T>) => {
67+
const MapChild = <T,>({ arrayFacet, index, length, children }: MapChildProps<T>) => {
6568
const childFacet = useFacetMap(
6669
(array) => {
6770
if (index < array.length) return array[index]
@@ -71,7 +74,7 @@ const MapChild = <T,>({ arrayFacet, index, children }: MapChildProps<T>) => {
7174
[arrayFacet],
7275
)
7376

74-
return children(childFacet, index)
77+
return children(childFacet, index, length)
7578
}
7679

7780
interface TimesFn<T> {

packages/@react-facet/core/src/hooks/useFacetUnwrap.spec.tsx

+15-1
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ it('does not trigger a re-render when changing a facet from undefined to undefin
228228
})
229229

230230
it('supports custom equality checks', () => {
231-
const value = {}
231+
const value = { prop: 'initial' }
232232
const demoFacet = createFacet({ initialValue: value })
233233

234234
// Dummy equality check that always returns its not equal
@@ -267,4 +267,18 @@ it('supports custom equality checks', () => {
267267
expect(check).toHaveBeenCalledTimes(1) // but the check should be executed
268268
expect(check).toHaveBeenCalledWith(value) // passing the value (which should be the same)
269269
expect(renderedMock).toHaveBeenCalledTimes(1) // and since the equality check always returns "false", we have a render
270+
271+
jest.clearAllMocks()
272+
273+
const newValue = { prop: 'new' }
274+
275+
// If we update with a new object,
276+
act(() => {
277+
demoFacet.set(newValue)
278+
})
279+
280+
expect(equalityCheck).toHaveBeenCalledTimes(0) // equality check was already initialized
281+
expect(check).toHaveBeenCalledTimes(1) // but the check should be executed
282+
expect(check).toHaveBeenCalledWith(newValue) // passing the new value
283+
expect(renderedMock).toHaveBeenCalledTimes(1) // and since the equality check always returns "false", we have a render
270284
})

packages/@react-facet/core/src/hooks/useFacetUnwrap.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export function useFacetUnwrap<T extends Value>(
6161
return { value }
6262
}
6363

64-
if (previousValue !== NO_VALUE && isEqual(previousValue)) {
64+
if (previousValue !== NO_VALUE && isEqual(value)) {
6565
return previousState
6666
}
6767

packages/@react-facet/core/src/mapFacets/mapFacetArrayCached.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,25 @@ export function mapFacetArrayCached<M>(
99
): Facet<M> {
1010
const initialValues = facets.map((facet) => facet.get())
1111
const hasAllValues = initialValues.reduce<boolean>((prev, curr) => prev && curr !== NO_VALUE, true)
12-
return createFacet<M>({
12+
const cachedFacet = createFacet<M>({
1313
// pass the equalityCheck to the mapIntoObserveArray to prevent even triggering the observable
1414
startSubscription: mapIntoObserveArray(facets, fn, equalityCheck),
1515
initialValue: hasAllValues ? fn(...initialValues) : NO_VALUE,
1616
})
17+
18+
return {
19+
get: () => {
20+
const cachedValue = cachedFacet.get()
21+
if (cachedValue !== NO_VALUE) return cachedValue
22+
23+
const dependencyValues = facets.map((facet) => facet.get())
24+
const hasAllValues = dependencyValues.reduce<boolean>((acc, value) => acc && value !== NO_VALUE, true)
25+
if (!hasAllValues) return NO_VALUE
26+
27+
const mappedValue = fn(...dependencyValues)
28+
if (mappedValue !== NO_VALUE) cachedFacet.set(mappedValue)
29+
return mappedValue
30+
},
31+
observe: cachedFacet.observe,
32+
}
1733
}

packages/@react-facet/core/src/mapFacets/mapFacetArrayLightweight.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ export function mapFacetArrayLightweight<M>(
88
): Facet<M> {
99
return {
1010
get: () => {
11-
const values = facets.map((facet) => facet.get())
12-
const hasAllValues = values.reduce<boolean>((acc, value) => acc && value !== NO_VALUE, true)
11+
const dependencyValues = facets.map((facet) => facet.get())
12+
const hasAllValues = dependencyValues.reduce<boolean>((acc, value) => acc && value !== NO_VALUE, true)
1313
if (!hasAllValues) return NO_VALUE
1414

15-
return fn(...values)
15+
return fn(...dependencyValues)
1616
},
1717
observe: mapIntoObserveArray(facets, fn, equalityCheck),
1818
}

packages/@react-facet/core/src/mapFacets/mapFacetSingleCached.ts

+19-4
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,29 @@ import { mapIntoObserveSingle } from './mapIntoObserveSingle'
33
import { createFacet } from '../facet'
44

55
export function mapFacetSingleCached<T, M>(
6-
facets: Facet<T>,
6+
facet: Facet<T>,
77
fn: (value: T) => M | NoValue,
88
equalityCheck?: EqualityCheck<M>,
99
): Facet<M> {
10-
const initialValue = facets.get()
11-
return createFacet<M>({
10+
const initialValue = facet.get()
11+
const cachedFacet = createFacet<M>({
1212
// pass the equalityCheck to the mapIntoObserveSingle to prevent even triggering the observable
13-
startSubscription: mapIntoObserveSingle(facets, fn, equalityCheck),
13+
startSubscription: mapIntoObserveSingle(facet, fn, equalityCheck),
1414
initialValue: initialValue !== NO_VALUE ? fn(initialValue) : NO_VALUE,
1515
})
16+
17+
return {
18+
get: () => {
19+
const cachedValue = cachedFacet.get()
20+
if (cachedValue !== NO_VALUE) return cachedValue
21+
22+
const dependencyValue = facet.get()
23+
if (dependencyValue === NO_VALUE) return NO_VALUE
24+
25+
const mappedValue = fn(dependencyValue)
26+
if (mappedValue !== NO_VALUE) cachedFacet.set(mappedValue)
27+
return mappedValue
28+
},
29+
observe: cachedFacet.observe,
30+
}
1631
}

packages/@react-facet/core/src/mapFacets/mapFacetSingleLightweight.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ export function mapFacetSingleLightweight<T, M>(
88
): Facet<M> {
99
return {
1010
get: () => {
11-
const value = facet.get()
12-
if (value === NO_VALUE) return NO_VALUE
11+
const dependencyValue = facet.get()
12+
if (dependencyValue === NO_VALUE) return NO_VALUE
1313

14-
return fn(value)
14+
return fn(dependencyValue)
1515
},
1616

1717
observe: mapIntoObserveSingle(facet, fn, equalityCheck),

packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts

+61
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,67 @@ describe('mapFacetsCached WITH an initial value', () => {
124124
// check that the map was called just once
125125
expect(mapFunction).toHaveBeenCalledTimes(1)
126126
})
127+
128+
it('gets NO_VALUE as a value from a single source if it also has NO_VALUE', () => {
129+
const mapFunction = jest.fn().mockReturnValue('dummy')
130+
const sourceFacet = createFacet({ initialValue: NO_VALUE })
131+
const mapFacet = mapFacetsCached([sourceFacet], mapFunction, () => () => false)
132+
133+
expect(mapFacet.get()).toBe(NO_VALUE)
134+
})
135+
136+
it('gets NO_VALUE as a value from multiple sources if they also have NO_VALUE', () => {
137+
const mapFunction = jest.fn().mockReturnValue('dummy')
138+
const sourceAFacet = createFacet({ initialValue: 'initial value' })
139+
const sourceBFacet = createFacet({ initialValue: NO_VALUE })
140+
const mapFacet = mapFacetsCached([sourceAFacet, sourceBFacet], mapFunction, () => () => false)
141+
142+
expect(mapFacet.get()).toBe(NO_VALUE)
143+
})
144+
145+
it('can get the mapped value from a single source before any subscription', () => {
146+
const mapFunction = jest.fn().mockReturnValue('dummy')
147+
const sourceFacet = createFacet({ initialValue: 'initial value' })
148+
const mapFacet = mapFacetsCached([sourceFacet], mapFunction, () => () => false)
149+
150+
expect(mapFacet.get()).toBe('dummy')
151+
})
152+
153+
it('can get the mapped value from multiple sources before any subscription', () => {
154+
const mapFunction = jest.fn().mockReturnValue('dummy')
155+
const sourceAFacet = createFacet({ initialValue: 'initial value' })
156+
const sourceBFacet = createFacet({ initialValue: 'initial value' })
157+
const mapFacet = mapFacetsCached([sourceAFacet, sourceBFacet], mapFunction, () => () => false)
158+
159+
expect(mapFacet.get()).toBe('dummy')
160+
})
161+
162+
it('caches calls to the mapFunction through a get call before any subscription, given a single source', () => {
163+
const mapFunction = jest.fn().mockReturnValue('dummy')
164+
const sourceFacet = createFacet({ initialValue: 'initial value' })
165+
const mapFacet = mapFacetsCached([sourceFacet], mapFunction, () => () => false)
166+
167+
expect(mapFacet.get()).toBe('dummy')
168+
expect(mapFunction).toHaveBeenCalledTimes(1)
169+
170+
mapFunction.mockClear()
171+
expect(mapFacet.get()).toBe('dummy')
172+
expect(mapFunction).not.toHaveBeenCalled()
173+
})
174+
175+
it('caches calls to the mapFunction through a get call before any subscription, given multiple sources', () => {
176+
const mapFunction = jest.fn().mockReturnValue('dummy')
177+
const sourceAFacet = createFacet({ initialValue: 'initial value' })
178+
const sourceBFacet = createFacet({ initialValue: 'initial value' })
179+
const mapFacet = mapFacetsCached([sourceAFacet, sourceBFacet], mapFunction, () => () => false)
180+
181+
expect(mapFacet.get()).toBe('dummy')
182+
expect(mapFunction).toHaveBeenCalledTimes(1)
183+
184+
mapFunction.mockClear()
185+
expect(mapFacet.get()).toBe('dummy')
186+
expect(mapFunction).not.toHaveBeenCalled()
187+
})
127188
})
128189

129190
describe('mapFacetsLightweight', () => {

packages/@react-facet/dom-components/package.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"homepage": "https://react-facet.mojang.com/",
3030
"bugs": "https://github.com/Mojang/ore-ui/issues",
3131
"license": "MIT",
32-
"version": "0.5.3",
32+
"version": "0.6.1",
3333
"main": "src/index.tsx",
3434
"publishConfig": {
3535
"main": "dist/index.js",
@@ -41,12 +41,12 @@
4141
"prepublish": "yarn build"
4242
},
4343
"peerDependencies": {
44-
"@react-facet/core": "0.5.3",
44+
"@react-facet/core": "0.6.1",
4545
"react": "^18.2.0"
4646
},
4747
"devDependencies": {
48-
"@react-facet/core": "0.5.3",
49-
"@react-facet/dom-fiber-testing-library": "0.5.3",
48+
"@react-facet/core": "0.6.1",
49+
"@react-facet/dom-fiber-testing-library": "0.6.1",
5050
"@testing-library/jest-dom": "^5.16.5",
5151
"@types/react": "^18.0.8",
5252
"@types/rimraf": "^3",

0 commit comments

Comments
 (0)