Skip to content

08 React Frontend

JP Barbosa edited this page Apr 15, 2023 · 2 revisions

React Frontend

Install Required Packages

npm install axios react-hook-form react-query

Remove Unused Files

rm ./packages/web/src/styles.scss
rm ./packages/web/src/app/app.module.scss
rm ./packages/web/src/app/nx-welcome.tsx

Download Required Assets

mkdir -p ./packages/web/src/styles

ASSETS_DST="./packages/web/src/styles"
ASSETS_SRC="https://raw.githubusercontent.com/jpbarbosa/neo4j-crud/main/packages/web/src/styles/"

(cd $ASSETS_DST && curl -OL "${ASSETS_SRC}actions-bar.scss")
(cd $ASSETS_DST && curl -OL "${ASSETS_SRC}alert.scss")
(cd $ASSETS_DST && curl -OL "${ASSETS_SRC}button.scss")
(cd $ASSETS_DST && curl -OL "${ASSETS_SRC}form.scss")
(cd $ASSETS_DST && curl -OL "${ASSETS_SRC}header.scss")
(cd $ASSETS_DST && curl -OL "${ASSETS_SRC}index.scss")
(cd $ASSETS_DST && curl -OL "${ASSETS_SRC}record-list.scss")
(cd $ASSETS_DST && curl -OL "${ASSETS_SRC}spacing.scss")
mkdir -p ./packages/web/public/img

ASSETS_DST="./packages/web/public/img"
ASSETS_SRC="https://raw.githubusercontent.com/jpbarbosa/neo4j-crud/main/packages/web/public/img/"

(cd $ASSETS_DST && curl -OL "${ASSETS_SRC}px.gif")

Update index.html

code ./packages/web/index.html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8" />
  <title>Neo4j Fullstack CRUD</title>
  <base href="/" />

  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <link rel="icon" type="image/x-icon" href="/favicon.ico" />
  <link rel="stylesheet" href="/src/styles/index.scss" />
</head>

<body>
  <div id="root"></div>
  <script type="module" src="/src/main.tsx"></script>
</body>

</html>

Setup React Query

code ./packages/web/src/main.tsx
import * as ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from 'react-query';
import App from './app/app';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      refetchOnWindowFocus: false,
      retry: 1,
    },
  },
});

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <BrowserRouter>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </BrowserRouter>
);

Setup React Router

code ./packages/web/src/app/app.tsx
import { Route, Routes, Navigate } from 'react-router-dom';
import { Movies } from '../pages';
import { Header } from '../components';

export function App() {
  return (
    <>
      <Header />
      <Routes>
        <Route path="/" element={<Navigate to="/movies" />} />
        <Route path="/movies/*" element={<Movies />} />
      </Routes>
    </>
  );
}

export default App;

Create Basic Components

code ./packages/web/src/components/Header.tsx
import { Link, PathPattern, useMatch } from 'react-router-dom';

type NavItem = {
  path: string;
  label: string;
};

type PageButtonProps = {
  item: NavItem;
};

const PageButton: React.FC<PageButtonProps> = ({ item: { path, label } }) => {
  const pathPattern: PathPattern<string> = { path: `${[path]}/*` };
  const match = useMatch(pathPattern);
  const isActive = !!match;
  const classList = ['button', isActive ? 'active' : ''];

  return (
    <Link to={path} className={classList.join(' ')}>
      {label}
    </Link>
  );
};

export const Header: React.FC = () => {
  const navItems: NavItem[] = [
    { path: '/movies', label: 'Movies' },
  ];

  return (
    <header>
      <h1>
        <div className="title">Neo4j Fullstack CRUD</div>
        <div className="subtitle">
          With NX Monorepo, Express, React and TypeScript
        </div>
      </h1>
      <div className="navigation">
        <div className="internal">
          {navItems.map((item) => (
            <PageButton item={item} key={item.path} />
          ))}
        </div>
        <div className="external">
          <a
            href="https://github.com/jpbarbosa/neo4j-crud"
            target="_blank"
            rel="noreferrer"
            className="button"
          >
            GitHub / jpbarbosa
          </a>
        </div>
      </div>
    </header>
  );
};
code ./packages/web/src/components/index.ts
export * from './Header';

Create Movies Page

code ./packages/web/src/pages/movies/index.tsx
import { Route, Routes } from 'react-router-dom';
import { List } from './List';

export const Movies = () => {
  return (
    <Routes>
      <Route path="/" element={<List />} />
      <Route path="/:id/edit" element={<div>To be implemented</div>} />
      <Route path="/new" element={<div>To be implemented</div>} />
    </Routes>
  );
};
code ./packages/web/src/pages/movies/List/index.tsx
import { Content } from './Content';

export const List = () => {
  return (
    <div>
      <div className="actions-bar">
        <h2>Movies</h2>
      </div>
      <Content />
    </div>
  );
};
code ./packages/web/src/pages/movies/List/Content.tsx
import axios from 'axios';
import { useQuery } from 'react-query';
import { AxiosCustomError, Movie } from '@neo4j-crud/shared';

const url = `${import.meta.env.VITE_API_URL}/movies`;

export const Content: React.FC = () => {
  const { data, error, isLoading } = useQuery<Movie[], AxiosCustomError>(
    ['movies'],
    () => axios.get<Movie[]>(url).then((res) => res.data)
  );

  if (error) {
    return <div>{error.message}</div>;
  }

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (!data) {
    return <div>No data.</div>;
  }

  return (
    <ul className="record-list">
      {data.map((movie) => (
        <li>{movie.title}</li>
      ))}
    </ul>
  );
};
code ./packages/web/src/pages/index.ts
export * from './movies';

Start all processes and open the app

nx run-many --target=serve --all
open http://localhost:4200

Commit

git add .
git commit -m "React Frontend"

Next step: Card And Search