Use Actions To Manipulate The Store
Contents
15. Use Actions To Manipulate The Store#
15.1. Wiring The Store#
Now that we have our store ready, it's time to connect the store to our code and remove all the unneeded functionality.
The first step is to factor out the Faq
component into a separate file called components/Faq.jsx
.
It is almost an exact copy of App.js
:
1import { useState } from "react";
2import FaqItem from "./FaqItem";
3
4function Faq() {
5 const [faqList, setFaqList] = useState([
6 {
7 question: "What does the Plone Foundation do?",
8 answer: "The mission of the Plone Foundation is to protect and...",
9 },
10 {
11 question: "Why does Plone need a Foundation?",
12 answer: "Plone has reached critical mass, with enterprise...",
13 },
14 ]);
15
16 const [question, setQuestion] = useState("");
17 const [answer, setAnswer] = useState("");
18
19 const onDelete = (index) => {
20 let faq = [...faqList];
21 faq.splice(index, 1);
22 setFaqList(faq);
23 };
24
25 const onChangeAnswer = (e) => {
26 setAnswer(e.target.value);
27 };
28
29 const onChangeQuestion = (e) => {
30 setQuestion(e.target.value);
31 };
32
33 const onEdit = (index, question, answer) => {
34 const faq = [...faqList];
35 faq[index] = { question, answer };
36 setFaqList(faq);
37 };
38
39 const onSubmit = (e) => {
40 e.preventDefault();
41 setFaqList([...faqList, { question, answer }]);
42 setQuestion("");
43 setAnswer("");
44 };
45
46 return (
47 <div>
48 <ul>
49 {faqList.map((item, index) => (
50 <FaqItem
51 key={index}
52 question={item.question}
53 answer={item.answer}
54 index={index}
55 onDelete={onDelete}
56 onEdit={onEdit}
57 />
58 ))}
59 </ul>
60 <form onSubmit={onSubmit}>
61 <label>
62 Question:{" "}
63 <input
64 name="question"
65 type="text"
66 value={question}
67 onChange={onChangeQuestion}
68 />
69 </label>
70 <label>
71 Answer:{" "}
72 <textarea name="answer" value={answer} onChange={onChangeAnswer} />
73 </label>
74 <input type="submit" value="Add" />
75 </form>
76 </div>
77 );
78}
79
80export default Faq;
Next we will create an App
component with just the store and a reference to our newly created Faq
component:
1import { Provider } from "react-redux";
2import { createStore } from "redux";
3
4import rootReducer from "./reducers";
5import Faq from "./components/Faq";
6
7import "./App.css";
8
9const store = createStore(rootReducer);
10
11const App = () => {
12 return (
13 <Provider store={store}>
14 <Faq />
15 </Provider>
16 );
17};
18
19export default App;
Differences
--- a/src/App.js
+++ b/src/App.js
@@ -1,81 +1,19 @@
-import { useState } from "react";
-import "./App.css";
-import FaqItem from "./components/FaqItem";
-
-function App() {
- const [faqList, setFaqList] = useState([
- {
- question: "What does the Plone Foundation do?",
- answer: "The mission of the Plone Foundation is to protect and...",
- },
- {
- question: "Why does Plone need a Foundation?",
- answer: "Plone has reached critical mass, with enterprise...",
- },
- ]);
-
- const [question, setQuestion] = useState("");
- const [answer, setAnswer] = useState("");
+import { Provider } from "react-redux";
+import { createStore } from "redux";
- const onDelete = (index) => {
- let faq = [...faqList];
- faq.splice(index, 1);
- setFaqList(faq);
- };
+import rootReducer from "./reducers";
+import Faq from "./components/Faq";
- const onChangeAnswer = (e) => {
- setAnswer(e.target.value);
- };
-
- const onChangeQuestion = (e) => {
- setQuestion(e.target.value);
- };
-
- const onEdit = (index, question, answer) => {
- const faq = [...faqList];
- faq[index] = { question, answer };
- setFaqList(faq);
- };
+import "./App.css";
- const onSubmit = (e) => {
- e.preventDefault();
- setFaqList([...faqList, { question, answer }]);
- setQuestion("");
- setAnswer("");
- };
+const store = createStore(rootReducer);
+const App = () => {
return (
- <div>
- <ul>
- {faqList.map((item, index) => (
- <FaqItem
- key={index}
- question={item.question}
- answer={item.answer}
- index={index}
- onDelete={onDelete}
- onEdit={onEdit}
- />
- ))}
- </ul>
- <form onSubmit={onSubmit}>
- <label>
- Question:{" "}
- <input
- name="question"
- type="text"
- value={question}
- onChange={onChangeQuestion}
- />
- </label>
- <label>
- Answer:{" "}
- <textarea name="answer" value={answer} onChange={onChangeAnswer} />
- </label>
- <input type="submit" value="Add" />
- </form>
- </div>
+ <Provider store={store}>
+ <Faq />
+ </Provider>
);
-}
+};
export default App;
15.2. Use The Data From The Store#
Now that we have our store wired, we can start using the store data instead of our local state.
We will use the hook useSelector
for extracting the data from the store, and useDispatch
for dispatching the action which is needed by the component.
2import { useSelector,useDispatch } from "react-redux";
3import { addFaqItem } from "../actions";
We can remove all the edit and delete references, since those will be handled by the FaqItem
to clean up our code.
We will also change the onSubmit
handler to use the addFaqItem
action.
The result will be as follows:
1import { useState } from "react";
2import { useSelector, useDispatch } from "react-redux";
3
4import { addFaqItem } from "../actions";
5import FaqItem from "./FaqItem";
6
7function Faq() {
8 const faqList = useSelector((state) => state.faq);
9 const dispatch = useDispatch();
10
11 const [question, setQuestion] = useState("");
12 const [answer, setAnswer] = useState("");
13
14 const onChangeAnswer = (e) => {
15 setAnswer(e.target.value);
16 };
17
18 const onChangeQuestion = (e) => {
19 setQuestion(e.target.value);
20 };
21
22 const onSubmit = (e) => {
23 e.preventDefault();
24 setQuestion("");
25 dispatch(addFaqItem(question, answer));
26 setAnswer("");
27 };
28
29 return (
30 <div>
31 <ul>
32 {faqList.map((item, index) => (
33 <FaqItem
34 key={index}
35 question={item.question}
36 answer={item.answer}
37 index={index}
38 />
39 ))}
40 </ul>
41 <form onSubmit={onSubmit}>
42 <label>
43 Question:{" "}
44 <input
45 name="question"
46 type="text"
47 value={question}
48 onChange={onChangeQuestion}
49 />
50 </label>
51 <label>
52 Answer:{" "}
53 <textarea name="answer" value={answer} onChange={onChangeAnswer} />
54 </label>
55 <input type="submit" value="Add" />
56 </form>
57 </div>
58 );
59}
60
61export default Faq;
Differences
--- a/src/components/Faq.jsx
+++ b/src/components/Faq.jsx
@@ -1,27 +1,16 @@
import { useState } from "react";
+import { useSelector, useDispatch } from "react-redux";
+
+import { addFaqItem } from "../actions";
import FaqItem from "./FaqItem";
function Faq() {
- const [faqList, setFaqList] = useState([
- {
- question: "What does the Plone Foundation do?",
- answer: "The mission of the Plone Foundation is to protect and...",
- },
- {
- question: "Why does Plone need a Foundation?",
- answer: "Plone has reached critical mass, with enterprise...",
- },
- ]);
+ const faqList = useSelector((state) => state.faq);
+ const dispatch = useDispatch();
const [question, setQuestion] = useState("");
const [answer, setAnswer] = useState("");
- const onDelete = (index) => {
- let faq = [...faqList];
- faq.splice(index, 1);
- setFaqList(faq);
- };
-
const onChangeAnswer = (e) => {
setAnswer(e.target.value);
};
@@ -30,16 +19,10 @@ function Faq() {
setQuestion(e.target.value);
};
- const onEdit = (index, question, answer) => {
- const faq = [...faqList];
- faq[index] = { question, answer };
- setFaqList(faq);
- };
-
const onSubmit = (e) => {
e.preventDefault();
- setFaqList([...faqList, { question, answer }]);
setQuestion("");
+ dispatch(addFaqItem(question, answer));
setAnswer("");
};
@@ -51,8 +34,6 @@ function Faq() {
question={item.question}
answer={item.answer}
index={index}
- onDelete={onDelete}
- onEdit={onEdit}
/>
))}
</ul>
15.3. Exercise#
Now that we factored out the edit and delete actions from the Faq
component, update the FaqItem
component to call the actions we created for our store.
Solution
1import { useState } from "react";
2import { useDispatch } from "react-redux";
3import PropTypes from "prop-types";
4
5import { editFaqItem, deleteFaqItem } from "../actions";
6import "./FaqItem.css";
7
8const FaqItem = (props) => {
9 const [isAnswer, setAnswer] = useState(false);
10 const [isEditMode, setIsEditMode] = useState(false);
11 const [question, setQuestion] = useState("");
12 const [answer, setQuestionAnswer] = useState("");
13 const dispatch = useDispatch();
14
15 const toggle = () => {
16 setAnswer(!isAnswer);
17 };
18 const ondelete = () => {
19 dispatch(deleteFaqItem(props.index));
20 };
21
22 const onEdit = () => {
23 setIsEditMode(true);
24 setQuestionAnswer(props.answer);
25 setQuestion(props.question);
26 };
27
28 const onChangeAnswer = (e) => {
29 setQuestionAnswer(e.target.value);
30 };
31 const onChangeQuestion = (e) => {
32 setQuestion(e.target.value);
33 };
34
35 const onSave = (e) => {
36 e.preventDefault();
37 setIsEditMode(false);
38 dispatch(editFaqItem(props.index, question, answer));
39 };
40
41 return (
42 <>
43 {isEditMode ? (
44 <li className="faq-item">
45 <form onSubmit={onSave}>
46 <label>
47 Question:
48 <input
49 name="question"
50 value={question}
51 onChange={onChangeQuestion}
52 />
53 </label>
54 <label>
55 Answer:
56 <textarea
57 name="answer"
58 value={answer}
59 onChange={onChangeAnswer}
60 />
61 </label>
62 <input type="submit" value="Save" />
63 </form>
64 </li>
65 ) : (
66 <li className="faq-item">
67 <h2 className="question" onClick={toggle}>
68 {props.question}
69 </h2>
70 {isAnswer && <p>{props.answer}</p>}
71 <button onClick={ondelete}>Delete</button>
72 <button onClick={onEdit}>Edit</button>
73 </li>
74 )}
75 </>
76 );
77};
78
79FaqItem.propTypes = {
80 question: PropTypes.string.isRequired,
81 answer: PropTypes.string.isRequired,
82 index: PropTypes.number.isRequired,
83};
84
85export default FaqItem;
--- a/src/components/FaqItem.jsx
+++ b/src/components/FaqItem.jsx
@@ -1,18 +1,22 @@
import { useState } from "react";
-import "./FaqItem.css";
+import { useDispatch } from "react-redux";
import PropTypes from "prop-types";
+import { editFaqItem, deleteFaqItem } from "../actions";
+import "./FaqItem.css";
+
const FaqItem = (props) => {
const [isAnswer, setAnswer] = useState(false);
const [isEditMode, setIsEditMode] = useState(false);
const [question, setQuestion] = useState("");
const [answer, setQuestionAnswer] = useState("");
+ const dispatch = useDispatch();
const toggle = () => {
setAnswer(!isAnswer);
};
const ondelete = () => {
- props.onDelete(props.index);
+ dispatch(deleteFaqItem(props.index));
};
const onEdit = () => {
@@ -31,7 +35,7 @@ const FaqItem = (props) => {
const onSave = (e) => {
e.preventDefault();
setIsEditMode(false);
- props.onEdit(props.index, question, answer);
+ dispatch(editFaqItem(props.index, question, answer));
};
return (
@@ -76,8 +80,6 @@ FaqItem.propTypes = {
question: PropTypes.string.isRequired,
answer: PropTypes.string.isRequired,
index: PropTypes.number.isRequired,
- onDelete: PropTypes.func.isRequired,
- onEdit: PropTypes.func.isRequired,
};