# Взаимодействуем с VK

*ОБНОВЛЕНИЕ 2018: Вышло* [*второе издание*](https://maxfarseer.gitbooks.io/redux-course-ru-v2/content/) *(современный код и версии пакетов, данное издание УСТАРЕЛО)*

*На* [*канале*](http://bit.ly/2IqH74P) *так же проводятся бесплатные вебинары, публикуются переводы и авторские материалы,* [*присоединяйтесь*](http://bit.ly/2IqH74P)*!*

## Взаимодействуем с VK

Чтобы работать с [VK API](https://vk.com/dev/methods) вам необходимо будет создать приложение на сайте [vk.com](https://vk.com/), и указать в настройках URL сервера, с которого вы будете выполнять запросы.

*Localhost* не поддерживается.

### Интеграция VK API

Необходимо добавить скрипт *openapi* перед нашей сборкой - *bundle.js*, а так же вызвать VK.init

```markup
<!DOCTYPE html>
<html>
  <head>
    <title>Redux [RU]Tutorial</title>
  </head>
  <body>
    <div id="root" class="container-fluid">
    </div>
    <script src="//vk.com/js/api/openapi.js"></script>
    <script src="/static/bundle.js"></script>
    <script language="javascript">
      VK.init({
        apiId: 5087365
      });
    </script>
  </body>
</html>
```

### Авторизация

Создадим действия для User.

*src/actions/UserActions.js*

```javascript
import {
  LOGIN_REQUEST,
  LOGIN_SUCCES,
  LOGIN_FAIL
} from '../constants/User'

export function handleLogin() {

  return function(dispatch) {

    dispatch({
      type: LOGIN_REQUEST
    })

    VK.Auth.login((r) => { // eslint-disable-line no-undef
      if (r.session) {
        let username = r.session.user.first_name;

        dispatch({
          type: LOGIN_SUCCES,
          payload: username
        })

      } else {
        dispatch({
          type: LOGIN_FAIL,
          error: true,
          payload: new Error('Ошибка авторизации')
        })
      }
    },4); // запрос прав на доступ к photo
  }

}
```

Проверьте список констант:

```javascript
export const LOGIN_REQUEST = 'LOGIN_REQUEST'
export const LOGIN_SUCCES = 'LOGIN_SUCCES'
export const LOGIN_FAIL = 'LOGIN_FAIL'
```

"Приконнектим" в `<App />` UserActions, и добавим новые свойства в компонент `<User />`

*src/containers/App.js*

```javascript
import React, { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import User from '../components/User'
import Page from '../components/Page'
import * as pageActions from '../actions/PageActions'
import * as userActions from '../actions/UserActions'

class App extends Component {
  render() {
    const { user, page } = this.props
    const { getPhotos } = this.props.pageActions
    const { handleLogin } = this.props.userActions

    return <div className='row'>
      <Page photos={page.photos} year={page.year} getPhotos={getPhotos} fetching={page.fetching} />
      <User name={user.name} handleLogin={handleLogin} error={user.error} />
    </div>
  }
}

function mapStateToProps(state) {
  return {
    user: state.user,
    page: state.page
  }
}

function mapDispatchToProps(dispatch) {
  return {
    pageActions: bindActionCreators(pageActions, dispatch),
    userActions: bindActionCreators(userActions, dispatch)
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(App)
```

Обновим reducer user:

*src/reducers/user.js*

```javascript
import {
  LOGIN_SUCCES,
  LOGIN_FAIL
} from '../constants/User'

const initialState = {
  name: '',
  error: ''
}

export default function user(state = initialState, action) {

  switch(action.type) {
    case LOGIN_SUCCES:
      return { ...state, name: action.payload, error: '' }

    case LOGIN_FAIL:
      return { ...state, error: action.payload.message }

    default:
      return state
  }

}
```

И покажем все это в компоненте `<User />`

*src/components/User.js*

```javascript
import React, { PropTypes, Component } from 'react'

export default class User extends Component {

  render() {
    const { name, error } = this.props
    let template

    if (name) {
      template = <p>Привет, {name}!</p>
    } else {

      template = <button className='btn' onClick={this.props.handleLogin}>Войти</button>
    }

    return <div className='ib user'>
      {template}
      {error ? <p className='error'> {error}. <br /> Попробуйте еще раз.</p> : ''}
    </div>
  }
}

User.propTypes = {
  name: PropTypes.string.isRequired,
  handleLogin: PropTypes.func.isRequired,
  error: PropTypes.string.isRequired
}
```

Сейчас если кликнуть на "войти" - всплывет VK окно с подтверждением прав доступа (первый раз). После подтверждения прав, вместо кнопки войти появляется надпись "Привет, ХХХ". При перезагрузке сайта и повторных нажатиях на "войти" - VK окно мгновенно закрывается, а кнопка вновь изменяется на "Привет, XXX". Неплохо бы было проверять "статус", например в *componentWillMount*, но оставлю это на "домашку".

Как всегда, доблестный логгер пишет в консоли - что происходит.

### Загрузка фото

Нам нужно практически повторить, все что написано выше, только для блока **Page**.

Можете попробовать сами, используя метод `photos.getAll` из VK API.

Для начала, проверим список констант:

*src/constants/Page.js*

```javascript
export const GET_PHOTOS_REQUEST = 'GET_PHOTOS_REQUEST'
export const GET_PHOTOS_SUCCESS = 'GET_PHOTOS_SUCCESS'
export const GET_PHOTOS_FAIL = 'GET_PHOTOS_FAIL'
```

Напишем немало кода, для загрузки фото:

*src/actions/PageActions.js*

```javascript
import {
  GET_PHOTOS_REQUEST,
  GET_PHOTOS_FAIL,
  GET_PHOTOS_SUCCESS
} from '../constants/Page'

let photosArr = []
let cached = false

function makeYearPhotos(photos, selectedYear) {
  let createdYear, yearPhotos = []

  photos.forEach((item) => {
    createdYear = new Date(item.created*1000).getFullYear()
    if (createdYear === selectedYear ) {
      yearPhotos.push(item)
    }
  })

  yearPhotos.sort((a,b) => b.likes.count-a.likes.count);

  return yearPhotos
}

function getMorePhotos(offset, count, year, dispatch) {
  VK.Api.call('photos.getAll', {extended:1, count: count, offset: offset},(r) => { // eslint-disable-line no-undef
    try {
      if (offset <= r.response[0] - count) {
        offset+=200;
        photosArr = photosArr.concat(r.response)
        getMorePhotos(offset,count,year,dispatch)
      } else {
        let photos = makeYearPhotos(photosArr, year)
        cached = true
        dispatch({
          type: GET_PHOTOS_SUCCESS,
          payload: photos
        })
      }
    }
    catch(e) {
      dispatch({
        type: GET_PHOTOS_FAIL,
        error: true,
        payload: new Error(e)
      })
    }

  })
}

export function getPhotos(year) {

  return (dispatch) => {
    dispatch({
      type: GET_PHOTOS_REQUEST,
      payload: year
    })

    if (cached) {
      let photos = makeYearPhotos(photosArr, year)
      dispatch({
        type: GET_PHOTOS_SUCCESS,
        payload: photos
      })
    } else {
      getMorePhotos(0,200,year,dispatch)
    }

  }
}
```

`makeYearPhotos` и `getMorePhotos` можно вынести в папку utils, как вспомогательные функции.

Главное здесь, что мы по прежнему вызываем действия (*dispatch actions*). Все так, как было в самом начале, просто добавилось немного больше логики для получения фото. Алгоритм получения всех фото (да и необходимость получения всех) - оставляю без комментариев. Мне кажется, это приемлемый способ.

Чтобы потестировать показ ошибок, достаточно просто исправить цифру 200 на 2 или 20. VK с любовью вам ответит, что вы мягко-говоря, очень настойчиво обращаетись к API ;)

Исправив редьюсер и отрисовку в компоненте, мы закончим начатое.

*src/reducers/page.js*

```javascript
import {
  GET_PHOTOS_REQUEST,
  GET_PHOTOS_SUCCESS,
  GET_PHOTOS_FAIL
} from '../constants/Page'

const initialState = {
  year: 2016,
  photos: [],
  fetching: false,
  error: ''
}

export default function page(state = initialState, action) {

  switch (action.type) {
    case GET_PHOTOS_REQUEST:
      return { ...state, year: action.payload, fetching: true, error: '' }

    case GET_PHOTOS_SUCCESS:
      return { ...state, photos: action.payload, fetching: false, error: '' }

    case GET_PHOTOS_FAIL:
      return { ...state, error: action.payload.message, fetching: false }

    default:
      return state;
  }

}
```

*src/components/Page.js*

```javascript
import React, { PropTypes, Component } from 'react'

export default class Page extends Component {
  onYearBtnClick(e) {
    this.props.getPhotos(+e.target.innerText)
  }
  render() {
    const { year, photos, fetching, error } = this.props
    const years = [2016,2015,2014,2013,2012,2011,2010]
    return <div className='ib page'>
      <p>
        { years.map((item,index) =>  <button className='btn' key={index} onClick={::this.onYearBtnClick}>{item}</button> )}
      </p>
      <h3>{year} год [{photos.length}]</h3>
      { error ? <p className='error'> Во время загрузки фото произошла ошибка</p> : '' }
      {
        fetching ?
        <p>Загрузка...</p>
        :
        photos.map((entry, index) =>
          <div key={index} className='photo'>
            <p><img src={entry.src} /></p>
            <p>{entry.likes.count} ❤</p>
          </div>
        )
      }
    </div>
  }
}

Page.propTypes = {
  year: PropTypes.number.isRequired,
  photos: PropTypes.array.isRequired,
  getPhotos: PropTypes.func.isRequired,
  error: PropTypes.string.isRequired
}
```

**Итого**: Вы научились выполнять асинхронные запросы и корректно показывать прелоадер, ошибки или успешный результат.

[Исходный код](https://github.com/maxfarseer/redux-ru-tutorial/tree/integrate_vk) на текущий момент.

P.S. css тоже был слегка подправлен.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://max-frontend.gitbook.io/redux-course-ru/struktura_prilozheniya/vzaimodeistvuem_s_vk.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
