LoginSignup
0
0

More than 1 year has passed since last update.

React (Firebase + Redux ToolKit + TypeScript)の構成でアプリを作成しました【Twitter clone】

Last updated at Posted at 2021-10-04

学習するスキル

[認証]
  • Email + Password認証
  • ユーザーProfile登録(Avator + Username)
  • Passwordリセット機能
  • Googleアカウント認証
[Redux]
  • ログインユーザー情報のRedux Toolkitによるグローバル状態管理
[データベース]
  • Cloud Firebaseを使ったデータベース操作(Tweet投稿データ+コメントデータ)
  • Cloud Storageを使った画像保存(Tweet投稿データとAvatar画像)
[Deploy]
  • Firebase Hostingディプロイ

環境構築

① Create React Appで新しいアプリを作成する。

$ mkdir ディレクトリ名
$ npx create-react-app . --template redux-typescript --use-npm
$ npm start

② 環境を構築する
公式サイト:material-ui
公式サイト:firebase

$ npm i firebase@8.10.0
$ npm i @material-ui/core --legacy-peer-deps
$ npm i @material-ui/icons --legacy-peer-deps

③ 使用しないファイルを削除する。

App.css, App.test.tsx, index.css, logo.svg, reportWebVitals.ts
setupTest.ts

$ yarn add @reduxjs/toolkit
$ yarn add react-router-dom @types/react-router-dom
$ yarn add @types/axios
$ yarn add @types/styled-components

ディレクトリ構成

src/
 ├ components/
 │ └ atoms/        
 │ └ molecules/    
 │ └ organisms/   
 │ └ templates/   
 │ └ pages/        
 ├ store/ 

 ├ App.css
 ├ App.tsx
 ├ index.tsx

##public/index.html を編集

public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta name="description" content="ReactWeather" />
    <title>React Weather</title>

    <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@200;400;500;600&display=swap" rel="stylesheet" />
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

##src/App.tsx を編集

src/App.tsx
import React from 'react';
import { useSelector } from 'react-redux';
import { ThemeProvider } from 'styled-components';
import { GlobalStyles } from './app.styled';
import Home from './pages/Home';
import { AppStore } from './store/store';
import { darkTheme, lightTheme } from './theme';

const App: React.FC = () => {
  const darkMode = useSelector((state: AppStore) => state.app.darkMode);
  return (
    <ThemeProvider theme={darkMode ? darkTheme : lightTheme}>
      <GlobalStyles />
      <Home />
    </ThemeProvider>
  );
};

export default App;

##src/app.styled.ts を作成

src/app.styled.ts
import { createGlobalStyle } from 'styled-components';
import { Theme } from './theme';

declare module 'styled-components' {
  /* tslint:disable */
  export interface DefaultTheme extends Theme {}
}

export const GlobalStyles = createGlobalStyle`
*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: inherit;
}
html {
  font-size: 16px;
}
body {
  font-family: 'Poppins', sans-serif;
  display: flex;
  justify-content: center;
  min-height: 100vh;
  background: url(${({ theme }) => theme.backgroundImage}) no-repeat center 120%, linear-gradient(${({ theme }) =>
  theme.backgroundGradient.color1} 0%, ${({ theme }) => theme.backgroundGradient.color2} 100%);
  background-size: auto;
}
#root {
  max-width: 960px;
  width: 100%;
  margin: auto 0;
  padding: 0 1rem;
}
`;

##src/index.tsx を編集

src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import store from './store/store';

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

##src/react-dark-mode-toggle.d.ts を作成

src/react-dark-mode-toggle.d.ts
declare module 'react-dark-mode-toggle';

##src/theme.ts を作成

src/theme.ts
import lightBg from './assets/bg.svg';
import darkBg from './assets/darkBg.svg';

export interface Theme {
  appTitleColor: string;
  backgroundImage: string;
  backgroundGradient: {
    color1: string;
    color2: string;
  };
  panelBgColor: string;
  panelTitleColor: string;
  forecastPanelBgColor: string;
  searchInput: {
    color: string;
    placeholderColor: string;
  };
  temperatureSwitch: {
    backgroundColor: string;
    sliderColor: string;
    textColor: string;
  };
  searchSuggestion: {
    backgroundColor: string;
    hoverBackgroundColor: string;
    seperatorLineColor: string;
  };
  smallIconColor: string;
  smallIconTextColor: string;
}

export const lightTheme: Theme = {
  appTitleColor: '#2F5D8A',
  backgroundImage: lightBg,
  backgroundGradient: {
    color1: '#F9FFFF',
    color2: '#38C8E6',
  },
  panelBgColor: '#FFFFFF',
  panelTitleColor: '#727E8E',
  forecastPanelBgColor: 'rgba(255, 255, 255, 0.75)',
  searchInput: {
    color: '#727E8E',
    placeholderColor: '#6898d5',
  },
  temperatureSwitch: {
    backgroundColor: '#77b1c7',
    sliderColor: '#fff',
    textColor: '#fff',
  },
  searchSuggestion: {
    backgroundColor: '#fff',
    hoverBackgroundColor: '#f9f9f9',
    seperatorLineColor: '#ccc',
  },
  smallIconColor: '#A1B9CE',
  smallIconTextColor: '#7B98B2',
};

export const darkTheme: Theme = {
  appTitleColor: '#2F5D8A',
  backgroundImage: darkBg,
  backgroundGradient: {
    color1: '#031027',
    color2: '#02101D',
  },
  panelBgColor: '#051A33',
  panelTitleColor: '#216397',
  forecastPanelBgColor: 'rgba(5, 26, 51, 0.75)',
  searchInput: {
    color: '#5f8bbf',
    placeholderColor: '#235A84',
  },
  temperatureSwitch: {
    backgroundColor: '#1b3657',
    sliderColor: '#437abd',
    textColor: '#718cac',
  },
  searchSuggestion: {
    backgroundColor: '#0f2744',
    hoverBackgroundColor: '#183553',
    seperatorLineColor: '#356097',
  },
  smallIconColor: '#153C5E',
  smallIconTextColor: '#3975AB',
};

##/src/api/placeSuggestion.ts を作成

/src/api/placeSuggestion.ts
export const fetchCities = async (search: string) => {
  const url = `https://places-dsn.algolia.net/1/places/query`;
  const res = await (
    await fetch(url, {
      method: 'POST',
      body: JSON.stringify({
        query: search,
        type: 'city',
        language: 'en',
      }),
    })
  ).json();

  return res.hits
    .filter((item: any) => item.is_city)
    .map((i: any) => {
      return i.locale_names[0] + ', ' + i.country;
    });
};

##src/api/types.ts を作成

src/api/types.ts
export interface IWeatherData {
  weather: {
    id: number;
    main: string;
    description: string;
    icon: string;
  };
  main: {
    temp: number;
    feels_like: number;
    temp_min: number;
    temp_max: number;
    pressure: number;
    humidity: number;
  };
  wind: {
    speed: number;
    deg: number;
  };
  sys: {
    country: string;
    sunrise: number;
    sunset: number;
  };
  name: string;
}

export interface IExtendedForecastData {
  day: string;
  temp: {
    temp_min: number;
    temp_max: number;
  };
  weather: {
    id: number;
    main: string;
  };
}

##src/api/weather.ts を作成

src/api/weather.ts
const baseUrl = 'https://api.openweathermap.org/data/2.5';

export const fetchWeatherData = async (city: string | { lat: number; lng: number }) => {
  let url = `${baseUrl}/weather?q=${city}&appid=${process.env.REACT_APP_WEATHER_API_KEY}`;

  if (typeof city === 'object') {
    url = `${baseUrl}/weather?lat=${city.lat}&lon=${city.lng}&appid=${process.env.REACT_APP_WEATHER_API_KEY}`;
  }
  return await (await fetch(url)).json();
};

export const fetchExtendedForecastData = async (city: string | { lat: number; lng: number }) => {
  let url = `${baseUrl}/forecast/daily?q=${city}&appid=${process.env.REACT_APP_WEATHER_API_KEY}`;

  if (typeof city === 'object') {
    url = `${baseUrl}/forecast/daily?lat=${city.lat}&lon=${city.lng}&appid=${process.env.REACT_APP_WEATHER_API_KEY}`;
  }

  return await (await fetch(url)).json();
};

参考サイト

Firebase + React Hooks(TypeScript)によるWebアプリ開発

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0