💻
Building and hosting a WebApp
  • Getting started
  • Project setup
    • Requirements
    • Files organisation
    • Lerna
    • Linter
    • Prettier
    • GitHook
    • Testing
    • Conclusion
  • Backend
    • Files organisation
    • Environment config
    • Express API
    • Security
    • Database
    • GraphQL
    • User authentication
    • Conclusion
  • Frontend
    • Create React App
    • Files organisation
    • Styles
    • Apollo Hooks
    • Form management
    • User authentication
    • Writing tests
    • Types generation
    • Conclusion
  • DevOps
    • CI/CD
    • AWS
      • Managing secrets
      • Pricing
      • RDS
      • S3
      • Route53
      • CloudFront
      • Serverless
      • Security
      • CloudFormation
    • Conclusion
  • 🚧Stripe payment
  • 🚧File upload
Powered by GitBook
On this page

Was this helpful?

  1. Frontend

Form management

PreviousApollo HooksNextUser authentication

Last updated 5 years ago

Was this helpful?

Before we can identify the user when we receive a request, we need to create a form to collect credentials.

We will use to build our forms.

$ yarn add @types/react formik
import * as React from "react";
import { FieldProps } from "formik";
import styled from "styled-components";
import { theme } from "../../config";

const Container = styled.div``;
const StyledInput = styled.input`
  border: 1px solid ${theme.colors.border};
  border-radius: 4px;
  padding: 8px;
  font-size: 14px;
  width: 100%;
  box-sizing: border-box;

  &:focus {
    border-color: ${theme.colors.background2};
    outline: none;
  }
`;
const Error = styled.div`
  color: red;
`;

export const Input: React.SFC<FieldProps<any>> = ({
  field, // { name, value, onChange, onBlur }
  form: { touched, errors }, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
  ...props
}) => (
  <Container>
    <StyledInput type="text" {...field} {...props} />
    {touched[field.name] && errors[field.name] && (
      <Error>{errors[field.name]}</Error>
    )}
  </Container>
);
import * as React from "react";
import styled from "styled-components";
import { theme } from "../../config";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCircleNotch } from "@fortawesome/free-solid-svg-icons";
import { ButtonHTMLAttributes } from "react";

export interface ButtonProps {
  isLoading?: boolean;
  color: keyof typeof theme.colors;
  onClick?: () => void;
}

const StyledButton = styled.button<ButtonProps>`
  border: 1px solid ${props => theme.colors[props.color]};
  background-color: ${props => theme.colors[props.color]};
  color: ${theme.colors.contrast2};
  border-radius: 4px;
  padding: 8px 20px;
  font-size: 12px;
  cursor: pointer;
  width: 100%;

  &:disabled {
    opacity: 0.5;
  }
`;

const StyledIcon = styled(FontAwesomeIcon)`
  margin: 0 !important;
`;

export const Button: React.SFC<
  ButtonProps & ButtonHTMLAttributes<HTMLButtonElement>
> = props => {
  return (
    <StyledButton {...props}>
      {props.isLoading ? (
        <StyledIcon spin icon={faCircleNotch} />
      ) : (
        props.children
      )}
    </StyledButton>
  );
};
import * as React from "react";
import { useMutation } from "react-apollo";
import { gql } from "apollo-boost";
import { Formik, Field, Form, FormikActions } from "formik";
import styled from "styled-components";
import { Input } from "../../components/Input";
import { Button } from "../../components/Button";
import { theme } from "../../config";
import { useHistory } from "react-router";

interface FormValues {
  emailAddress: string;
  password: string;
}

const Container = styled.div`
  max-width: 400px;
  margin-right: auto;
  margin-left: auto;
  box-shadow: 0px 0px 10px lightgrey;
  padding: 30px 50px 50px;
  margin-top: 10vh;
`;

const Title = styled.h1`
  margin: 20px 0;
  font-size: 20px;
  font-weight: bold;
`;

const StyledForm = styled(Form)`
  input {
    margin-bottom: 20px;
  }
  button {
    margin-top: 20px;
  }
`;

const StyledErrorMessage = styled.div`
  color: ${theme.colors.negative};
`;

export const SignupPage = () => {
  const history = useHistory();
  const [signUpMutation, { loading, error }] = useMutation(
    gql`
      mutation signUp($input: UserAuthInput!) {
        signUp(input: $input) {
          id
          emailAddress
          jwt
        }
      }
    `,
    {
      onCompleted({ signUp }) {
        localStorage.setItem("token", signUp.jwt);
        history.push("/");
      }
    }
  );

  return (
    <Container>
      <Title>Create an account</Title>
      <Formik
        initialValues={{
          emailAddress: "",
          password: ""
        }}
        onSubmit={async (
          values: FormValues,
          { setSubmitting }: FormikActions<FormValues>
        ) => {
          signUpMutation({ variables: { input: values } });
          setSubmitting(false);
        }}
        render={() => (
          <StyledForm>
            <Field
              component={Input}
              id="emailAddress"
              name="emailAddress"
              placeholder="Email address"
              type="email"
            />

            <Field
              component={Input}
              id="password"
              name="password"
              placeholder="Password"
              type="password"
              autoComplete="new-password"
            />
            {error && (
              <StyledErrorMessage>
                {error.graphQLErrors[0].message}
              </StyledErrorMessage>
            )}

            <Button isLoading={loading} color="positive" type="submit">
              Submit
            </Button>
          </StyledForm>
        )}
      />
    </Container>
  );
};
App.tsx
import React, { Suspense, lazy } from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import reset from "styled-reset";
import ApolloClient from "apollo-boost";
import { ApolloProvider } from "react-apollo";
import { LoadingScreen } from "./components/Loading";
import { Header } from "./components/Header";
import { createGlobalStyle } from "styled-components";

const SignupPage = lazy(() => import("./pages/signup"));
const LinksPage = lazy(() => import("./pages/links"));

const client = new ApolloClient({ uri: "/graphql" });

const GlobalStyle = createGlobalStyle`
  ${reset}
  body {
    font-family: "Open Sans", sans-serif;
  }
  a {
    text-decoration: none;
    color: inherit;
  }
`;

const App: React.FC = () => {
  return (
    <ApolloProvider client={client}>
      <GlobalStyle />
      <div className="App">
        <Router>
          <Header />
          <Switch>
            <Route path="/signup">
              <Suspense fallback={<LoadingScreen />}>
                <SignupPage />
              </Suspense>
            </Route>
            <Route path="/users">
              <Suspense fallback={<LoadingScreen />}>
                <LinksPage />
              </Suspense>
            </Route>
            <Route path="/">
              <div>Home</div>
            </Route>
          </Switch>
        </Router>
      </div>
    </ApolloProvider>
  );
};

export default App;

This cover everything needed to interact with the GraphQL API and display the results.

We need 2 components to build our form, Input and Buttonwhich we can then use to build our form. We send the request to the server using .

We also use and :

branch and with comments available on GitHub.

useMutation
React Router
Lazy
form
pull request
Formik