aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorGravatar ks129 <[email protected]>2021-02-21 07:35:29 +0200
committerGravatar ks129 <[email protected]>2021-02-21 07:35:29 +0200
commitb1f05fa57c862ce8219e5ca464e794353261f842 (patch)
treeff67e7265ad52099181ceb0bf2a0af36f0525fdd /src
parentMove hCaptcha types library to dev-dependencies (diff)
Migrate from public state map to Redux
Diffstat (limited to 'src')
-rw-r--r--src/App.tsx4
-rw-r--r--src/components/InputTypes/Checkbox.tsx7
-rw-r--r--src/components/InputTypes/Select.tsx40
-rw-r--r--src/components/InputTypes/index.tsx13
-rw-r--r--src/components/Question.tsx153
-rw-r--r--src/pages/FormPage.tsx38
-rw-r--r--src/store/form/actions.ts79
-rw-r--r--src/store/form/reducers.ts29
-rw-r--r--src/store/form/store.ts4
-rw-r--r--src/store/form/types.ts5
10 files changed, 285 insertions, 87 deletions
diff --git a/src/App.tsx b/src/App.tsx
index 523e583..14e5329 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -3,6 +3,7 @@
import React, { Suspense } from "react";
import { jsx, css, Global } from "@emotion/react";
+import { Provider } from "react-redux";
import {
BrowserRouter as Router,
Route,
@@ -14,6 +15,7 @@ import { PropagateLoader } from "react-spinners";
import { CSSTransition, TransitionGroup } from "react-transition-group";
import globalStyles from "./globalStyles";
+import { store } from "./store/form/store";
const LandingPage = React.lazy(() => import("./pages/LandingPage"));
const FormPage = React.lazy(() => import("./pages/FormPage"));
@@ -51,7 +53,7 @@ function App(): JSX.Element {
{routes.map(({path, Component}) => (
<Route exact key={path} path={path}>
<Suspense fallback={<PageLoading/>}>
- <Component/>
+ {path == "/form/:id" ? <Provider store={store}><Component/></Provider> : <Component/>}
</Suspense>
</Route>
))}
diff --git a/src/components/InputTypes/Checkbox.tsx b/src/components/InputTypes/Checkbox.tsx
index 3093caf..b3130c6 100644
--- a/src/components/InputTypes/Checkbox.tsx
+++ b/src/components/InputTypes/Checkbox.tsx
@@ -3,6 +3,8 @@ import { jsx, css } from "@emotion/react";
import React, { ChangeEvent } from "react";
import colors from "../../colors";
import { multiSelectInput, hiddenInput } from "../../commonStyles";
+import {useSelector} from "react-redux";
+import {FormState} from "../../store/form/types";
interface CheckboxProps {
index: number,
@@ -53,10 +55,13 @@ const activeStyles = css`
`;
export default function Checkbox(props: CheckboxProps): JSX.Element {
+ const values = useSelector<FormState, FormState["values"]>(
+ state => state.values
+ );
return (
<label css={[generalStyles, activeStyles]}>
<label className="unselected" css={multiSelectInput}>
- <input type="checkbox" value={props.option} css={hiddenInput} name={`${("000" + props.index).slice(-4)}. ${props.option}`} onChange={props.handler}/>
+ <input type="checkbox" value={props.option} css={hiddenInput} name={`${("000" + props.index).slice(-4)}. ${props.option}`} onChange={props.handler} checked={!!values.get(`${("000" + props.index).slice(-4)}. ${props.option}`)}/>
<span className="checkmark"/>
</label>
{props.option}<br/>
diff --git a/src/components/InputTypes/Select.tsx b/src/components/InputTypes/Select.tsx
index 2d0187a..c0f5425 100644
--- a/src/components/InputTypes/Select.tsx
+++ b/src/components/InputTypes/Select.tsx
@@ -1,13 +1,26 @@
/** @jsx jsx */
import { jsx, css } from "@emotion/react";
import React from "react";
+import { connect } from "react-redux";
+
import { hiddenInput, invalidStyles } from "../../commonStyles";
+import { Question } from "../../api/question";
+import { setValue, SetValueAction } from "../../store/form/actions";
+import { FormState } from "../../store/form/types";
interface SelectProps {
options: Array<string>,
- state_dict: Map<string, string | boolean | null>,
valid: boolean,
- onBlurHandler: () => void
+ onBlurHandler: () => void,
+ question: Question
+}
+
+interface SelectStateProps {
+ values: Map<string, string | Map<string, boolean> | null>
+}
+
+interface SelectDispatchProps {
+ setValue: (question: Question, value: string | Map<string, boolean> | null) => SetValueAction
}
const containerStyles = css`
@@ -143,7 +156,7 @@ const optionStyles = css`
}
`;
-class Select extends React.Component<SelectProps> {
+class Select extends React.Component<SelectProps & SelectStateProps & SelectDispatchProps> {
handler(selected_option: React.RefObject<HTMLDivElement>, event: React.ChangeEvent<HTMLInputElement>): void {
const option_container = event.target.parentElement;
if (!option_container || !option_container.parentElement || !selected_option.current) {
@@ -151,7 +164,7 @@ class Select extends React.Component<SelectProps> {
}
// Update stored value
- this.props.state_dict.set("value", option_container.textContent);
+ this.props.setValue(this.props.question, option_container.textContent);
// Close the menu
selected_option.current.focus();
@@ -178,10 +191,10 @@ class Select extends React.Component<SelectProps> {
}
focusOption(): void {
- if (!this.props.state_dict.get("value")) {
- this.props.state_dict.set("value", "temporary");
+ if (!this.props.values.get(this.props.question.id)) {
+ this.props.setValue(this.props.question, "temporary");
this.props.onBlurHandler();
- this.props.state_dict.set("value", null);
+ this.props.setValue(this.props.question, null);
}
}
@@ -211,4 +224,15 @@ class Select extends React.Component<SelectProps> {
}
}
-export default Select;
+const mapStateToProps = (state: FormState, ownProps: SelectProps): SelectProps & SelectStateProps => {
+ return {
+ ...ownProps,
+ values: state.values
+ };
+};
+
+const mapDispatchToProps = {
+ setValue
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(Select);
diff --git a/src/components/InputTypes/index.tsx b/src/components/InputTypes/index.tsx
index bc65248..e816c9c 100644
--- a/src/components/InputTypes/index.tsx
+++ b/src/components/InputTypes/index.tsx
@@ -6,10 +6,10 @@ import Select from "./Select";
import ShortText from "./ShortText";
import TextArea from "./TextArea";
-import React, { ChangeEvent } from "react";
+import React, {ChangeEvent} from "react";
-import { QuestionType } from "../../api/question";
-import { QuestionProp } from "../Question";
+import {QuestionType} from "../../api/question";
+import {QuestionDispatchProp, QuestionProp, QuestionStateProp} from "../Question";
const require_options: Array<QuestionType> = [
QuestionType.Radio,
@@ -19,14 +19,15 @@ const require_options: Array<QuestionType> = [
];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export default function create_input({ question, public_state }: QuestionProp, handler: (event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void, onBlurHandler: () => void, focus_ref: React.RefObject<any>): JSX.Element | JSX.Element[] {
+export default function create_input(props: QuestionProp & QuestionStateProp & QuestionDispatchProp, handler: (event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void, onBlurHandler: () => void, focus_ref: React.RefObject<any>): JSX.Element | JSX.Element[] {
let result: JSX.Element | JSX.Element[];
+ const question = props.question;
// eslint-disable-next-line
// @ts-ignore
let options: string[] = question.data["options"];
let valid = true;
- if (!public_state.get("valid")) {
+ if (!props.valid.get(question.id)) {
valid = false;
}
@@ -51,7 +52,7 @@ export default function create_input({ question, public_state }: QuestionProp, h
break;
case QuestionType.Select:
- result = <Select options={options} state_dict={public_state} valid={valid} onBlurHandler={onBlurHandler}/>;
+ result = <Select options={options} question={question} valid={valid} onBlurHandler={onBlurHandler}/>;
break;
case QuestionType.ShortText:
diff --git a/src/components/Question.tsx b/src/components/Question.tsx
index 0af745e..74c4a71 100644
--- a/src/components/Question.tsx
+++ b/src/components/Question.tsx
@@ -1,11 +1,14 @@
/** @jsx jsx */
import { jsx, css } from "@emotion/react";
import React, { ChangeEvent } from "react";
+import { connect } from "react-redux";
import { Question, QuestionType } from "../api/question";
import { selectable } from "../commonStyles";
import create_input from "./InputTypes";
import ErrorMessage from "./ErrorMessage";
+import { FormState } from "../store/form/types";
+import { setError, SetErrorAction, setValid, SetValidAction, setValue, SetValueAction } from "../store/form/actions";
const skip_normal_state: Array<QuestionType> = [
QuestionType.Radio,
@@ -17,14 +20,25 @@ const skip_normal_state: Array<QuestionType> = [
export type QuestionProp = {
question: Question,
- public_state: Map<string, string | boolean | null>,
scroll_ref: React.RefObject<HTMLDivElement>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
focus_ref: React.RefObject<any>
}
-class RenderedQuestion extends React.Component<QuestionProp> {
- constructor(props: QuestionProp) {
+export type QuestionStateProp = {
+ values: Map<string, string | Map<string, boolean> | null>,
+ errors: Map<string, string>,
+ valid: Map<string, boolean>,
+};
+
+export type QuestionDispatchProp = {
+ setValue: (question: Question, value: string | Map<string, boolean> | null) => SetValueAction,
+ setValid: (question: Question, valid: boolean) => SetValidAction,
+ setError: (question: Question, error: string) => SetErrorAction
+};
+
+export class RenderedQuestion extends React.Component<QuestionProp & QuestionStateProp & QuestionDispatchProp> {
+ constructor(props: QuestionProp & QuestionStateProp & QuestionDispatchProp) {
super(props);
if (props.question.type === QuestionType.TextArea) {
this.handler = this.text_area_handler.bind(this);
@@ -33,47 +47,39 @@ class RenderedQuestion extends React.Component<QuestionProp> {
}
this.blurHandler = this.blurHandler.bind(this);
- this.setPublicState("valid", true);
- this.setPublicState("error", "");
+ props.setValid(props.question, true);
+ props.setError(props.question, "");
if (!skip_normal_state.includes(props.question.type)) {
- this.setPublicState("value", "");
+ props.setValue(props.question, "");
}
}
- setPublicState(target: string, value: string | boolean | null, callback?:() => void): void {
- this.setState({[target]: value}, callback);
- this.props.public_state.set(target, value);
- }
-
// This is here to allow dynamic selection between the general handler, and the textarea handler.
handler(_: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>): void {} // eslint-disable-line
blurHandler(): void {
if (this.props.question.required) {
- if (!this.props.public_state.get("value")) {
- this.setPublicState("error", "Field must be filled.");
- this.setPublicState("valid", false);
+ if (!this.props.values.get(this.props.question.id)) {
+ this.props.setError(this.props.question, "Field must be filled.");
+ this.props.setValid(this.props.question, false);
} else {
- this.setPublicState("error", "");
- this.setPublicState("valid", true);
+ this.props.setError(this.props.question, "");
+ this.props.setValid(this.props.question, true);
}
}
}
normal_handler(event: ChangeEvent<HTMLInputElement>): void {
- let target: string;
- let value: string | boolean;
+ let value: string | [string, boolean];
switch (event.target.type) {
case "checkbox":
- target = event.target.name;
- value = event.target.checked;
+ value = [event.target.name, event.target.checked];
break;
case "radio":
- // This handles radios and ranges, as they are both based on the same fundamental input type
- target = "value";
+ // This handles radios and ranges, as they are both based on the same fundamental input type
if (event.target.parentElement) {
value = event.target.parentElement.innerText.trimEnd();
} else {
@@ -82,11 +88,19 @@ class RenderedQuestion extends React.Component<QuestionProp> {
break;
default:
- target = "value";
value = event.target.value;
}
- this.setPublicState(target, value);
+ if (value instanceof Array) {
+ let values = this.props.values.get(this.props.question.id);
+ if (!(values instanceof Map)) {
+ values = new Map<string, boolean>();
+ }
+ values.set(value[0], value[1]);
+ this.props.setValue(this.props.question, values);
+ } else {
+ this.props.setValue(this.props.question, value);
+ }
// Toggle checkbox class
if (event.target.type == "checkbox" && event.target.parentElement !== null) {
@@ -97,7 +111,7 @@ class RenderedQuestion extends React.Component<QuestionProp> {
const options: string | string[] = this.props.question.data["options"];
switch (event.target.type) {
case "text":
- this.setPublicState("valid", true);
+ this.props.setValid(this.props.question, true);
break;
case "checkbox":
@@ -107,29 +121,30 @@ class RenderedQuestion extends React.Component<QuestionProp> {
options.forEach((val, index) => {
keys.push(`${("000" + index).slice(-4)}. ${val}`);
});
- if (keys.every(v => !this.props.public_state.get(v))) {
- this.setPublicState("error", "Field must be filled.");
- this.setPublicState("valid", false);
+ const values = this.props.values.get(this.props.question.id);
+ if (values instanceof Map && keys.every(v => !values.get(v))) {
+ this.props.setError(this.props.question, "Field must be filled.");
+ this.props.setValid(this.props.question, false);
} else {
- this.setPublicState("error", "");
- this.setPublicState("valid", true);
+ this.props.setError(this.props.question, "");
+ this.props.setValid(this.props.question, true);
}
}
break;
case "radio":
- this.setPublicState("valid", true);
- this.setPublicState("error", "");
+ this.props.setError(this.props.question, "");
+ this.props.setValid(this.props.question, true);
break;
}
}
text_area_handler(event: ChangeEvent<HTMLTextAreaElement>): void {
// We will validate again when focusing out.
- this.setPublicState("valid", true);
- this.setPublicState("error", "");
+ this.props.setError(this.props.question, "");
+ this.props.setValid(this.props.question, true);
- this.setPublicState("value", event.target.value);
+ this.props.setValue(this.props.question, event.target.value);
}
validateField(): void {
@@ -142,7 +157,7 @@ class RenderedQuestion extends React.Component<QuestionProp> {
switch (this.props.question.type) {
case QuestionType.TextArea:
case QuestionType.ShortText:
- if (this.props.public_state.get("value") === "") {
+ if (this.props.values.get(this.props.question.id) === "") {
invalid = true;
}
break;
@@ -150,7 +165,7 @@ class RenderedQuestion extends React.Component<QuestionProp> {
case QuestionType.Select:
case QuestionType.Range:
case QuestionType.Radio:
- if (!this.props.public_state.get("value")) {
+ if (!this.props.values.get(this.props.question.id)) {
invalid = true;
}
break;
@@ -161,7 +176,8 @@ class RenderedQuestion extends React.Component<QuestionProp> {
options.forEach((val, index) => {
keys.push(`${("000" + index).slice(-4)}. ${val}`);
});
- if (keys.every(v => !this.props.public_state.get(v))) {
+ const values = this.props.values.get(this.props.question.id);
+ if (values instanceof Map && keys.every(v => !values.get(v))) {
invalid = true;
}
}
@@ -169,36 +185,36 @@ class RenderedQuestion extends React.Component<QuestionProp> {
}
if (invalid) {
- this.setPublicState("error", "Field must be filled.");
- this.setPublicState("valid", false);
+ this.props.setError(this.props.question, "Field must be filled.");
+ this.props.setValid(this.props.question, false);
} else {
- this.setPublicState("error", "");
- this.setPublicState("valid", true);
+ this.props.setError(this.props.question, "");
+ this.props.setValid(this.props.question, true);
}
}
componentDidMount(): void {
// Initialize defaults for complex and nested fields
const options: string | string[] = this.props.question.data["options"];
+ const values = this.props.values.get(this.props.question.id);
- if (this.props.public_state.size === 0) {
- switch (this.props.question.type) {
- case QuestionType.Checkbox:
- if (typeof options === "string") {
- return;
- }
+ switch (this.props.question.type) {
+ case QuestionType.Checkbox:
+ if (typeof options === "string" || !(values instanceof Map)) {
+ return;
+ }
- options.forEach((option, index) => {
- this.setPublicState(`${("000" + index).slice(-4)}. ${option}`, false);
- });
- break;
+ options.forEach((option, index) => {
+ values.set(`${("000" + index).slice(-4)}. ${option}`, false);
+ });
+ this.props.setValue(this.props.question, values);
+ break;
- case QuestionType.Range:
- case QuestionType.Radio:
- case QuestionType.Select:
- this.setPublicState("value", null);
- break;
- }
+ case QuestionType.Range:
+ case QuestionType.Radio:
+ case QuestionType.Select:
+ this.props.setValue(this.props.question, null);
+ break;
}
}
@@ -250,10 +266,10 @@ class RenderedQuestion extends React.Component<QuestionProp> {
}
`;
let valid = true;
- if (!this.props.public_state.get("valid")) {
+ if (!this.props.valid.get(this.props.question.id)) {
valid = false;
}
- const rawError = this.props.public_state.get("error");
+ const rawError = this.props.errors.get(this.props.question.id);
let error = "";
if (typeof rawError === "string") {
error = rawError;
@@ -271,4 +287,19 @@ class RenderedQuestion extends React.Component<QuestionProp> {
}
}
-export default RenderedQuestion;
+const mapStateToProps = (state: FormState, ownProps: QuestionProp): QuestionProp & QuestionStateProp => {
+ return {
+ ...ownProps,
+ values: state.values,
+ errors: state.errors,
+ valid: state.valid
+ };
+};
+
+const mapDispatchToProps = {
+ setValue,
+ setValid,
+ setError
+};
+
+export default connect(mapStateToProps, mapDispatchToProps, null, { forwardRef: true })(RenderedQuestion);
diff --git a/src/pages/FormPage.tsx b/src/pages/FormPage.tsx
index 1e331b9..e9306e3 100644
--- a/src/pages/FormPage.tsx
+++ b/src/pages/FormPage.tsx
@@ -5,9 +5,10 @@ import { Link } from "react-router-dom";
import React, { SyntheticEvent, useEffect, useState, createRef } from "react";
import { useParams } from "react-router";
import { PropagateLoader } from "react-spinners";
+import { useDispatch, useSelector } from "react-redux";
import HeaderBar from "../components/HeaderBar";
-import RenderedQuestion from "../components/Question";
+import ConnectedRenderedQuestion, { RenderedQuestion } from "../components/Question";
import Loading from "../components/Loading";
import ScrollToTop from "../components/ScrollToTop";
@@ -16,6 +17,8 @@ import colors from "../colors";
import { unselectable } from "../commonStyles";
import { Question, QuestionType } from "../api/question";
import ApiClient from "../api/client";
+import { FormState } from "../store/form/types";
+import { clean } from "../store/form/actions";
interface PathParams {
id: string
@@ -157,11 +160,21 @@ const closedHeaderStyles = css`
function FormPage(): JSX.Element {
const { id } = useParams<PathParams>();
+ const valid = useSelector<FormState, FormState["valid"]>(
+ state => state.valid
+ );
+ const values = useSelector<FormState, FormState["values"]>(
+ state => state.values
+ );
+
+ const dispatch = useDispatch();
+
const [form, setForm] = useState<Form>();
const [sending, setSending] = useState<boolean>();
const [sent, setSent] = useState<boolean>();
useEffect(() => {
+ dispatch(clean());
getForm(id).then(form => {
setForm(form);
});
@@ -203,7 +216,7 @@ function FormPage(): JSX.Element {
const questionRef = createRef<RenderedQuestion>();
refMap.set(question.id, questionRef);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- return <RenderedQuestion ref={questionRef} focus_ref={createRef<any>()} scroll_ref={createRef<HTMLDivElement>()} question={question} public_state={new Map()} key={index + Date.now()}/>;
+ return <ConnectedRenderedQuestion ref={questionRef} focus_ref={createRef<any>()} scroll_ref={createRef<HTMLDivElement>()} question={question} key={index + Date.now()}/>;
});
async function handleSubmit(event: SyntheticEvent) {
@@ -220,8 +233,8 @@ function FormPage(): JSX.Element {
if (questionRef && questionRef.current) {
questionRef.current.validateField();
}
- // In case when field is invalid, add this to invalid fields list.
- if (prop.props.public_state.get("valid") === false) {
+ // In case when field is invalid, add this to invalid fields list
+ if (valid.get(question.id) === false) {
invalidFieldIDs.push(i);
}
});
@@ -257,22 +270,27 @@ function FormPage(): JSX.Element {
case QuestionType.Checkbox: {
if (typeof options !== "string") {
+ console.log(values);
+ const checkbox_values = values.get(question.id);
+ console.log(checkbox_values);
const keys: Map<string, string> = new Map();
options.forEach((val: string, index) => {
keys.set(val, `${("000" + index).slice(-4)}. ${val}`);
});
- const pairs: { [key: string]: boolean } = { };
- keys.forEach((val, key) => {
- pairs[key] = !!prop.props.public_state.get(val);
- });
- answers[question.id] = pairs;
+ if (checkbox_values instanceof Map) {
+ const pairs: { [key: string]: boolean } = { };
+ keys.forEach((val, key) => {
+ pairs[key] = !!checkbox_values.get(val);
+ });
+ answers[question.id] = pairs;
+ }
}
break;
}
case QuestionType.Code:
default:
- answers[question.id] = prop.props.public_state.get("value");
+ answers[question.id] = values.get(question.id);
}
});
diff --git a/src/store/form/actions.ts b/src/store/form/actions.ts
new file mode 100644
index 0000000..8b48a37
--- /dev/null
+++ b/src/store/form/actions.ts
@@ -0,0 +1,79 @@
+import { Question } from "../../api/question";
+
+// All Redux actions that can be triggered
+export enum FormAction {
+ SET_VALUE = "SET_VALUE",
+ SET_ERROR = "SET_ERROR",
+ SET_VALID = "SET_VALID",
+ CLEAN = "CLEAN"
+}
+
+// This is base for all actions
+export interface DefaultFormAction {
+ type: FormAction
+}
+
+// Return values for actions
+export interface SetValueAction extends DefaultFormAction {
+ type: FormAction.SET_VALUE,
+ payload: {
+ question: Question,
+ value: string | Map<string, boolean> | null
+ }
+}
+
+export interface SetErrorAction extends DefaultFormAction {
+ type: FormAction.SET_ERROR,
+ payload: {
+ question: Question,
+ error: string
+ }
+}
+
+export interface SetValidAction extends DefaultFormAction {
+ type: FormAction.SET_VALID,
+ payload: {
+ question: Question,
+ valid: boolean
+ }
+}
+
+export interface CleanAction extends DefaultFormAction {
+ type: FormAction.CLEAN
+}
+
+export type Action = SetValueAction | SetErrorAction | SetValidAction | CleanAction;
+
+export function setValue(question: Question, value: string | Map<string, boolean> | null): SetValueAction {
+ return {
+ type: FormAction.SET_VALUE,
+ payload: {
+ question: question,
+ value: value
+ }
+ };
+}
+
+export function setError(question: Question, error: string): SetErrorAction {
+ return {
+ type: FormAction.SET_ERROR,
+ payload: {
+ question: question,
+ error: error
+ }
+ };
+}
+
+export function setValid(question: Question, valid: boolean): SetValidAction {
+ return {
+ type: FormAction.SET_VALID,
+ payload: {
+ question: question,
+ valid: valid
+ }
+ };
+}
+
+export function clean(): CleanAction {
+ return { type: FormAction.CLEAN };
+}
diff --git a/src/store/form/reducers.ts b/src/store/form/reducers.ts
new file mode 100644
index 0000000..fcbb33a
--- /dev/null
+++ b/src/store/form/reducers.ts
@@ -0,0 +1,29 @@
+import {Action, FormAction} from "./actions";
+import {FormState} from "./types";
+
+export const initialState: FormState = {
+ values: new Map(),
+ errors: new Map(),
+ valid: new Map()
+};
+
+export function formReducer(state = initialState, action: Action): FormState {
+ const new_state = state;
+ switch (action.type) {
+ case FormAction.SET_VALUE:
+ new_state.values.set(action.payload.question.id, action.payload.value);
+ break;
+
+ case FormAction.SET_ERROR:
+ new_state.errors.set(action.payload.question.id, action.payload.error);
+ break;
+
+ case FormAction.SET_VALID:
+ new_state.valid.set(action.payload.question.id, action.payload.valid);
+ break;
+
+ case FormAction.CLEAN:
+ return initialState;
+ }
+ return new_state;
+}
diff --git a/src/store/form/store.ts b/src/store/form/store.ts
new file mode 100644
index 0000000..4311a2f
--- /dev/null
+++ b/src/store/form/store.ts
@@ -0,0 +1,4 @@
+import { createStore } from "redux";
+import { formReducer } from "./reducers";
+
+export const store = createStore(formReducer);
diff --git a/src/store/form/types.ts b/src/store/form/types.ts
new file mode 100644
index 0000000..1d3e594
--- /dev/null
+++ b/src/store/form/types.ts
@@ -0,0 +1,5 @@
+export interface FormState {
+ values: Map<string, string | Map<string, boolean> | null>,
+ errors: Map<string, string>,
+ valid: Map<string, boolean>
+}