⏰ Clock Component

구조

src
  - Components
      - Clock
          - ClockContainer.js
          - ClockPresenter.js
          - index.js

Clock/index.js

import ClockContainer from './ClockContainer';

export default ClockContainer;

Clock/ClockContainer.js

import React, { Component } from 'react';
import ClockPresenter from './ClockPresenter';

class ClockContainer extends Component {
    state = {
        time: '',
        greeting: '',
    };
    getTime = () => {
        const date = new Date();
        const hour = date.getHours();
        const minute = date.getMinutes();
        const time = `${hour > 9 ? hour : `0${hour}`}:${minute > 9 ? minute : `0${minute}`}`; // 9이하 숫자 앞에는 0을 붙인다.
        this.setState({
            time,
        });
          // 시간대별 인사 문구
        if (hour >= 5 && hour < 12) {
            this.setState({
                greeting: 'Good Morning',
            });
        } else if (hour >= 12 && hour < 17) {
            this.setState({
                greeting: 'Good Afternoon',
            });
        } else {
            this.setState({
                greeting: 'Good Evening',
            });
        }
    };
    componentDidMount() {
        setInterval(this.getTime, 1);
    }
    render() {
        const { time, greeting } = this.state;
        const { name } = this.props; // App.js에서 전달받은 name값
        return <ClockPresenter name={name} time={time} greeting={greeting} />;
    }
}

export default ClockContainer;

시간과 인사 문구를 얻는 getTime함수를 만들어 componentDidMount될 때 시간값을 얻도록 하였습니다.
setInterval을 통하여 현재 시간이 실시간으로 업데이트 되도록 설정하였습니다.
(1000ms 설정을 하여 1초마다 시간이 업데이트 되도록 하려구 하였으나 그러면 1초 뒤에 getTime함수를 실행이되어 초기 렌더링 시 시간값이 없기 떄문에 1ms로 설정하였습니다.)

Clock/ClockPresenter.js

import React from 'react';
import styled from 'styled-components';
import PropTypes from 'prop-types';

const Container = styled.div`
    display: flex;
    flex-direction: column;
    align-items: center;
`;

const Time = styled.span`
    font-size: 3em;
    margin-bottom: 0.5rem;
`;

const Greeting = styled.span`
    font-size: 2rem;
`;

const Clock = ({ name, time, greeting }) => (
    <Container>
        <Time>{time}</Time>
        <Greeting>{`${greeting}, ${name}`}</Greeting>
    </Container>
);

Clock.propTypes = {
    name: PropTypes.string.isRequired,
    time: PropTypes.string.isRequired,
    greeting: PropTypes.string.isRequired,
};

export default Clock;

Components/App.js

import React, { Component } from 'react';
// CSS
import GlobalStyle from './GlobalStyle';
// Components
import Name from './Name';
import Clock from './Clock';

class App extends Component {
    state = {
        name: null,
    };
    saveName = data => {
        this.setState({
            name: data,
        });
        localStorage.setItem('MOMENTUM_NAME', data);
    };
    getName = () => {
        const name = localStorage.getItem('MOMENTUM_NAME');
        if (name !== null) {
            this.setState({
                name,
            });
        }
    };
    componentDidMount() {
        this.getName();
    }
    render() {
        const { name } = this.state;
        return (
            <>
                <GlobalStyle />
                {name === null ? (
                    <Name saveName={this.saveName} />
                ) : (
                    <>
                        <Clock name={name} />
                    </>
                )}
            </>
        );
    }
}

export default App;

name 의 값이 null 아닐 경우 Clock Component가 렌더링이 되고 현재 name의 값을 전달해 주도록 하였습니다.

결과화면
스크린샷 2019-02-14 오후 6.31.37(2).png

🔍 Search Component

구조 Search/index.js 생략...

Search/SearchContainer.js

import React, { Component } from 'react';
import SearchPresenter from './SearchPresenter';

class SearchContainer extends Component {
    state = {
        value: '',
        isOpen: false,
    };
    handleSubmit = e => {
        e.preventDefault();
          // 현재 입력된 value값이 쿼리문에 담긴 url 주소로 리다이렉트
        window.location.href = `https://www.bing.com/search?q=${this.state.value}&PC=ATMM&FORM=MMXT01`; // bing 검색
    };
    handleChange = e => {
        this.setState({
            value: e.target.value,
        });
    };
    searchButton = () => {
        this.setState({
            isOpen: !this.state.isOpen,
        });
    };
    render() {
        const { value, isOpen } = this.state;
        return (
            <SearchPresenter
                value={value}
                isOpen={isOpen}
                handleSubmit={this.handleSubmit}
                handleChange={this.handleChange}
                searchButton={this.searchButton}
            />
        );
    }
}

export default SearchContainer;

bing검색시 url부분은 search?q={검색어} 검색어 부분만 변경되기 때문에 해당 쿼리에 현재 value의 값을 넣은 다음
window.location.href를 통하여 검색어가 담긴 주소가 리다이렉트 되도록 구현하였습니다.

searchButton함수를 통하여 검색을 할 수 있는 검색창이 토글형식이 되도록 구현하였습니다.

Search/SearchPresenter.js

import React from 'react';
import styled from 'styled-components';
import PropTypes from 'prop-types';

// 홈페이지 좌측상단에 컴포넌트 위치
const Container = styled.div`
    position: fixed;
    display: flex;
    left: 0;
    top: 0;
    margin: 1rem;
`;

const Text = styled.span`
    margin-right: 1rem;
    font-weight: bold;
`;

const SearchIcon = styled.i`
    position: absolute;
    cursor: pointer;
`;

const Input = styled.input`
    all: unset;
    border-bottom: 2px solid #fff;
    padding-left: 1.5rem;
    padding-bottom: 0.5rem;
    /* 전달받은 isOpen값의 결과에 따라 검색창화먼 토글*/
    visibility: ${prop => (prop.isOpen === true ? 'visible' : 'hidden')};
`;

const Search = ({ value, isOpen, handleSubmit, handleChange, searchButton }) => (
    <Container>
        <Text>Search</Text>
        <form onSubmit={handleSubmit}>
              {/* onClick 시 isOnpen값 변경 */}
            <SearchIcon className="fas fa-search" onClick={searchButton} />
            <Input value={value} onChange={handleChange} isOpen={isOpen} />
        </form>
    </Container>
);

Search.propTypes = {
    value: PropTypes.string,
    isOpen: PropTypes.bool.isRequired,
    handleSubmit: PropTypes.func.isRequired,
    handleChange: PropTypes.func.isRequired,
    searchButton: PropTypes.func.isRequired,
};

export default Search;

SearchIcon 클릭시 searchButtong함수가 실행되어 isOpen값이 변경되기 때문에
검색창 화면이 보였다 안보였다 토글이 될 수 있도록 구현하였습니다.

Components/App.js

import React, { Component } from 'react';
// CSS
import GlobalStyle from './GlobalStyle';
// Components
import Name from './Name';
import Clock from './Clock';
import Search from './Search'; // 추가

class App extends Component {
    state = {
        name: null,
    };
    saveName = data => {
        this.setState({
            name: data,
        });
        localStorage.setItem('MOMENTUM_NAME', data);
    };
    getName = () => {
        const name = localStorage.getItem('MOMENTUM_NAME');
        if (name !== null) {
            this.setState({
                name,
            });
        }
    };
    componentDidMount() {
        this.getName();
    }
    render() {
        const { name } = this.state;
        return (
            <>
                <GlobalStyle />
                {name === null ? (
                    <Name saveName={this.saveName} />
                ) : (
                    <>
                        <Clock name={name} />
                        <Search /> {/* 추가 */}
                    </>
                )}
            </>
        );
    }
}

export default App;

Clock Component와 마찬가지로 name 값의 유무에 따라 rendering이 되도록 하였습니다.

결과화면

search.mov.gif

☀️ Weather Component

날씨 정보를 가져오기 위해서 https://openweathermap.org에 회원가입을 신청한 후 API KEY를 받았습니다. (API KEY 받는법)

API 문서에서 By geographic coordinates라는 부분을 보면

api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}

lat : latitude
lon : longitude

위도와 경도 정보만 주어지면 해당지역의 날씨 정보를 주는 API가 있는걸 확인 할 수 있습니다. 그래서 위도와 경도의 정보를 얻을 수 있는 geolocation 객체를 활용하여 API에서 날씨정보을 얻을 수 있도록 하였습니다.

스크린샷 2019-02-14 오후 8.07.33.png

구조 Weather/index.js 생략...

Weather/WeatherContainer.js

import React, { Component } from 'react';
import WeatherPresenter from './WeatherPresenter';
import axios from 'axios'; // api 정보를 쉽게 가져오기 위해 axios 설치

class WeatherContainer extends Component {
    state = {
        temp: '',
        name: '',
        location: '',
    };

    // 기본 api 주소정보로 baseURL과 APPID설정
    api = axios.create({
        baseURL: 'https://api.openweathermap.org/data/2.5',
        params: {
            APPID: 'f45feac96ba10f6adf703ab2e66bd792'
        }
    });

    componentDidMount() {
        navigator.geolocation.getCurrentPosition(position => {
              // api 추가 파라미터로 latitude, longitude 추가
            const getWeather = this.api.get('/weather',
                {
                    params: {
                        lat: position.coords.latitude,
                        lon: position.coords.longitude,
                    },
                },
                err => console.log(err)
            );
            getWeather
                .then(res => {
                    this.setState({
                        temp: Math.ceil(res.data.main.temp - 273.15), // 온도(섭씨온도 계산)
                        name: res.data.weather[0].main, // 날씨
                        location: res.data.name, // 지역
                    });
                })
                .catch(err => console.log(err));
        });
    }

    render() {
        const { temp, name, location } = this.state;
        return <WeatherPresenter temp={temp} name={name} location={location} />;
    }
}

export default WeatherContainer;

api 정보를 쉽게 가져오기 위해서 axios를 설치한 다음 axios를 통하여 api라는 변수에 기본적인 파라미터를 설정해 주었습니다.

componentDidMount 할 때 geolocation객체의 getCurrentPosition() 메서드를 통하여 api 변수에 추가적으로 위도와 경도의 값을 파라미터로 넣어준 다음 날씨 정보를 호출하여 getWeather 변수에 담아 주었습니다.

getWeather 변수에 담겨 있는 result값 중에서 필요한 데이터인 온도, 날씨, 지역의 정보를 가져왔다.

Weather/WeatherPresenter.js

import React from 'react';
import styled from 'styled-components';
import PropTypes from 'prop-types';

// 홈페이지 우측상단에 컴포넌트 위치
const Container = styled.div`
    position: fixed;
    right: 0;
    top: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    margin: 1rem;
`;

const Temp = styled.span`
    font-weight: 500;
    font-size: 1.2rem;
    display: flex;
    align-items: center;
    margin-bottom: 0.5rem;
`;

const Name = styled.span`
    font-weight: 400;
    font-size: 0.8rem;
    margin-right: 0.2rem;
`;

const Location = styled.span`
    font-size: 0.5rem;
`;

const Weather = ({ temp, name, location }) => (
    <Container>
        <Temp>
            <Name>{name}</Name>
            {temp}˚
        </Temp>
        <Location>{location}</Location>
    </Container>
);

Weather.propTypes = {
    temp: PropTypes.string.isRequired,
    name: PropTypes.string.isRequired,
    location: PropTypes.string.isRequired,
};

export default Weather;

Components/App.js

import React, { Component } from 'react';
// CSS
import GlobalStyle from './GlobalStyle';
// Components
import Name from './Name';
import Clock from './Clock';
import Search from './Search'; 
import Weather from './Weather'; // 추가

class App extends Component {
    state = {
        name: null,
    };
    saveName = data => {
        this.setState({
            name: data,
        });
        localStorage.setItem('MOMENTUM_NAME', data);
    };
    getName = () => {
        const name = localStorage.getItem('MOMENTUM_NAME');
        if (name !== null) {
            this.setState({
                name,
            });
        }
    };
    componentDidMount() {
        this.getName();
    }
    render() {
        const { name } = this.state;
        return (
            <>
                <GlobalStyle />
                {name === null ? (
                    <Name saveName={this.saveName} />
                ) : (
                    <>
                        <Clock name={name} />
                        <Search />
                        <Weather /> {/* 추가 */}
                    </>
                )}
            </>
        );
    }
}

export default App;

Weather 컴포넌트 추가
api를 호출하는 시간이 있기 때문에 화면 날씨정보가 바로 나타나지 않을 수 있습니다.

결과화면

스크린샷 2019-02-18 오전 10.39.50.png