Реагировать + Ruby on Rails =
Реакция взяла мир развития фронтенда штурмом. Это отличная библиотека JavaScript для создания пользовательских интерфейсов. И это замечательно в сочетании с Ruby on Rails. Вы можете использовать Rails на заднем конце с React на переднем конце различными способами .
В этом практическом руководстве мы собираемся создать приложение React, которое работает с Rails 5.1 API.
Вы можете посмотреть видео версию этого урока здесь .
Чтобы следовать этому уроку, вы должны быть знакомы с Rails и знать основы React.
Если вы не используете Rails, вы также можете создать API на выбранном вами языке или платформе и просто использовать это руководство для части React.
Учебное пособие охватывает функциональные компоненты без сохранения состояния, компоненты на основе классов, использование приложения Create React, использование axios для выполнения вызовов API, помощник по постоянству и многое другое.
Что мы собираемся построить
Мы собираемся создать доску идей в виде одностраничного приложения (SPA), которое отображает идеи в виде квадратных плиток.
Вы можете добавлять новые идеи, редактировать их и удалять их. Идеи автоматически сохраняются, когда пользователь фокусируется вне формы редактирования.
В конце этого урока у нас будет функциональное приложение CRUD, в которое мы можем добавить некоторые улучшения, такие как анимация, сортировка и поиск, в будущем уроке.
Вы можете увидеть полный код приложения на GitHub:
Настройка Rails API
Давайте начнем с создания Rails API. Мы будем использовать встроенную функцию Rails для создания приложений только для API.
Убедитесь, что у вас установлена версия 5.1 или более поздняя версия Rails.
gem install rails -v 5.1.3
На момент написания этого руководства 5.1.3 был последним стабильным выпуском, поэтому мы будем его использовать.
Затем сгенерируйте новое приложение Rails API с флагом --api
.
rails new --api ideaboard-api cd ideaboard-api
Далее давайте создадим модель данных. Нам нужна только одна модель данных для идей с двумя полями — заголовок и тело, оба типа string
.
Давайте сгенерируем и запустим миграцию:
rails generate model Idea title:string body:string rails db:migrate
Теперь, когда мы создали таблицу идей в нашей базе данных, давайте заполним ее несколькими записями, чтобы у нас было несколько идей для отображения.
В файле db/seeds.rb
добавьте следующий код:
ideas = Idea.create( [ { title: "A new cake recipe", body: "Made of chocolate" }, { title: "A twitter client idea", body: "Only for replying to mentions and DMs" }, { title: "A novel set in Italy", body: "A mafia crime drama starring Berlusconi" }, { title: "Card game design", body: "Like Uno but involves drinking" } ])
Не стесняйтесь добавлять свои собственные идеи.
Затем запустите:
rails db:seed
Далее, давайте создадим IdeasController с действием index в app/controllers/api/v1/ideas_controller.rb
:
module Api::V1 class IdeasController < ApplicationController def index @ideas = Idea.all render json: @ideas end end end
Обратите внимание, что контроллер находится в app/controllers/api/v1
потому что мы версии нашего API. Это хорошая практика, чтобы избежать внесения изменений и обеспечить некоторую обратную совместимость с нашим API.
Затем добавьте идеи в качестве ресурса в config/routes.rb
:
Rails.application.routes.draw do namespace :api do namespace :v1 do resources :ideas end end end
Хорошо, теперь давайте проверим нашу первую конечную точку API!
Сначала давайте запустим сервер Rails API на порту 3001:
rails s -p 3001
Затем давайте проверим нашу конечную точку для получения всех идей с помощью curl:
curl -G http://localhost:3001/api/v1/ideas
И это печатает все наши идеи в формате JSON:
[{"id":18,"title":"Card game design","body":"Like Uno but involves drinking","created_at":"2017-09-05T15:42:36.217Z","updated_at":"2017-09-05T15:42:36.217Z"},{"id":17,"title":"A novel set in Italy","body":"A mafia crime drama starring Berlusconi","created_at":"2017-09-05T15:42:36.213Z","updated_at":"2017-09-05T15:42:36.213Z"},{"id":16,"title":"A twitter client idea","body":"Only for replying to mentions and DMs","created_at":"2017-09-05T15:42:36.209Z","updated_at":"2017-09-05T15:42:36.209Z"},{"id":15,"title":"A new cake recipe","body":"Made of chocolate","created_at":"2017-09-05T15:42:36.205Z","updated_at":"2017-09-05T15:42:36.205Z"}]
Мы также можем проверить конечную точку в браузере, перейдя по адресу http: // localhost: 3001 / api / v1 / ideas .
Настройка нашего интерфейсного приложения с помощью приложения Create React
Теперь, когда у нас есть базовый API, давайте настроим наше внешнее приложение React с помощью приложения Create React . Create React App — это проект Facebook, который поможет вам быстро приступить к работе с приложением React без какой-либо настройки.
Во-первых, убедитесь, что у вас установлены Node.js и npm. Вы можете скачать установщик с веб-сайта Node.js. Затем установите приложение Create React, запустив:
npm install -g create-react-app
Затем убедитесь, что вы находитесь за пределами каталога Rails, и выполните следующую команду:
create-react-app ideaboard
Это создаст приложение React под названием ideaboard, которое мы теперь будем использовать для общения с нашим Rails API.
Давайте запустим приложение React:
cd ideaboard npm start
Это откроет его на http: // localhost: 3000 .
Приложение имеет страницу по умолчанию с компонентом React под названием App, на котором отображается логотип React и приветственное сообщение.
Содержимое страницы отображается через компонент React в файле src/App.js
:
import React, { Component } from 'react' import logo from './logo.svg' import './App.css' class App extends Component { render() { return ( <div className="App"> <div className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <h2>Welcome to React</h2> </div> <p className="App-intro"> To get started, edit <code>src/App.js</code> and save to reload. </p> </div> ); } } export default App
Наш первый компонент React
Наш следующий шаг — отредактировать этот файл, чтобы использовать только что созданный API, и перечислить все идеи на странице.
Начнем с того, что заменим приветственное сообщение тегом h1
заголовком нашего приложения «Доска идей».
Давайте также добавим новый компонент под названием IdeasContainer
. Нам нужно импортировать его и добавить в функцию рендеринга:
import React, { Component } from 'react' import './App.css' import IdeasContainer from './components/IdeasContainer' class App extends Component { render() { return ( <div className="App"> <div className="App-header"> <h1>Idea Board</h1> </div> <IdeasContainer /> </div> ); } } export default App
Давайте создадим этот компонент IdeasContainer
в новом файле в src/IdeasContainer.js
src/components
.
import React, { Component } from 'react' class IdeasContainer extends Component { render() { return ( <div> Ideas </div> ) } } export default IdeasContainer
Давайте также изменим стили в App.css
чтобы они имели белый заголовок и черный текст, а также удалим стили, которые нам не нужны:
.App-header { text-align: center; height: 150px; padding: 20px; } .App-intro { font-size: large; }
Этот компонент должен общаться с нашей конечной точкой Rails API для получения всех идей и их отображения.
Извлечение данных API с помощью axios
Мы сделаем Ajax-вызов API в методе жизненного цикла componentDidMount()
компонента IdeasContainer и сохраним идеи в состоянии компонента.
Давайте начнем с инициализации состояния в конструкторе с идеями в виде пустого массива:
constructor(props) { super(props) this.state = { ideas: [] } }
А затем мы обновим состояние в componentDidMount()
.
Давайте использовать библиотеку axios для выполнения вызовов API. Вы также можете использовать fetch или jQuery, если хотите.
Установите axios с npm:
npm install axios --save
Затем импортируйте его в IdeasContainer
:
import axios from 'axios'
И используйте его в componentDidMount()
:
componentDidMount() { axios.get('http://localhost:3001/api/v1/ideas.json') .then(response => { console.log(response) this.setState({ideas: response.data}) }) .catch(error => console.log(error)) }
Теперь, если мы обновим страницу … она не будет работать!
Мы получим ошибку «Нет заголовка Access-Control-Allow-Origin», потому что наш API находится на другом порту, и мы не включили Cross Source Resource Sharing (CORS).
Включение перекрестного общего доступа к ресурсам (CORS)
Итак, давайте сначала включим CORS, используя гем rack-cors в нашем приложении Rails.
Добавьте драгоценный камень в Gemfile:
gem 'rack-cors', :require => 'rack/cors'
Установите это:
bundle install
Затем добавьте конфигурацию промежуточного программного обеспечения в файл config/application.rb
:
config.middleware.insert_before 0, Rack::Cors do allow do origins 'http://localhost:3000' resource '*', :headers => :any, :methods => [:get, :post, :put, :delete, :options] end end
Мы ограничиваем источники нашего внешнего приложения по адресу http://localhost:3000
и разрешаем доступ к стандартным методам конечной точки API REST для всех ресурсов.
Теперь нам нужно перезапустить сервер Rails, и если мы обновим браузер, мы больше не получим ошибку CORS.
Страница загрузится нормально, и мы увидим данные ответов, записанные в консоли.
Теперь, когда мы знаем, что можем извлекать идеи из нашего API, давайте использовать их в нашем компоненте React.
Мы можем изменить функцию рендеринга, чтобы перебирать идеи списка из состояния и отображать каждое из них:
render() { return ( <div> {this.state.ideas.map((idea) => { return( <div className="tile" key={idea.id} > <h4>{idea.title}</h4> <p>{idea.body}</p> </div> ) })} </div> ); }
Это покажет все идеи на странице сейчас.
Обратите внимание на key
атрибут на плитке div.
Мы должны включить его при создании списков элементов. Клавиши помогают React определить, какие элементы были изменены, добавлены или удалены.
Теперь давайте добавим немного стилей в App.css
чтобы каждая идея выглядела как плитка:
.tile { height: 150px; width: 150px; margin: 10px; background: lightyellow; float: left; font-size: 11px; text-align: left; }
Мы устанавливаем высоту, ширину, цвет фона и заставляем плитки плавать влево.
Функциональные компоненты без сохранения состояния
Прежде чем мы продолжим, давайте проведем рефакторинг нашего кода и переместим JSX для плиток идей в отдельный компонент под названием Idea
.
import React from 'react' const Idea = ({idea}) => <div className="tile" key={idea.id}> <h4>{idea.title}</h4> <p>{idea.body}</p> </div> export default Idea
Это функциональный компонент без сохранения состояния (или, как его называют некоторые, «тупой» компонент), что означает, что он не обрабатывает никакое состояние. Это чистая функция, которая принимает некоторые данные и возвращает JSX.
Затем внутри функции карты в IdeasContainer
мы можем вернуть новый компонент Idea:
{this.state.ideas.map((idea) => { return (<Idea idea={idea} key={idea.id} />) })}
Не забудьте также импортировать Idea
:
import Idea from './Idea'
Отлично, так что первая часть нашего приложения завершена. У нас есть API с конечной точкой для получения идей и приложение React для отображения их в виде плиток на доске!
Добавление новой записи
Далее мы добавим способ создания новых идей.
Давайте начнем с добавления кнопки, чтобы добавить новую идею.
Внутри функции рендеринга в IdeasContainer
добавьте:
<button className="newIdeaButton"> New Idea </button>
И давайте добавим немного стилей для этого в App.css
:
.newIdeaButton { background: darkblue; color: white; border: none; font-size: 18px; cursor: pointer; margin-right: 10px; margin-left: 10px; padding:10px; }
Теперь, когда мы нажимаем кнопку, мы хотим, чтобы появилась другая плитка с формой для редактирования идеи.
Как только мы отредактируем форму, мы хотим отправить ее в наш API, чтобы создать новую идею.
Конечная точка API для создания новой идеи
Итак, начнем с создания конечной точки API для создания новых идей в IdeasController
:
def create @idea = Idea.create(idea_params) render json: @idea end private def idea_params params.require(:idea).permit(:title, :body) end
Поскольку в Rails используются idea_params
параметры , мы определяем закрытый метод idea_params
для idea_params
в белый список idea_params
нам параметров — title
и body
.
Теперь у нас есть конечная точка API, в которую мы можем публиковать данные идей и создавать новые идеи.
Вернувшись в наше приложение React, теперь давайте добавим обработчик addNewIdea
к кнопке новой идеи:
<button className="newIdeaButton" onClick={this.addNewIdea} > New Idea </button>
Давайте определим addNewIdea
как функцию, которая использует axios для вызова POST нашей новой конечной точки идеи с пустой идеей. Давайте пока просто запишем ответ на консоль:
addNewIdea = () => { axios.post( 'http://localhost:3001/api/v1/ideas', { idea: { title: '', body: '' } } ) .then(response => { console.log(response) }) .catch(error => console.log(error)) }
Теперь, если мы попробуем нажать на кнопку новой идеи в браузере, мы увидим в консоли, что ответ содержит объект данных с нашей новой идеей с пустым заголовком и телом.
Когда мы обновляем страницу, мы видим пустую плитку, представляющую нашу новую идею.
Что мы действительно хотим, чтобы это произошло, так это то, что, когда мы нажимаем кнопку новой идеи, идея создается немедленно, и на странице появляется форма для редактирования этой идеи.
Таким образом, мы можем использовать ту же форму и логику для редактирования любой идеи позже в руководстве.
Прежде чем мы это сделаем, давайте сначала упорядочим идеи на странице в обратном хронологическом порядке, чтобы самые новые идеи появлялись вверху.
Итак, давайте изменим определение @ideas
в IdeasController
чтобы упорядочить идеи в порядке убывания их времени created_at
:
module Api::V1 class IdeasController < ApplicationController def index @ideas = Idea.order("created_at DESC") render json: @ideas end end end
Хорошо, теперь последние идеи отображаются в первую очередь.
Теперь давайте продолжим с определением addNewIdea
.
Сначала давайте воспользуемся ответом на наш вызов POST
чтобы обновить массив идей в состоянии, чтобы при добавлении новой идеи она сразу появлялась на странице.
Мы могли бы просто push
новую идею в массив, поскольку это только пример приложения, но рекомендуется использовать неизменяемые данные.
Итак, давайте используем immutability-helper , который является хорошим пакетом для обновления данных без непосредственного их изменения.
Установите его с помощью npm:
npm install immutability-helper --save
Затем импортируйте функцию update
в IdeasContainer
:
import update from 'immutability-helper'
Теперь давайте использовать его внутри addNewIdea
чтобы вставить нашу новую идею в начало массива идей:
addNewIdea = () => { axios.post( 'http://localhost:3001/api/v1/ideas', { idea: { title: '', body: '' } } ) .then(response => { console.log(response) const ideas = update(this.state.ideas, { $splice: [[0, 0, response.data]] }) this.setState({ideas: ideas}) }) .catch(error => console.log(error)) }
Мы создаем новую копию this.state.ideas
и используем команду $splice
чтобы вставить новую идею (в response.data
) в 0-й индекс этого массива.
Затем мы используем этот новый массив идей для обновления состояния с помощью setState
.
Теперь, если мы попробуем приложение в браузере и нажмем кнопку новой идеи, сразу появится новая пустая плитка.
Теперь мы можем приступить к редактированию этой идеи.
Во-первых, нам нужно новое свойство editingIdeaId
, которое отслеживает, какая идея редактируется в данный момент.
По умолчанию мы не редактируем ни одной идеи, поэтому давайте инициализируем editingIdeaId
в состоянии с нулевым значением:
this.state = { ideas: [], editingIdeaId: null }
Теперь, когда мы добавляем новую идею, в дополнение к добавлению ее в state.ideas
, мы также хотим установить ее id в качестве значения state.editingIdeaId
. Итак, давайте setState
вызов addNewIdea
в addNewIdea
чтобы включить также set editingIdeaId
:
this.setState({ ideas: ideas, editingIdeaId: response.data.id })
Таким образом, это означает, что мы только что добавили новую идею и хотим немедленно ее отредактировать.
Полная функция addNewIdea
теперь выглядит так:
addNewIdea = () => { axios.post( 'http://localhost:3001/api/v1/ideas', { idea: { title: '', body: '' } } ) .then(response => { const ideas = update(this.state.ideas, { $splice: [[0, 0, response.data]] }) this.setState({ ideas: ideas, editingIdeaId: response.data.id }) }) .catch(error => console.log(error)) }
Компонент формы
Теперь мы можем использовать state.editingIdeaId
в функции рендеринга, чтобы вместо отображения обычной плитки идей мы могли отображать форму.
Внутри функции map
давайте изменим возвращаемое значение на условный оператор, который отображает компонент IdeaForm
если идентификатор идеи совпадает с state.editingIdeaId
, в противном случае отображается компонент Idea
:
{this.state.ideas.map((idea) => { if(this.state.editingIdeaId === idea.id) { return(<IdeaForm idea={idea} key={idea.id} />) } else { return (<Idea idea={idea} key={idea.id} />) } })}
Давайте импортируем компонент IdeasContainer
в IdeasContainer
:
import IdeaForm from './IdeaForm'
И давайте определим это в IdeaForm.js
. Начнем с простого компонента класса, который отображает форму с двумя полями ввода для заголовка и тела идеи:
import React, { Component } from 'react' import axios from 'axios' class IdeaForm extends Component { constructor(props) { super(props) this.state = { } } render() { return ( <div className="tile"> <form> <input className='input' type="text" name="title" placeholder='Enter a Title' /> <textarea className='input' name="body" placeholder='Describe your idea'></textarea> </form> </div> ); } } export default IdeaForm
Давайте добавим немного CSS в App.css
для App.css
формы:
.input { border: 0; background: none; outline: none; margin-top:10px; width: 140px; font-size: 11px; } .input:focus { border: solid 1px lightgrey; } textarea { resize: none; height: 90px; font-size: 11px; }
Теперь, когда мы нажимаем на кнопку новой идеи, появляется новая плитка с формой:
Теперь давайте сделаем эту форму функциональной!
Нам нужно подключить поля ввода формы к состоянию.
Во-первых, давайте инициализируем IdeaForm
состояния компонента IdeaForm
из idea
которую он получает от IdeasContainer
:
class IdeaForm extends Component { constructor(props) { super(props) this.state = { title: this.props.idea.title, body: this.props.idea.body } }
Затем установите значения полей формы в соответствующие значения состояния и установите обработчик onChange
:
<form> <input className='input' type="text" name="title" placeholder='Enter a Title' value={this.state.title} onChange={this.handleInput} /> <textarea className='input' name="body" placeholder='Describe your idea' value={this.state.body} onChange={this.handleInput}> </textarea> </form>
Мы определим handleInput
таким образом, чтобы при вводе в любом из полей ввода соответствующее значение состояния, а затем значение поля обновлялось:
handleInput = (e) => { this.setState({[e.target.name]: e.target.value}) }
Отслеживание изменений состояния в React Developer Tools
Давайте посмотрим на эти изменения состояния в действии с расширением браузера React Developer Tools. Вы можете получить его для Chrome здесь и для Firefox здесь .
После установки обновите страницу приложения и откройте консоль разработчика. Вы должны увидеть новую вкладку React.
Когда вы щелкнете по нему, вы увидите дерево компонентов нашего приложения слева и все реквизиты и состояние, связанные с каждым компонентом справа.
Сейчас мы обновляем поля формы, но мы все еще не сохраняем отредактированную идею. Итак, следующее, что нам нужно, это то, что, когда мы размываем поле формы, мы хотим отправить форму и обновить идею.
Конечная точка API для обновления идей
Во-первых, нам нужно определить конечную точку API для обновления идей. Итак, давайте добавим действие update
в IdeasController
:
def update @idea = Idea.find(params[:id]) @idea.update_attributes(idea_params) render json: @idea end
Вернувшись в IdeaForm.js
, мы установим обработчик handleBlur
с именем handleBlur
в форму:
<form onBlur={this.handleBlur} >
Мы определим handleBlur
чтобы сделать PUT
вызов нашей конечной точки API для обновления идей данными идеи из состояния. А пока давайте просто запишем ответ на консоль и посмотрим, работает ли наш вызов:
handleBlur = () => { const idea = { title: this.state.title, body: this.state.body } axios.put( `http://localhost:3001/api/v1/ideas/${this.props.idea.id}`, { idea: idea }) .then(response => { console.log(response) }) .catch(error => console.log(error)) }
Нам также нужно импортировать axios в этот файл, чтобы иметь возможность использовать его:
import axios from 'axios'
Теперь, если мы нажмем кнопку новой идеи, отредактируем ее заголовок и вычеркнем из этого поля, мы увидим наш ответ API, зарегистрированный в консоли, с новыми отредактированными данными идеи.
То же самое происходит, если мы редактируем тело и размываем это поле.
Таким образом, наш обработчик onBlur
работает, и мы можем редактировать нашу новую идею, но нам также необходимо отправить отредактированные данные идеи обратно в IdeasContainer
чтобы он также мог обновлять свое собственное состояние.
В противном случае, у state.ideas
не будет обновленного значения идеи, которую мы только что отредактировали.
Мы будем использовать метод с именем updateIdea
, который мы передадим в качестве поддержки от IdeasContainer
к IdeaForm
. Мы будем вызывать updateIdea
с данными ответа от нашего вызова API:
handleBlur = () => { const idea = { title: this.state.title, body: this.state.body } axios.put( `http://localhost:3001/api/v1/ideas/${this.props.idea.id}`, { idea: idea }) .then(response => { console.log(response) this.props.updateIdea(response.data) }) .catch(error => console.log(error)) }
Теперь в IdeasContainer
давайте отправим функцию updateIdea
в качестве опоры для IdeaForm:
<IdeaForm idea={idea} key={idea.id} updateIdea={this.updateIdea} />
Давайте определим функцию для выполнения неизменного обновления идеи в state.ideas
:
updateIdea = (idea) => { const ideaIndex = this.state.ideas.findIndex(x => x.id === idea.id) const ideas = update(this.state.ideas, { [ideaIndex]: { $set: idea } }) this.setState({ideas: ideas}) }
Сначала мы находим индекс отредактированной идеи в массиве, а затем используем команду $set
чтобы заменить старое значение новым. Наконец, мы вызываем setState для обновления state.ideas
.
Мы можем увидеть это в действии в браузере с открытой вкладкой React Developer Tools.
Отображение уведомления об успехе
Теперь мы можем добавить новую идею и отредактировать ее, но пользователь не получает визуальной обратной связи или подтверждения при сохранении идеи. Итак, давайте добавим уведомление, чтобы сообщить пользователю, когда идея была успешно сохранена.
Давайте добавим диапазон рядом с кнопкой новой идеи, чтобы отобразить уведомление от значения в состоянии:
<span className="notification"> {this.state.notification} </span>
Давайте инициализируем state.notification
как пустую строку:
constructor(props) { super(props) this.state = { ideas: [], editingIdeaId: null, notification: '' } }
Теперь каждый раз, когда идея обновляется, мы будем обновлять state.notification
уведомлением об успешном state.notification
мы хотим показать пользователю.
Поэтому в вызове setState
в updateIdea
, помимо обновления ideas
, давайте также обновим notification
:
this.setState({ ideas: ideas, notification: 'All changes saved' })
Теперь, когда мы редактируем идею и размываем поле ввода, идея сохраняется и мы видим уведомление об успехе.
Мы также хотим сбросить уведомление, как только пользователь внесет изменение, которое еще не было сохранено.
Итак, в функции IdeaForm
компонента IdeaForm
, давайте вызовем функцию resetNotification
для сброса уведомляющего сообщения:
handleInput = (e) => { this.props.resetNotification() this.setState({[e.target.name]: e.target.value}) }
Теперь, внутри функции render
IdeasContainer
, давайте также передадим resetNotification
в качестве опоры для IdeaForm
:
<IdeaForm idea={idea} key={idea.id} updateIdea={this.updateIdea} resetNotification={this.resetNotification} />
Давайте определим resetNotification
как:
resetNotification = () => { this.setState({notification: ''}) }
Теперь, когда появляется уведомление об успехе, если мы снова отредактируем идею, уведомление исчезнет.
Редактирование существующей идеи
Далее давайте добавим возможность редактировать существующую идею. Когда мы нажимаем на плитку идеи, мы хотим изменить плитку, чтобы она IdeaForm
компонент Idea
компонентом IdeaForm
для редактирования этой идеи.
Затем мы можем отредактировать идею, и она будет сохранена при размытии.
Чтобы добавить эту функцию, нам нужно добавить обработчик кликов на плитки идей.
Итак, сначала нам нужно преобразовать наш компонент Idea
из функционального компонента в компонент класса, а затем мы можем задать функцию-обработчик handleClick
для заголовка и тела.
import React, { Component } from 'react' class Idea extends Component { handleClick = () => { this.props.onClick(this.props.idea.id) } render () { return( <div className="tile"> <h4 onClick={this.handleClick}> {this.props.idea.title} </h4> <p onClick={this.handleClick}> {this.props.idea.body} </p> </div> ) } } export default Idea
Обратите внимание, что мы должны добавить this.props.
использовать значение props, потому что, в отличие от функционального компонента, мы больше не разрушаем объект props.
handleClick
вызывает this.props.onClick
с идентификатором идеи.
Теперь, внутри функции render
IdeasContainer
, давайте также передадим onClick
в качестве опоры для Idea
:
return (<Idea idea={idea} key={idea.id} onClick={this.enableEditing} />)
Мы определим enableEditing
чтобы установить значение state.editingIdeaId
для state.editingIdeaId
идеи:
enableEditing = (id) => { this.setState({editingIdeaId: id}) }
Теперь, когда мы нажимаем на плитку, она мгновенно становится редактируемой!
Когда мы нажимаем на плитку, когда форма появляется, давайте также установим фокус курсора на поле ввода заголовка.
Мы можем сделать это, добавив IdeaForm
поле ввода заголовка в IdeaForm
:
<input className='input' type="text" name="title" placeholder='Enter a Title' value={this.state.title} onChange={this.handleInput} ref={this.props.titleRef} />
Нам нужно передать ref как опору, потому что мы хотим использовать его в родительском компоненте IdeasContainer
, где мы можем определить ref как функцию обратного вызова:
<IdeaForm idea={idea} key={idea.id} updateIdea={this.updateIdea} titleRef= {input => this.title = input} resetNotification={this.resetNotification} />
Теперь мы можем использовать эту enableEditing
в enableEditing
чтобы установить фокус в поле ввода заголовка:
enableEditing = (id) => { this.setState({editingIdeaId: id}, () => { this.title.focus() }) }
Обратите внимание, что мы не вызывали this.title.focus()
как отдельную функцию после вызова setState
. Вместо этого мы передали его setState
внутри обратного вызова в качестве второго аргумента.
Мы сделали это, потому что setState
не всегда сразу обновляет компонент. Передавая фокус-вызов в обратном вызове, мы гарантируем, что он будет вызван только после обновления компонента.
Теперь, если мы попробуем приложение в браузере, когда мы нажимаем на плитку идеи, оно становится редактируемым с помощью формы, и курсор фокусируется внутри поля ввода заголовка.
Так что теперь мы можем добавлять и редактировать идеи.
Удаление идеи
Наконец, мы хотим иметь возможность удалять идеи.
Когда мы наводим курсор на плитку идеи, мы хотим, чтобы кнопка удаления (в форме красного креста) появилась в верхнем правом углу. Нажатие на этот крестик должно удалить идею и удалить плитку с доски.
Итак, давайте начнем с добавления некоторой разметки и CSS для отображения кнопки удаления при наведении курсора.
В компоненте Idea
добавьте span с классом deleteButton
и текстом «x»:
<div className="tile"> <span className="deleteButton"> x </span>
Затем давайте добавим CSS в App.css
чтобы скрыть этот диапазон по умолчанию и сделать его видимым при наведении курсора на плитку:
.deleteButton { visibility: hidden; float: right; margin: 5px; font-size: 14px; cursor: pointer; color: red; } .tile:hover .deleteButton { visibility: visible; }
Далее, давайте добавим обработчик handleDelete
к этой кнопке удаления, которая затем удалит идею:
<span className="deleteButton" onClick={this.handleDelete}> x </span>
Подобно handleClick
, мы определим handleDelete
как функцию стрелки, которая вызывает другую функцию this.props.onDelete
с идентификатором идеи плитки:
handleDelete = () => { this.props.onDelete(this.props.idea.id) }
Давайте перейдем к onDelete
в качестве опоры от IdeasContainer
:
<Idea idea={idea} key={idea.id} onClick={this.enableEditing} onDelete={this.deleteIdea} />
Мы определим deleteIdea
через мгновение, но сначала давайте добавим конечную точку API для удаления идей в IdeasController
:
def destroy @idea = Idea.find(params[:id]) if @idea.destroy head :no_content, status: :ok else render json: @idea.errors, status: :unprocessable_entity end end
Теперь давайте определим deleteIdea
в IdeasContainer
как функцию, которая выполняет вызов DELETE
для нашего API с идентификатором идеи и, в случае успеха, обновляет state.ideas
:
deleteIdea = (id) => { axios.delete(`http://localhost:3001/api/v1/ideas/${id}`) .then(response => { const ideaIndex = this.state.ideas.findIndex(x => x.id === id) const ideas = update(this.state.ideas, { $splice: [[ideaIndex, 1]]}) this.setState({ideas: ideas}) }) .catch(error => console.log(error)) }
Еще раз, мы ищем индекс удаленной идеи, используем update
с командой $splice
чтобы создать новый массив идей, а затем обновляем state.ideas
с этим.
Теперь мы можем попробовать это в браузере. Когда мы наводим курсор на плитку идеи, появляется красная кнопка удаления. Нажатие на нее удаляет идею и удаляет плитку с доски.
Ура, теперь у нас есть функциональное приложение со всеми основными функциями CRUD!
Заворачивать
В этом руководстве мы создали полное приложение CRUD с использованием Rails 5.1 API и интерфейсного приложения React.
Наш API имеет три конечных точки, по одной для создания, обновления и удаления идей.
Мы использовали Create React App, чтобы сделать наше приложение React. Это сделало настройку совершенно безболезненной и легкой. Мы могли бы погрузиться прямо в создание нашего приложения, а не настраивать что-либо
Мы использовали axios для выполнения вызовов Ajax к API и immutability-helper для обновления данных.
В будущем руководстве мы рассмотрим, как развернуть это приложение на рабочем сервере, а также добавим анимацию и переходы, чтобы оживить пользовательский интерфейс. Например, мы можем добавить новые идеи и скрыть удаленные элементы, добавить и отпустить уведомления.
Вы можете посмотреть видео версию этого урока здесь .
Вы можете увидеть полный код приложения на GitHub: