Skip to content

Commit 7ab5bb8

Browse files
MichaelDeBoeySaraVieira
authored andcommitted
Refactor SearchInput to functional component with hooks (codesandbox#1871)
1 parent fa6bb49 commit 7ab5bb8

File tree

6 files changed

+94
-94
lines changed

6 files changed

+94
-94
lines changed

.eslintrc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"extends": ["airbnb", "prettier", "prettier/react", "prettier/flowtype"],
3+
"plugins": ["react-hooks"],
34
"parser": "babel-eslint",
45
"env": {
56
"browser": true,
@@ -40,6 +41,8 @@
4041
}
4142
],
4243
"no-param-reassign": ["error", { "props": false }],
43-
"camelcase": "error"
44+
"camelcase": "error",
45+
"react-hooks/rules-of-hooks": "error",
46+
"react-hooks/exhaustive-deps": "warn"
4447
}
4548
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"eslint-plugin-import": "^2.3.0",
7272
"eslint-plugin-jsx-a11y": "^5.0.3",
7373
"eslint-plugin-react": "~7.4.0",
74+
"eslint-plugin-react-hooks": "^1.6.0",
7475
"flow-bin": "^0.72.0",
7576
"gulp": "^3.9.1",
7677
"lerna": "^2.5.1",
Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import React from 'react';
2-
import styled, { css } from 'styled-components';
3-
41
import { TimelineMax } from 'gsap';
2+
import React, { useEffect, useRef } from 'react';
3+
import styled, { css } from 'styled-components';
54

65
const Container = styled.a`
76
transition: 0.3s ease all;
8-
display: block;
97
margin-bottom: 0.5rem;
108
padding: 1rem;
119
display: flex;
@@ -38,37 +36,38 @@ const Count = styled.div`
3836
font-size: 1.125rem;
3937
`;
4038

41-
export default class Hit extends React.PureComponent {
42-
componentDidMount() {
43-
this.tl = new TimelineMax().fromTo(
44-
this.el,
39+
const Hit = ({ hit: { count, value } }) => {
40+
const el = useRef(null);
41+
const tl = useRef(null);
42+
43+
useEffect(() => {
44+
tl.current = new TimelineMax().fromTo(
45+
el.current,
4546
0.1,
4647
{ opacity: 0 },
4748
{ opacity: 1 }
4849
);
49-
}
50+
}, []);
5051

51-
componentWillReceiveProps(nextProps) {
52-
if (!this.props || nextProps.hit.value !== this.props.hit.value) {
53-
this.tl.restart();
54-
}
55-
}
52+
useEffect(
53+
() => {
54+
tl.current.restart();
55+
},
56+
[value]
57+
);
5658

57-
render() {
58-
return (
59-
<Container
60-
ref={el => {
61-
this.el = el;
62-
}}
63-
href={`https://codesandbox.io/search?refinementList%5Bnpm_dependencies.dependency%5D%5B0%5D=${
64-
this.props.hit.value
65-
}&page=1`}
66-
target="_blank"
67-
rel="noreferrer noopener"
68-
>
69-
<Title>{this.props.hit.value}</Title>
70-
<Count>{this.props.hit.count.toLocaleString('en-US')}</Count>
71-
</Container>
72-
);
73-
}
74-
}
59+
return (
60+
<Container
61+
href={`https://codesandbox.io/search?refinementList%5Bnpm_dependencies.dependency%5D%5B0%5D=${value}&page=1`}
62+
ref={el}
63+
rel="noreferrer noopener"
64+
target="_blank"
65+
>
66+
<Title>{value}</Title>
67+
68+
<Count>{count.toLocaleString('en-US')}</Count>
69+
</Container>
70+
);
71+
};
72+
73+
export default Hit;

packages/homepage/src/screens/home/NPMFeature/SearchInput/Input.js

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useState } from 'react';
22
import styled from 'styled-components';
33

44
import Relative from '@codesandbox/common/lib/components/Relative';
@@ -33,26 +33,25 @@ const Icon = styled(SearchIcon)`
3333
color: rgba(0, 0, 0, 0.5);
3434
`;
3535

36-
export default class SearchInput extends React.PureComponent {
37-
state = {
38-
query: '',
39-
};
36+
const SearchInput = ({ searchQuery }) => {
37+
const [query, setQuery] = useState('');
4038

41-
onChange = e => {
42-
this.setState({ query: e.target.value });
43-
this.props.searchQuery(e.target.value);
39+
const onChange = e => {
40+
setQuery(e.target.value);
41+
searchQuery(e.target.value);
4442
};
4543

46-
render() {
47-
return (
48-
<Relative>
49-
<Input
50-
placeholder="Search for a dependency"
51-
value={this.state.query}
52-
onChange={this.onChange}
53-
/>
54-
<Icon />
55-
</Relative>
56-
);
57-
}
58-
}
44+
return (
45+
<Relative>
46+
<Input
47+
onChange={onChange}
48+
placeholder="Search for a dependency"
49+
value={query}
50+
/>
51+
52+
<Icon />
53+
</Relative>
54+
);
55+
};
56+
57+
export default SearchInput;
Lines changed: 37 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useEffect, useState } from 'react';
22
import styled from 'styled-components';
33

44
import Input from './Input';
@@ -17,50 +17,48 @@ const Legend = styled.div`
1717
color: white;
1818
`;
1919

20-
// eslint-disable-next-line
21-
export default class SearchInput extends React.PureComponent {
22-
state = {
23-
hits: [],
24-
};
25-
26-
componentDidMount() {
27-
this.searchQuery('');
28-
}
20+
const SearchInput = () => {
21+
const [hits, setHits] = useState([]);
2922

30-
searchQuery = (query: string) => {
23+
const searchQuery = (query: string) => {
3124
searchFacets({
3225
facet: 'npm_dependencies.dependency',
3326
query,
3427
hitsPerPage: 3,
3528
}).then(({ facetHits }) => {
36-
this.setState({
37-
hits: facetHits,
38-
});
29+
setHits(facetHits);
3930
});
4031
};
4132

42-
render() {
43-
return (
44-
<div style={{ width: '100%' }}>
45-
<Input searchQuery={this.searchQuery} />
46-
<Legend>
47-
<div>Dependency</div>
48-
<div>Sandbox Count</div>
49-
</Legend>
50-
{this.state.hits.map(hit => <Hit key={hit.value} hit={hit} />)}
51-
<a
52-
href="https://www.algolia.com/?utm_source=algoliaclient&utm_medium=website&utm_content=codesandbox.io&utm_campaign=poweredby"
53-
target="_blank"
54-
rel="noreferrer noopener"
55-
>
56-
<img
57-
alt="Algolia"
58-
style={{ marginTop: '1rem' }}
59-
width={160}
60-
src={algoliaImage}
61-
/>
62-
</a>
63-
</div>
64-
);
65-
}
66-
}
33+
useEffect(() => {
34+
searchQuery('');
35+
}, []);
36+
37+
return (
38+
<div style={{ width: '100%' }}>
39+
<Input searchQuery={searchQuery} />
40+
41+
<Legend>
42+
<div>Dependency</div>
43+
<div>Sandbox Count</div>
44+
</Legend>
45+
46+
{hits.map(hit => <Hit key={hit.value} hit={hit} />)}
47+
48+
<a
49+
href="https://www.algolia.com/?utm_source=algoliaclient&utm_medium=website&utm_content=codesandbox.io&utm_campaign=poweredby"
50+
target="_blank"
51+
rel="noreferrer noopener"
52+
>
53+
<img
54+
alt="Algolia"
55+
style={{ marginTop: '1rem' }}
56+
width={160}
57+
src={algoliaImage}
58+
/>
59+
</a>
60+
</div>
61+
);
62+
};
63+
64+
export default SearchInput;

yarn.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9552,7 +9552,7 @@ eslint-plugin-jsx-a11y@^6.0.3:
95529552
has "^1.0.3"
95539553
jsx-ast-utils "^2.0.1"
95549554

9555-
9555+
[email protected], eslint-plugin-react-hooks@^1.6.0:
95569556
version "1.6.0"
95579557
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.6.0.tgz#348efcda8fb426399ac7b8609607c7b4025a6f5f"
95589558
integrity sha512-lHBVRIaz5ibnIgNG07JNiAuBUeKhEf8l4etNx5vfAEwqQ5tcuK3jV9yjmopPgQDagQb7HwIuQVsE3IVcGrRnag==

0 commit comments

Comments
 (0)