Skip to content

Commit 4d590da

Browse files
committed
♻️ Refactor local state to custom state management
Using a custom state management system with hooks and context API. If this proves burdensome to maintain, I'm open to refactoring to alternative state management system. #26
1 parent 6bf2845 commit 4d590da

8 files changed

Lines changed: 72 additions & 56 deletions

File tree

src/App.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ import { search, results } from "store/reducer";
88

99
import "./App.scss";
1010

11-
const [SearchProvider, useSearch] = createStore(search);
12-
const [ResultsProvider, useResults] = createStore(results);
13-
export { useSearch, useResults };
11+
export const [SearchProvider, useSearch] = createStore(search);
12+
export const [ResultsProvider, useResults] = createStore(results);
1413

1514
function App() {
1615
return (

src/components/KeywordsInput.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import React, { useState } from "react";
1+
import React from "react";
2+
import { setKeywords } from "store/actions";
3+
import { useSearch } from "App";
24

35
const KeywordInput = () => {
6+
const [{ keywords }, dispatch] = useSearch();
7+
48
const handleChange = e => {
5-
setKeywords(e.target.value);
9+
dispatch(setKeywords(e.target.value));
610
};
711

8-
const [keywords, setKeywords] = useState("");
9-
1012
return (
1113
<label>
1214
<span className="label-text">Keywords</span>

src/components/LabelInput.js

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,51 @@
1-
import React, { useState } from "react";
2-
import uniq from "lodash/fp/uniq";
3-
import { normalizeLabelString } from "lib";
4-
import { TEST_IDS } from "./SearchForm";
1+
import React from "react";
52

6-
const SUGGESTED_LABELS = [
7-
"good first issue",
8-
"help wanted",
9-
"first-timers-only"
10-
];
3+
import { TEST_IDS } from "./SearchForm";
4+
import {
5+
addSelectedLabel,
6+
removeSelectedLabel,
7+
setCurrentLabel
8+
} from "store/actions";
9+
import { useSearch } from "App";
1110

1211
const LabelInput = () => {
13-
const [labelArray, setLabelArray] = useState(SUGGESTED_LABELS);
14-
const [label, setLabel] = useState("");
12+
const [
13+
{ currentLabel, selectedLabels, suggestedLabels },
14+
dispatch
15+
] = useSearch();
1516

1617
const handleChange = e => {
17-
setLabel(e.target.value);
18+
dispatch(setCurrentLabel(e.target.value));
19+
};
20+
const handleRemoveLabel = label => _e => {
21+
dispatch(removeSelectedLabel(label));
1822
};
1923

20-
const handleSubmitLabel = e => {
24+
const handleAddLabel = e => {
2125
e.preventDefault();
22-
const normalizedLabel = normalizeLabelString(label);
23-
if (normalizedLabel !== "") {
24-
setLabelArray(ls => uniq([...ls, normalizedLabel]));
25-
setLabel("");
26-
}
26+
dispatch(addSelectedLabel(e.target.value));
2727
};
2828
return (
2929
<label>
3030
<span className="label-text">With labels</span>
3131
<input
3232
autoComplete="off"
3333
list="labels"
34-
value={label}
34+
value={currentLabel}
3535
placeholder="Add a label"
3636
onChange={handleChange}
37-
onKeyDown={e => e.keyCode === 13 && handleSubmitLabel(e)}
38-
onBlur={handleSubmitLabel}
37+
onKeyDown={e => e.keyCode === 13 && handleAddLabel(e)}
38+
onBlur={handleAddLabel}
3939
/>
4040
<datalist id="labels">
41-
{SUGGESTED_LABELS.filter(l => !labelArray.includes(l)).map(label => (
41+
{suggestedLabels.map(label => (
4242
<option key={label} value={label} />
4343
))}
4444
</datalist>
4545
<ul data-testid={TEST_IDS.activeLabels}>
46-
{labelArray.map(label => (
46+
{selectedLabels.map(label => (
4747
<li key={label}>
48-
<button
49-
onClick={_e => setLabelArray(ls => ls.filter(l => l !== label))}
50-
>
48+
<button onClick={handleRemoveLabel(label)}>
5149
<span role="img" aria-label="X">
5250
5351
</span>

src/components/LanguageInput.js

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,19 @@
1-
import React, { useState } from "react";
2-
3-
export const LANGUAGES = [
4-
"Any",
5-
"Haskell",
6-
"JavaScript",
7-
"OCaml",
8-
"PureScript"
9-
];
1+
import React from "react";
2+
import { useSearch } from "App";
3+
import { setSelectedLanguage } from "store/actions";
104

115
const LanguageInput = () => {
12-
const [language, setLanguage] = useState(LANGUAGES[2]);
6+
const [{ languages, selectedLanguage }, dispatch] = useSearch();
137

148
const handleChange = e => {
15-
setLanguage(e.target.value);
9+
dispatch(setSelectedLanguage(e.target.value));
1610
};
1711

1812
return (
1913
<label>
2014
<span className="label-text">In language</span>
21-
<select value={language} onChange={handleChange}>
22-
{LANGUAGES.map(language => (
15+
<select value={selectedLanguage} onChange={handleChange}>
16+
{languages.map(language => (
2317
<option key={language} value={language}>
2418
{language}
2519
</option>

src/components/SearchForm.test.js

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,19 @@ import React from "react";
22
import { render, fireEvent, cleanup } from "@testing-library/react";
33
import "@testing-library/jest-dom/extend-expect";
44
import SearchForm, { TEST_IDS } from "./SearchForm";
5-
import { LANGUAGES } from "./LanguageInput";
5+
import { SearchProvider } from "App";
66

77
afterEach(cleanup);
88

9+
const renderWithProvider = () =>
10+
render(
11+
<SearchProvider>
12+
<SearchForm />
13+
</SearchProvider>
14+
);
15+
916
test("`LabelInput` adds a label to `labelArray`", () => {
10-
const { getByLabelText, getByTestId } = render(<SearchForm />);
17+
const { getByLabelText, getByTestId } = renderWithProvider();
1118

1219
const labelInput = getByLabelText(/label/i);
1320
const activeLabels = getByTestId(TEST_IDS.activeLabels);
@@ -24,16 +31,16 @@ test("`LabelInput` adds a label to `labelArray`", () => {
2431
});
2532

2633
test("`LanguageInput` selection works", () => {
27-
const { getByLabelText } = render(<SearchForm />);
34+
const { getByLabelText } = renderWithProvider();
2835

2936
const languageInput = getByLabelText(/language/i);
3037

31-
fireEvent.change(languageInput, { target: { value: LANGUAGES[1] } });
32-
expect(languageInput).toHaveValue(LANGUAGES[1]);
38+
fireEvent.change(languageInput, { target: { value: "Haskell" } });
39+
expect(languageInput).toHaveValue("Haskell");
3340
});
3441

3542
test("`KeywordsInput` selection works", () => {
36-
const { getByLabelText } = render(<SearchForm />);
43+
const { getByLabelText } = renderWithProvider();
3744

3845
const keywordsInput = getByLabelText(/keywords/i);
3946

@@ -42,7 +49,7 @@ test("`KeywordsInput` selection works", () => {
4249
});
4350

4451
test("`SearchForm` form submit works", () => {
45-
const { getByLabelText } = render(<SearchForm />);
52+
const { getByLabelText } = renderWithProvider();
4653

4754
const keywordsInput = getByLabelText(/keywords/i);
4855
const languageInput = getByLabelText(/language/i);
@@ -51,7 +58,7 @@ test("`SearchForm` form submit works", () => {
5158
fireEvent.change(keywordsInput, { target: { value: "Hello" } });
5259
fireEvent.change(labelInput, { target: { value: "Foo" } });
5360
fireEvent.keyDown(labelInput, { keyCode: 13 });
54-
fireEvent.change(languageInput, { target: { value: LANGUAGES[1] } });
61+
fireEvent.change(languageInput, { target: { value: "Any" } });
5562

5663
// @TODO: Implement the `handleSubmit` test
5764
});

src/store/actions/index.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import {
22
ADD_SELECTED_LABEL,
33
REMOVE_SELECTED_LABEL,
44
SET_SELECTED_LANGUAGE,
5-
SET_KEYWORDS
5+
SET_KEYWORDS,
6+
SET_CURRENT_LABEL
67
} from "store/constants";
78

89
export const addSelectedLabel = label => ({
@@ -24,3 +25,8 @@ export const setKeywords = keywords => ({
2425
type: SET_KEYWORDS,
2526
payload: keywords
2627
});
28+
29+
export const setCurrentLabel = label => ({
30+
type: SET_CURRENT_LABEL,
31+
payload: label
32+
});

src/store/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export const ADD_SELECTED_LABEL = "ADD_SELECTED_LABEL";
22
export const REMOVE_SELECTED_LABEL = "REMOVE_SELECTED_LABEL";
3+
export const SET_CURRENT_LABEL = "SET_CURRENT_LABEL";
34
export const SET_SELECTED_LANGUAGE = "SET_SELECTED_LANGUAGE";
45
export const SET_KEYWORDS = "SET_KEYWORDS";
56
export const FETCH_RESULTS_INIT = "FETCH_RESULTS_INIT";

src/store/reducer/search.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import {
33
ADD_SELECTED_LABEL,
44
REMOVE_SELECTED_LABEL,
55
SET_SELECTED_LANGUAGE,
6-
SET_KEYWORDS
6+
SET_KEYWORDS,
7+
SET_CURRENT_LABEL
78
} from "store/constants";
89

910
const DEFAULT_LABELS = ["good first issue", "help wanted", "first-timers-only"];
@@ -20,6 +21,7 @@ const LANGUAGES = [
2021

2122
const initialState = {
2223
keywords: "",
24+
currentLabel: "",
2325
suggestedLabels: DEFAULT_LABELS,
2426
selectedLabels: DEFAULT_LABELS,
2527
languages: LANGUAGES,
@@ -46,11 +48,18 @@ const selectedLabels = (state = initialState.selectedLabels, action) => {
4648
const reducer = (state = initialState, action) => {
4749
switch (action.type) {
4850
case ADD_SELECTED_LABEL:
51+
return {
52+
...state,
53+
currentLabel: "",
54+
selectedLabels: selectedLabels(state.selectedLabels, action)
55+
};
4956
case REMOVE_SELECTED_LABEL:
5057
return {
5158
...state,
5259
selectedLabels: selectedLabels(state.selectedLabels, action)
5360
};
61+
case SET_CURRENT_LABEL:
62+
return { ...state, currentLabel: action.payload };
5463
case SET_SELECTED_LANGUAGE:
5564
return { ...state, selectedLanguage: action.payload };
5665
case SET_KEYWORDS:

0 commit comments

Comments
 (0)