twitter cloning(nestJS & react) - 2. frontend

eugene's blog·2021년 8월 20일
0

twitter clone

목록 보기
2/2
post-thumbnail

CRA 생성

$ npx create-react-app frontend --template=typescript

폴더정리(안쓰는 파일들 삭제)

npm i -D eslint prettier eslint-config-prettier eslint-plugin-prettier @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest

backend에 있는 .eslintrc 파일과 prettierrc 파일 frontend에 붙여넣기

tailwind

css framework

사용법


npm install -D tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
npm install @craco/craco

package.json

"scripts": {
    "start": "craco start",
    "build": "craco build",
    "test": "craco test",
    "eject": "craco eject"
  }
npx tailwindcss-cli@latest init

craco.config.js

module.exports = {
   purge: [],
   purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
    darkMode: false, // or 'media' or 'class'
    theme: {
      extend: {},
    },
    variants: {
      extend: {},
    },
    plugins: [],
  }

만약에 tailwind.config.js나 craco.config.js에서 module에 빨간줄이 뜨면 .eslintignore 추가

eslintignore

tailwind.config.js
craco.config.js

Layouts


npm i react-router-dom

npm i -D @types/react-router-dom

App.tsx

import React, { FC } from 'react';
import Main from './pages/main';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

const App: FC = () => {
  return (
    <Router>
      <Switch>
        <Route exact path="/" component={Main} />
      </Switch>
    </Router>
  );
};

export default App;

pages/main.tsx

import React, { FC } from 'react';

const Main: FC = () => {
  return (
    <div className="flex bg-red-100 min-h-screen">
      <div className="bg-blue-200 flex-auto">1</div>
      <div className="bg-purple-200 max-w-screen-sm flex-auto">2</div>
      <div className="bg-green-200 flex-auto">3</div>
    </div>
  );
};

export default Main;

components/Layout.tsx

import React, { FC } from 'react';

const Layout: FC = ({ children }) => {
  return (
    <div className="flex bg-red-100 min-h-screen">
      <div className="bg-blue-200 flex-auto">1</div>
      <div className="bg-purple-200 max-w-screen-sm flex-auto">{children}</div>
      <div className="bg-green-200 flex-auto">3</div>
    </div>
  );
};

export default Layout;

App.tsx

import React, { FC } from 'react';
import Main from './pages/main';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Layout from './components/Layout';

const App: FC = () => {
  return (
    <Router>
      <Layout>
        <Switch>
          <Route exact path="/" component={Main} />
        </Switch>
      </Layout>
    </Router>
  );
};

export default App;

pages/main.tsx

import React, { FC } from 'react';

const Main: FC = () => {
  return <div>main</div>;
};

export default Main;

트윗들 가져와서 카드 형태로 보여주기


npm i axios swr

main.tsx

import React, { FC, useEffect } from 'react';
import axios from 'axios';
import useSWR from 'swr';

const Main: FC = () => {
  const fetcher = async (url: string) => {
    try {
      const response = await axios.get(url);

      return response.data;
    } catch (error) {
      console.error(error);
    }
  };

  const { data, error } = useSWR('http://localhost:3010/tweets', fetcher);

  useEffect(() => {
    console.log(data);
  }, [data]);

  return <div>main</div>;
};

export default Main;

CORS error -> backend에서 접근 허용 설정

backend/main.ts

import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    cors: { origin: process.env.FRONT_URL, credentials: true },
  });

  app.useGlobalPipes(new ValidationPipe());

  await app.listen(process.env.PORT);
}
bootstrap();

.env

FRONT_URL=http://localhost:3000

backend 재시작(.env파일 수정시 재시작해야 반영)

pages/main.tsx

import React, { FC, useEffect } from 'react';
import axios from 'axios';
import useSWR from 'swr';

const Main: FC = () => {
  const fetcher = async (url: string) => {
    try {
      const response = await axios.get(url);

      return response.data;
    } catch (error) {
      console.error(error);
    }
  };

  const { data, error } = useSWR('http://localhost:3010/tweets', fetcher);

  useEffect(() => {
    console.log(data);
  }, [data]);

  if (!data) return <div>loading...</div>;
  if (error) return <div>error</div>;

  return <div>main</div>;
};

export default Main;

.env

// CRA는 REACT_APP으로 시작해야한다
REACT_APP_BACK_URL=http://localhost:3010

src/components/common/Cards.tsx

import React, { FC } from 'react';
import { ITweet } from '../../interfaces';
import Card from './Card';

interface CardsProps {
  tweets: ITweet[];
}

const Cards: FC<CardsProps> = ({ tweets }) => {
  return (
    <div>
      {tweets.map((tweet) => {
        return <Card key={tweet.id} />;
      })}
    </div>
  );
};

export default Cards;

src/components/common/Card.tsx

import React, { FC } from 'react';

const Card: FC = () => {
  return <div>Card</div>;
};

export default Card;

src/interfaces/index.ts

export interface ITweet {
  id: number;
  createdAt: Date;
  tweet: string;
  users: {
    id: number;
    nickname: string;
  };
}

src/pages/main.tsx

import React, { FC, useEffect } from 'react';
import axios from 'axios';
import useSWR from 'swr';
import Cards from '../components/common/Cards';
import { ITweet } from '../interfaces';

const Main: FC = () => {
  const fetcher = async (url: string) => {
    try {
      const response = await axios.get(url);

      return response.data;
    } catch (error) {
      console.error(error);
    }
  };

  const { data, error } = useSWR<ITweet[]>(
    `${process.env.REACT_APP_BACK_URL}/tweets`,
    fetcher,
  );

  if (!data) return <div className="bg-blue-700">Loading....</div>;
  if (error) return <div>error</div>;

  return <Cards tweets={data} />;
};

export default Main;

fontawesome 설치

npm i --save @fortawesome/fontawesome-svg-core
npm install --save @fortawesome/free-solid-svg-icons
npm install --save @fortawesome/react-fontawesome

npm install --save @fortawesome/free-brands-svg-icons
npm install --save @fortawesome/free-regular-svg-icons

components/common/ProfileIcon.tsx

import React, { FC } from 'react';
import { faUser } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

const ProfileIcon: FC = () => {
  return (
    <div className="rounded-full h-12 w-12 flex items-center justify-center bg-gray-300 hover:bg-gray-400">
      <FontAwesomeIcon className="text-3xl text-blue-400" icon={faUser} />
    </div>
  );
};

export default ProfileIcon;

Cards.tsx

import React, { FC } from 'react';
import { ITweet } from '../../interfaces';
import Card from './Card';

interface CardsProps {
  tweets: ITweet[];
}

const Cards: FC<CardsProps> = ({ tweets }) => {
  return (
    <div>
      {tweets.map((tweet) => {
        return <Card key={tweet.id} tweet={tweet} />;
      })}
    </div>
  );
};

export default Cards;

Card.tsx

import React, { FC } from 'react';
import { ITweet } from '../../interfaces';
import ProfileIcon from './ProfileIcon';

interface CardProps {
  tweet: ITweet;
}

const Card: FC<CardProps> = ({ tweet }) => {
  return (
    <div>
      <ProfileIcon />
      {tweet.tweet}
    </div>
  );
};

export default Card;
profile
매일 노력하는 개발자 김유진입니다.

0개의 댓글