Skip to content

Commit 0cb7dc0

Browse files
authored
React 18 (#1)
1 parent 9c58d65 commit 0cb7dc0

17 files changed

+1405
-1404
lines changed

README.md

-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ yarn add react-input-suggestions
2424
## Usage
2525

2626
```jsx
27-
import React from 'react'
2827
import { InputSuggestions } from 'react-input-suggestions'
2928

3029
const MyComponent = () => (
@@ -79,7 +78,6 @@ The markup is very simple. You bring what populates each search suggestion. In t
7978
If you wanted to do something else `onClick` or `onKeyDown`, you could do something like:
8079

8180
```jsx
82-
import React from 'react'
8381
import { InputSuggestions } from 'react-input-suggestions'
8482

8583
const customFunction = (arg: string) => {

esbuild.config.js

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
const path = require('path')
2+
3+
const chokidar = require('chokidar')
4+
const esbuild = require('esbuild')
5+
const liveServer = require('live-server')
6+
7+
const root = './public'
8+
9+
const build = process.argv[2] === 'build' || false
10+
const dev = process.argv[2] === 'dev' || false
11+
const watch = (dev && process.argv[3] === '--watch') || false
12+
13+
const common = {
14+
bundle: true,
15+
inject: [path.join(__dirname, './react-shim.js')],
16+
sourcemap: false,
17+
}
18+
19+
if (build) {
20+
const options = {
21+
...common,
22+
entryPoints: ['./src/index.ts'],
23+
external: Object.keys(require('./package.json').dependencies || {}),
24+
logLevel: 'info',
25+
minify: true,
26+
target: ['esnext'],
27+
}
28+
29+
esbuild.build({
30+
...options,
31+
format: 'esm',
32+
outfile: './dist/index.esm.js',
33+
})
34+
35+
esbuild.build({
36+
...options,
37+
format: 'cjs',
38+
outfile: './dist/index.js',
39+
})
40+
}
41+
42+
if (dev) {
43+
;(async () => {
44+
const builder = await esbuild.build({
45+
...common,
46+
define: {
47+
'process.env.NODE_ENV': JSON.stringify(
48+
process.env.NODE_ENV || 'development'
49+
),
50+
},
51+
entryPoints: ['./src/example/index.tsx'],
52+
incremental: true,
53+
minify: !watch,
54+
outfile: `./${root}/script.js`,
55+
})
56+
57+
if (!watch) {
58+
process.exit(0)
59+
}
60+
61+
chokidar
62+
.watch('./src/**/*.{ts,tsx}', {
63+
interval: 0,
64+
})
65+
.on('all', () => {
66+
builder.rebuild()
67+
})
68+
69+
liveServer.start({
70+
open: true,
71+
port: +process.env.PORT || 8080,
72+
root,
73+
})
74+
})()
75+
}

esbuild/build.js

-25
This file was deleted.

esbuild/dev.js

-43
This file was deleted.

package.json

+18-19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-input-suggestions",
3-
"version": "1.0.2",
3+
"version": "2.0.0",
44
"description": "A React input component with pluggable suggestions and autocomplete",
55
"keywords": [
66
"react",
@@ -26,11 +26,11 @@
2626
],
2727
"scripts": {
2828
"prebuild": "npx rimraf dist",
29-
"build": "node esbuild/build.js",
29+
"build": "node esbuild.config.js build",
3030
"postbuild": "tsc --project tsconfig.build.json",
31-
"predeploy": "node esbuild/dev.js",
31+
"predeploy": "node esbuild.config.js dev",
3232
"deploy": "gh-pages -d public",
33-
"dev": "node esbuild/dev.js --watch",
33+
"dev": "node esbuild.config.js dev --watch",
3434
"lint": "yarn lint:scripts && yarn lint:styles",
3535
"lint:scripts": "eslint 'src/**/*.ts*' --ignore-path=.eslintignore --config=.eslintrc.json",
3636
"lint:styles": "stylelint 'src/**/*.ts*'",
@@ -39,34 +39,33 @@
3939
"type": "tsc --noEmit"
4040
},
4141
"dependencies": {
42-
"@emotion/react": "^11.8.1",
42+
"@emotion/react": "^11.8.2",
4343
"@emotion/styled": "^11.8.1",
44-
"react": "^17.0.2",
45-
"react-dom": "^17.0.2",
44+
"react": "^18.0.0",
45+
"react-dom": "^18.0.0",
4646
"react-string-replace": "^1.0.0"
4747
},
4848
"devDependencies": {
49-
"@adhamu/zero": "^3.0.1",
49+
"@adhamu/zero": "^4.0.0",
5050
"@emotion/jest": "^11.8.0",
5151
"@stylelint/postcss-css-in-js": "^0.37.2",
52-
"@testing-library/jest-dom": "^5.16.2",
53-
"@testing-library/react": "^12.1.4",
54-
"@testing-library/react-hooks": "^7.0.2",
55-
"@testing-library/user-event": "^13.5.0",
52+
"@testing-library/jest-dom": "^5.16.4",
53+
"@testing-library/react": "^13.1.1",
54+
"@testing-library/user-event": "^14.1.1",
5655
"@types/jest": "^27.4.1",
57-
"@types/react": "^17.0.39",
58-
"@types/react-dom": "^17.0.13",
56+
"@types/react": "^18.0.5",
57+
"@types/react-dom": "^18.0.1",
5958
"chokidar": "^3.5.3",
60-
"esbuild": "^0.14.25",
59+
"esbuild": "^0.14.36",
6160
"gh-pages": "^3.2.3",
6261
"jest": "^27.5.1",
6362
"live-server": "^1.2.1",
6463
"postcss-syntax": "^0.36.2",
65-
"ts-jest": "^27.1.3",
66-
"typescript": "^4.6.2"
64+
"ts-jest": "^27.1.4",
65+
"typescript": "^4.6.3"
6766
},
6867
"peerDependencies": {
69-
"react": "^17.0.2",
70-
"react-dom": "^17.0.2"
68+
"react": "^18.0.0",
69+
"react-dom": "^18.0.0"
7170
}
7271
}

react-shim.js

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import * as React from 'react'
2+
3+
// eslint-disable-next-line import/prefer-default-export
4+
export { React }

src/InputSuggestions.tsx

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import React from 'react'
1+
import type { ReactNode } from 'react'
2+
import { useRef, useState } from 'react'
23

34
import type { Props } from './types'
45

@@ -18,9 +19,9 @@ const InputSuggestions = ({
1819
onChange,
1920
highlightKeywords = false,
2021
}: Props): JSX.Element => {
21-
const [results, setResults] = React.useState<React.ReactNode[]>(suggestions)
22-
const inputSearchRef = React.useRef<HTMLInputElement>(null)
23-
const searchSuggestionsRef = React.useRef<HTMLUListElement>(null)
22+
const [results, setResults] = useState<ReactNode[]>(suggestions)
23+
const inputSearchRef = useRef<HTMLInputElement>(null)
24+
const searchSuggestionsRef = useRef<HTMLUListElement>(null)
2425

2526
const {
2627
selectInitialResult,

src/__tests__/InputSuggestions.test.tsx

+23-19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import React from 'react'
2-
3-
import { render, screen } from '@testing-library/react'
1+
import { render, screen, waitFor } from '@testing-library/react'
42
import userEvent from '@testing-library/user-event'
53

64
import InputSuggestions from '../InputSuggestions'
@@ -125,16 +123,16 @@ describe('InputSuggestions', () => {
125123
expect(container.firstChild).toHaveClass('react-search')
126124
})
127125

128-
it('calls wrapElementText when highlightKeywords provided', () => {
126+
it('calls wrapElementText when highlightKeywords provided', async () => {
129127
render(<InputSuggestions suggestions={suggestions} highlightKeywords />)
130128

131129
userEvent.type(screen.getByRole('searchbox'), 't')
132130

133-
expect(mockWrapElementText).toHaveBeenCalledTimes(2)
131+
await waitFor(() => expect(mockWrapElementText).toHaveBeenCalledTimes(2))
134132
})
135133
})
136134

137-
it('fires an onChange event if provided', () => {
135+
it('fires an onChange event if provided', async () => {
138136
const mockOnChange = jest.fn()
139137

140138
render(
@@ -145,31 +143,37 @@ describe('InputSuggestions', () => {
145143

146144
userEvent.type(screen.getByRole('searchbox'), 't')
147145

148-
expect(mockOnChange).toHaveBeenCalled()
149-
expect(mockGetElementText).toHaveBeenCalled()
146+
await waitFor(() => expect(mockOnChange).toHaveBeenCalled())
147+
await waitFor(() => expect(mockGetElementText).toHaveBeenCalled())
150148
})
151149

152-
it('shows filtered search suggestions based on input entered', () => {
150+
it('shows filtered search suggestions based on input entered', async () => {
153151
render(<InputSuggestions suggestions={suggestions} />)
154152

155153
expect(screen.queryByRole('list')).not.toBeInTheDocument()
156154

157155
userEvent.type(screen.getByRole('searchbox'), 't')
158156

159-
expect(screen.getByRole('list')).toBeInTheDocument()
157+
expect(await screen.findByRole('list')).toBeInTheDocument()
160158

161-
expect(screen.getByRole('link', { name: 'reddit' })).toHaveAttribute(
162-
'href',
163-
'https://reddit.com'
159+
await waitFor(() =>
160+
expect(screen.getByRole('link', { name: 'reddit' })).toHaveAttribute(
161+
'href',
162+
'https://reddit.com'
163+
)
164164
)
165165

166-
expect(screen.getByRole('link', { name: 'twitter' })).toHaveAttribute(
167-
'href',
168-
'https://twitter.com'
166+
await waitFor(() =>
167+
expect(screen.getByRole('link', { name: 'twitter' })).toHaveAttribute(
168+
'href',
169+
'https://twitter.com'
170+
)
169171
)
170172

171-
expect(
172-
screen.queryByRole('link', { name: 'facebook' })
173-
).not.toBeInTheDocument()
173+
await waitFor(() =>
174+
expect(
175+
screen.queryByRole('link', { name: 'facebook' })
176+
).not.toBeInTheDocument()
177+
)
174178
})
175179
})

src/__tests__/elementText.test.tsx

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import React from 'react'
2-
31
import { render } from '@testing-library/react'
42

53
import { getElementText, wrapElementText } from '../elementText'

src/__tests__/styled.test.tsx

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import React from 'react'
2-
31
import { render } from '@testing-library/react'
42

53
import { Styled } from '../styled'

src/__tests__/useSuggestions.test.tsx

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
import React from 'react'
2-
3-
import { screen } from '@testing-library/react'
4-
import { renderHook } from '@testing-library/react-hooks'
1+
import { renderHook, screen } from '@testing-library/react'
52

63
import { useSuggestions } from '../useSuggestions'
74

src/elementText.tsx

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import React from 'react'
1+
import type { ReactElement } from 'react'
2+
import React, { Children, cloneElement } from 'react'
23

34
import reactStringReplace from 'react-string-replace'
45

@@ -12,7 +13,7 @@ export const getElementText = (node: React.ReactNode): string | undefined => {
1213
}
1314

1415
if (typeof node === 'object' && node) {
15-
return getElementText((node as React.ReactElement).props.children)
16+
return getElementText((node as ReactElement).props.children)
1617
}
1718

1819
return undefined
@@ -23,10 +24,10 @@ const highlightKeyword = (content: string, keyword: string) =>
2324
<mark key={key}>{match}</mark>
2425
))
2526

26-
const cloneChildren = (children: React.ReactElement[], keyword: string): any =>
27-
React.Children.map(children, child =>
27+
const cloneChildren = (children: ReactElement[], keyword: string): any =>
28+
Children.map(children, child =>
2829
child.props
29-
? React.cloneElement(child, {
30+
? cloneElement(child, {
3031
children: highlightKeyword(
3132
cloneChildren(child.props.children, keyword),
3233
keyword
@@ -38,7 +39,7 @@ const cloneChildren = (children: React.ReactElement[], keyword: string): any =>
3839
export const wrapElementText = (
3940
node: React.ReactNode,
4041
keyword: string
41-
): React.ReactElement | React.ReactNode => {
42+
): ReactElement | React.ReactNode => {
4243
const n = node as React.ReactElement
4344
const {
4445
props: { children },

0 commit comments

Comments
 (0)