Translate

Поиск по этому блогу

Показаны сообщения с ярлыком сайт. Показать все сообщения
Показаны сообщения с ярлыком сайт. Показать все сообщения

понедельник, 13 мая 2019 г.

React-Сайт ( I ). Меню и слайдер.

Создадим полноценный сайт с меню, слайдером, обратным отчетом и прочими обычными и не очень компонентами



Что мы будем строить?

Лучше один раз увидеть , чем 100 раз услышать.

Готовый сайт на surge.sh


Все статьи этого проекта:




Начнем, как обычно с создания проекта на основе create-react-app. Если вы еще не знакомы с новой версией, то вам сюда ==> Что нового в приложении Create React App 3, а если не получилось запустить обновленную, третью версию, то вам сюда ==> cra3-boilerplate. Возмите готовое, настроенное мной решение и пользуйтесь в удовольствие.

Запуск и настройку не рассказываю. Об этом я много писал ранее. Считаю, что вы уже развернули приложение в отдельной папке, запустили его и готовы приступить к работе.

Удаляем лишнее.

В папке src удаляем все кроме App.js index.js В файле index.js оставляем только то, что будем использовать:

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

ReactDOM.render(<App />, document.getElementById("root"));


В файле App.js оставляем только то, что будем использовать:

import React from "react";

function App() {
  return <div className="App">
              My App
         </div>;
}

export default App;


Добавляем нужное.

Папки-файлы

В папке src мы создадим папку resources, а в не папку слайд-фото для нашего слайдера - img. Вы можете взять любые три изображения размером 1920px x 1200px и сразу поместить их в эту папку.

В resources положим и файл для стилизации всего приложения - style.css Все стили для нашего приложения мы поместим в папку

Зависимости

Мы будем использовать Material-UI. Ранее я уже рассказывал о том. как использовать библиотеку Materialize. Позднее вы увидите. что они очень похожи и их использование не будет вызывать у вас беспокойства.

По поводу стилизации вашего приложения, я надеюсь, что на этом этапе вы можете сами написать нужные стили для вашего приложения и я на этом останавливаться не буду.

Итак. Мы будем использовать следующие модули:
  1. @material-ui/core
  2. @material-ui/icons
  3. react-reveal
  4. react-scroll
  5. react-slick
Устанавливаем material-ui

Ядро:

// with npm
npm install @material-ui/core

// with yarn
yarn add @material-ui/core



И иконки:

npm install @material-ui/icons


Одной командой:

npm install @material-ui/core @material-ui/icons


В файл App.js импортируем стили, которые создадим создадим в ранее созданном файле стилей и будем их добавлять к классам, которые мы будем присваивать нашим элементам разметки во всем приложении. Так можно поступить в данном случае, потому что у нас компонент App будет являться родителем для всего приложения. Само приложение будет небольшое, поэтому нет смысла использовать стороннюю библиотеку.

import './resources/styles.css';

Все компоненты мы будем помещать в отдельной папке. Создадим её - Components в папке src. В ней разу создадим папку для компонента Header с одноименным названием и в ней файл Header.js .

import React from "react";

const Header = () => {
  return <div>
          Header
         </div>;
};

export default Header;


И сразу импортируем и выведем его в App.js

import React from "react";
import './resources/style.css';
import Header from "./Compomemts/Header_Footer/Header";

function App() {
  return (
    <div className="App">
      <Header />
    </div>
  );
}

export default App;


Сейчас мы просто увидим слово Header на странице приложения.

Все файлы проекта на этом этапе смотрите в репо - react-site-slider-abc
ci -m "Creating the main structure and connecting the header"
ci -m — здесь и далее - коммит.

Создание меню

Вывод логотипа и заголовка.

Импортируем в файл Header.js все необходимые для этого компоненты и создадим простую структуру на основе Material-UI.

Header.js
import React from "react";
import AppBar from "@material-ui/core/AppBar";
import Toolbar from "@material-ui/core/Toolbar";
import MenuIcon from "@material-ui/icons/Menu";
import IconButton from "@material-ui/core/IconButton";

const Header = () => {
  return (
    <AppBar
      position="fixed"
      style={{
        background: "#090050",
        padding: " 20px 0px"
      }}
    >
      <Toolbar>
        <div className="header_logo">
          <div className="font_left header_logo_title">wmg</div>
          <div className="font_left header_logo_caption">React mitting</div>
        </div>
      </Toolbar>
    </AppBar>
  );
};

export default Header;



Помимо инлайновых стилей, добавил немного и в resources/style.css

body {
    padding:0;
    margin:0;
    font-family: 'Roboto', sans-serif;
}

.font_left {
    font-family: 'Righteous', cursive;
}

/* ===============>>> HEADER <<<================ */
header {
    padding:10px 0px;
    transition: all 300ms ease-in;
}

header .header_logo {
    flex-grow: 1;
}
header .header_logo_title {
    text-transform: uppercase;
    font-size: 40px;
}

header .header_logo_caption {
    text-transform: uppercase;
}


Стили для шрифтов, я взял в Google Fonts и сразу же добавил их в header файла public/index.html В браузере пока что так:



Вывод значка сандвич меню.

Добавим вывод значка сандвич-меню в файл Header.js

import React from "react";
import AppBar from "@material-ui/core/AppBar";
import Toolbar from "@material-ui/core/Toolbar";
import MenuIcon from "@material-ui/icons/Menu";
import IconButton from "@material-ui/core/IconButton";

const Header = () => {
  return (
    <AppBar
      position="fixed"
      style={{
        background: "#090050",
        padding: " 20px 0px"
      }}
    >
      <Toolbar>
        <div className="header_logo">
          <div className="font_left header_logo_title">wmg</div>
          <div className="font_left header_logo_caption">React mitting</div>
        </div>
        <IconButton aria-label="Menu" color="inherit">
          <MenuIcon />
        </IconButton>
      </Toolbar>
    </AppBar>
  );
};

export default Header;



В браузере так:



Пока что на клик ничего не происходит. Но мы сделаем так, что по клику будет открываться сайдбар с меню справа. Вы можете это легко сделать и сверху, и снизу и слева, самостоятельно.

Пока что мы просто добавим элементу

<IconButton aria-label="Menu" color="inherit" onClick={() => console.log("OPEN")} >

Если теперь посмотретьв браузере. то в консоли мы увидим "OPEN" при клике.

Идем в "Material-UI"==> "Component Demos"==> "Drawer". Здесь вы можете выбрать различные варианты для расположения сайтбара.

Оно определяется значение атрибута anchor="top" компонента Drawer. Атрибут open={this.state.top} получает значение из состояния компонента. Функция изменяет его и от того, true / false определяется показывать или нет меню-сайдбар.

Для сохранения порядка в нашем приложении, мы создадим в папке Header_Footer отдельный файл - SideDrawer.js

Внутри него определим обычный компонент без состояния (stateless component) и импортируем в него Driwer из Material-UI. Сразу же импортируем компонент List и ListItem оттуда же. Они нам потребуются для создания самого сайдбара с элементами.

Кстати, если вы используете VS-Code, то там есть отличный плагин - Reactjs code snippets, котоый позволяет писать компоненты одной короткой командой:
  • rcc - React Class Component
  • rsc - React Stateless Component
Компонент будет принимать props


import React from 'react';
import Drawer from "@material-ui/core/Drawer";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
const SideDrawer = () => {
  return (
    <Drawer
    anhor="right"
    open={}
    onClose={()=>{}}
    >
      Drawer
    </Drawer>
  );
};

export default SideDrawer;



В open мы в пропсах передадим состояние,и функцию закрытия. Пока что подключим наш файл в Header.js и добавим функцию, которую будем передавать вниз. Добавим состояние, используя hook - useState. Проще показать:

Header.js

import React, { useState } from "react";
import AppBar from "@material-ui/core/AppBar";
import Toolbar from "@material-ui/core/Toolbar";
import MenuIcon from "@material-ui/icons/Menu";
import IconButton from "@material-ui/core/IconButton";
import SideDrawer from "./SideDrawer";
const Header = () => {
  const [drawerOpen, setDrawerOpen] = useState(false);

  const toggleDrawer = value => {
    setDrawerOpen(value);
  };
  return (
    <AppBar
      position="fixed"
      style={{
        background: "#090050",
        padding: " 20px 0px"
      }}
    >
      <Toolbar>
        <div className="header_logo">
          <div className="font_left header_logo_title">wmg</div>
          <div className="font_left header_logo_caption">React mitting</div>
        </div>
        <IconButton
          aria-label="Menu"
          color="inherit"
          onClick={() => toggleDrawer(true)}
        >
          <MenuIcon />
        </IconButton>
        <SideDrawer open={drawerOpen} onClose={value => toggleDrawer(value)} />
      </Toolbar>
    </AppBar>
  );
};

export default Header;



И доделаем SideDrawer.js

import React from "react";
import Drawer from "@material-ui/core/Drawer";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
const SideDrawer = props => {
  return (
    <Drawer
      anchor="right"
      open={props.open}
      onClose={() => props.onClose(false)}
    >
      Drawer
    </Drawer>
  );
};

export default SideDrawer;



Теперь по клику на сандвич показывается сайдбар.



Добавим разделы меню:

SideDrawer.js
import React from "react";
import Drawer from "@material-ui/core/Drawer";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
const SideDrawer = props => {
  return (
    <Drawer
      anchor="right"
      open={props.open}
      onClose={() => props.onClose(false)}
    >
      <List component="nav">
        <ListItem button onClick={() => console.log("Start Events")}>
          Event starts in
        </ListItem>

        <ListItem button onClick={() => console.log("React info")}>
          React info
        </ListItem>

        <ListItem button onClick={() => console.log(" Our Team")}>
          Our Team
        </ListItem>

        <ListItem button onClick={() => console.log("Pricing")}>
          Pricing
        </ListItem>

        <ListItem button onClick={() => console.log("Location")}>
          Location
        </ListItem>
      </List>
    </Drawer>
  );
};

export default SideDrawer;



Стилизацию придает сама библиотека Material UI, в зависимости от заданных параметров листа и его итемов. Более подробно см в документации.

На клик в консоль, пока что просто вывел название элемента.

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



Все файлы проекта на этом этапе смотрите в репо - react-site-slider-abc
ci -m "Toggle Menu"

Добавление интерактивности меню.

Можно сделать так. что при открытии страницы меню будет иметь прозрачность, а при скроле, оно будет проявляться и находиться в фиксированном состоянии, прижатое к верхнему краю. Вначале нам нужно задать большую высоту странице, чтобы получить скрол. Для этого в компоненте App добавим инлайн стиль для всего компонента:

App
import React from "react";
import "./resources/style.css";
import Header from "./Compomemts/Header_Footer/Header";

function App() {
  return (
    <div className="App" style={{ height: "2000px", background: "#BEE0FF"  }}>
      <Header />
    </div>
  );
}

export default App;



Теперь у нас появился скрол и мы можем видеть то, что наше меню остается фиксированным к верхнему краю и никак не изменяется при прокрутке вниз. Для лучшего отображения, добавили цвет странице.

Добавим меню прозрачности при позиции без скрола - то есть в самом верхнем положении.

Для этого идем в файл Header.js и нам нужно добавить "состояние" нашему компоненту, которое будет отвечать за прозрачность меню (показать или нет меню полностью).

Сделаем это с помощью хука useState и зададим начальное состояние - false

const [headerShow, setHeaderShow] = useState(false);

Там, гдде мы задавали с вами цвет компоненту AppBar, внутри метода render(), мы добавим изменение цвета компонента по условию:

background: headerShow ? "#090050" : "transparent"

Сейчас все будет выглядеть так:



Уберем тень от меню - boxShadow: 'none'

Теперь наша задача изменить значение нашей переменной headerShow на true при скроле. Для этого мы будем использовать useEffect хук, который позволяет нам заменять жизненые циклы в компонентах без состояния.

Для этого нам нужно будет получить величину скрола. Это все просто, как в ванильном JS.

window.addEventLestener('scroll', handleScroll)

Эту конструкцию мы поместим внутри функции, которая будет находиться в хуке useEffect, и для того, чтобы этот хук отслеживал только изменение переменной headerShow, мы передадим ее хуку вторым аргументом в виде массива. Это не будет создавать нагрузку на приложение, потому что хук будет вызываться только при изменении этой переменной "состояния".

Теперь создадим стрелочную функцию handleScroll в которой будем изменять значение этого "состояния" в зависимости от данных скрола.

  useEffect(() => {
    window.addEventListener("scroll", handleScroll);
  }, [headerShow]);

  const handleScroll = () =>{
    if (window.scrollY > 0) {
      setHeaderShow(true)
    } else {
      setHeaderShow(false)
    }
  }


Эту функцию можно записать еще короче, тернарным оператором, но я оставлю ее здесь так, для наглядности.В коде можно записать и так:


  const handleScroll = () =>{
    (window.scrollY > 0) ? setHeaderShow(true) : setHeaderShow(false);
  }



И даже скобки не нужны, но... Мне вообще нравится тенденция использования коротких записей, только там, где это действительно необходимо, а не там, где хочется разработчику. Красота чтения такого кода, как книги, превосходит затраты на пару килобайт, компилированног кода. Всегда есть "золотая середина" и ее стоит придерживаться!

Теперь в браузере мы получим такую картину:



Вся красота создания таких компонентов в том, что они не только легкие, но еще и могут быть использованы как модули в других проектах. Они имеют свои независимые "состояния" и могут использоваться многократно со стилизацией под каждый отдельный проект.

Все файлы проекта на этом этапе смотрите в репо - react-site-slider-abc
ci -m "Transparent Menu on Scroll events"

Слайдер

Переходим к слайдеру. В папке ресурсов, как мы и договорились ранее, есть папка с изображениями - img. все изображения имеют размер 1920 X 1200 px. Это позволит нам создавать слайдер на всю ширину окна браузера, без потери качества, в большинстве случаев.

По центру слайдера, мы создадим некий заголовок в двойной рамке. Слайдер будет сам запускаться и менять картинки непрерывно. Впрочем, вы сами сможете регулировать все его доступные функции, а их достаточно даже для требовательных разработчиков и пользователей.

В нижней части слайдера мы выведем калькулятор, который будет показывать оставшееся время (дни, часы, минуты, секунды) до намеченного события. Например - конференции React-разработчиков в Амстердаме.

Идем в проект и в папке Componentsи создадим там папку для нашего слайдера, заголовка и таймера - Sliderв ней файл index.js в который и будет собираться весь наш слайдер из трех вышеперечисленных компонентов.

Набираем - rsc и жмем Enter и у нас уже есть готовый функциональный компонент. переименовываем его в Slider. Не забываем изменить имя и в экспорте!

Всему возвращаемому элементу - div добавим стиль div style={{position:'relative'}}

Внутри него создадим два вложенных элемента в которых и будет находиться заголовок:

import React from "react";

const Slider = () => {
  return (
    <div style={{ position: "relative" }}>
      <div className="event_name">
        <div className="wrapper">React Meetting</div>
      </div>
    </div>
  );
};

export default Slider;



Импортируем все это в App.js и выводим на экран.

import React from "react";
import "./resources/style.css";
import Header from "./Compomemts/Header_Footer/Header";
import Slider from "./Compomemts/Slider";
function App() {
  return (
    <div className="App" style={{ height: "2000px", background: "#BEE0FF" }}>
      <Header />
      <Slider />
    </div>
  );
}

export default App;



На экране покажется заголовок, который мы вывели, справа, просто черным текстом. Добавим немного стилей в файле

src/resources/style.css
/* =================>>> SLIDER <<<================== */
.event_name {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 400px;
    height: 160px;
    -webkit-transform: translate(-50%, -50%);  
    transform: translate(-50%, -50%);   
    border: 2px solid white;
}

.event_name .wrapper {
    border: 2px solid white;
    height: 121px;
    margin: 6px;
    text-align: center;
    color: #fff;
    font-size: 40px;
    padding: 21px 70px 0px 70px;
    text-transform: uppercase;
}


Вы можете стилизовать все сами, как вам больше нравится.



Добавляем React-Slick

Прям там, на странице, мы возьмем cdn - ссылки для подключения css и добавим их в header нашего html - файла public/index.html

Там же на странице есть хорошие примеры подключения и использования слайдера. Добавляем его в проект:
npm install react-slick --save
Внутри папки Slider создадим файл Carousel.js, там так же создаем компонент без состояния. Импортируем сам слайдер import Slider from "react-slick"; В индексовый файл слайдера импортируем карусель: import Carousel from './Carousel'; И выводим ее на страницу перед заголовком. Возвращаемся в компонент Carousel и внутри компонента создадим константу с настройками слайдера.

    const settings = {
        dots: false,
        infinite:true,
        autoplay: true,
        speed: 500
    }


Думаю, что тут все и так понятно: dots - не показывать "точки" внизу слайдера, с бесконечной прокруткой, автоматический старт и задаем произвольно скорость.

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

src/resources/style.css
.carrousel_wrapper {
    height:700px;
    overflow: hidden;
}

.carrousel_image {
    
    background-size: cover !important;
}


Добавим ему и инлайновые стили

style={{
     height:`${window.innerHeight}px`,
     overflow:'hidden'
}}


Здесь самый интересный, это высота, которая рассчитывается динамически от высоты окна браузера. Именно благодаря этому наш слайдер будет всегда занимать полностью всю высоту любого экрана и второй компонент будет появляться только при скроле.

Если сейчас прописать инлайн этому элементу какой либо другой цвет (например: background:'red'), то мы наглядно в этом убедимся.

Идем далее и по образцу на странице библиотеки слайдера, создадим компонент Slider внутри которого получим с помощью оператора rest - ... все настройки ...settings, как если бы мы их передали по отдельности так ( Slider dots: false infinite: true и т.д). Внутри него разместим наши слайды, которые импортируем в этот файл по отдельности из папки img. зададим им динамически высоту. А сами фото пойдут как фон дива. все очень очень просто!

Приведу этот файл полностью для наглядности.

Carousel.js
import React from "react";
import Slider from "react-slick";

import slide_one from "../../resources/img/slide_one.jpg";
import slide_two from "../../resources/img/slide_two.jpg";
import slide_three from "../../resources/img/slide_three.jpg";

const Carousel = () => {
  const settings = {
    dots: false,
    infinite: true,
    autoplay: true,
    speed: 500
  };
  return (
    <div
      className="carrousel_wrapper"
      style={{
        height: `${window.innerHeight}px`,
        overflow: "hidden"
      }}
    >
      <Slider {...settings}>
        <div>
          <div
            className="carrousel_image"
            style={{
              background: `url(${slide_one})`,
              height: `${window.innerHeight}px`
            }}
          />
        </div>
        <div>
          <div
            className="carrousel_image"
            style={{
              background: `url(${slide_two})`,
              height: `${window.innerHeight}px`
            }}
          />
        </div>
        <div>
          <div
            className="carrousel_image"
            style={{
              background: `url(${slide_three})`,
              height: `${window.innerHeight}px`
            }}
          />
        </div>
      </Slider>
    </div>
  );
};

export default Carousel;



Таким образом мы с вами получили слайдер, который сам запускается, занимает всю высоту экрана и меню, которое появляется только при начале скрола страницы.



Все файлы проекта на этом этапе смотрите в репо - react-site-slider-abc
ci -m "Slider and Menu"

Обратный отсчет

Создаем сам таймер

Начнем с того, что в папке Slider создадим для этого компонента отдельный файл Timer.js c rsc (react stateless component). Сразу же импортируем его в Slider/index.js и выведем его сразу перед закрывающим последним дивом.

В компоненте Timer.js зададим общему диву класс для дальнейшей стилизации className="countdown_wrapper" Внутри него будут секции с днями, часами, минутами и секундами.

Внутри него создадим еще один див с заголовком события. Сразу после него еще один див с классом countdown_bottom и уже внутри него у нас будут 4 разных значения для вывода цифр.

Внутри него будет див с классом countdown_item, в который мы поместим еще два дива, один из которых будет для вывода значений - countdown_time, второй для вывода названия значения - countdown_tag.

Timer.js
import React from "react";

const Timer = () => {
  return (
    <div className="countdown_wrapper">
      <div className="countdown_top">Event starts in</div>
      <div className="countdown_bottom">
        <div className="countdown_item">
          <div className="countdown_time">15</div>
          <div className="countdown_tag">Days</div>
        </div>
      </div>
    </div>
  );
};

export default Timer;



В браузере пока что это выглядит очень неприглядно:



Добавим стили:

src/resources/style.css
/* =====================>>> TIMER <<<======================== */
.countdown_wrapper {
    position:absolute;
    bottom: 0px;
    color:#ffffff;
}

.countdown_wrapper .countdown_top {
    background: #ff4800;
    font-size: 19px;
    padding: 10px;
    display: inline-block;
    text-transform: uppercase;
}

.countdown_wrapper .countdown_bottom {
    display: flex;
    background: red;
    padding: 10px 20px;
}

.countdown_wrapper .countdown_time {
    font-size: 70px;
    border-right: 1px solid red;
    margin-right: 14px;
    padding-right: 49px;
}
.countdown_wrapper .countdown_item:last-child .countdown_time{
    border:none;
}

.countdown_wrapper .countdown_tag {
    text-transform: uppercase;
    font-size: 20px;
    padding-left: 7px;
}


Теперь так:



Идем в файл Timer.js.Теперь мы продублируем див с классом - countdown_item для того, чтобы на страницу выводились не только дни, но и часы, минуты и секунды.



Пока что так.

Добавляем эффект появления

Теперь мы добавим эффект появления нашего таймера при перезагрузке страницы.

Нам потребуется для этого - react-reveal

Отлично все написано на официальном сайте - react-reveal

Или на странице gitHub - github.com react-reveal

Согласно документации устанавливаем его.
npm install react-reveal --save


Здесь есть github.com react-reveal - примеры, которые вы можете смело использовать в ваших приложениях:

Теперь импортируем его в файл:

import Slide from 'react-reveal/Slide';

И обернем в него все что возвращает наш компонент (внутри return ()). Зададим ему параметры left delay={1000}

Таким образом у нас появился Fade эффект - плавного появления таймера справа.

Файл полностью:

Timer.js
import React from "react";
import Slide from "react-reveal/Slide";

const Timer = () => {
  return (
    <Slide left delay={1000}>
      <div className="countdown_wrapper">
        <div className="countdown_top">Event starts in</div>
        <div className="countdown_bottom">
          <div className="countdown_item">
            <div className="countdown_time">15</div>
            <div className="countdown_tag">Days</div>
          </div>
          <div className="countdown_item">
            <div className="countdown_time">15</div>
            <div className="countdown_tag">Hs</div>
          </div>
          <div className="countdown_item">
            <div className="countdown_time">15</div>
            <div className="countdown_tag">Min</div>
          </div>
          <div className="countdown_item">
            <div className="countdown_time">15</div>
            <div className="countdown_tag">Sec</div>
          </div>
        </div>
      </div>
    </Slide>
  );
};

export default Timer;



Добавляем логику (динамическое изменение значений)

Мы создадим эту часть таким образом, чтобы в "состояния" (хуком) сохранить некую дату события и значения Дни, часы, минуты, секунды. Затем мы создадим функцию, которая будет считать сколько осталось дней, часов, минут, секунд до этой даты и эти значения заносила в наши "переменные состояния". Эту функцию мы будем вызывать каждую секунду, после загрузки нашего приложения, при помощи setInterval, а полученные значения мы будем передавать в компонент, где и отобразим их на странице. Это проще показать в коде.

Timer.js
import React, {useState, useEffect} from "react";
import Slide from "react-reveal/Slide";

const Timer = () => {
  const  deadline = 'Dec, 16, 2019';

  const [days, setDays] = useState('0');
  const [hours , setHours] = useState('0');
  const [minutes, setMinutes] = useState('0');
  const [seconds, setSeconds] = useState('0');

     const getTimeUntil = (deadline) =>{
        const time = Date.parse(deadline) - Date.parse(new Date());
        if(time < 0) {
            console.log('Date passed')
        } else {
            const seconds = Math.floor((time/1000)%60);
            const minutes = Math.floor((time/1000/60)%60);
            const hours = Math.floor((time/(1000*60*60))%24);
            const days = Math.floor(time/(1000*60*60*24));

          setDays(days);
          setHours(hours);
          setMinutes(minutes);
          setSeconds(seconds);
        }    
    }
    useEffect(()=>{
      setInterval(()=> getTimeUntil(deadline),1000)
    },[seconds])
  return (
    <Slide left delay={1000}>
      <div className="countdown_wrapper">
        <div className="countdown_top">Event starts in</div>
        <div className="countdown_bottom">
          <div className="countdown_item">
            <div className="countdown_time">{days}</div>
            <div className="countdown_tag">Days</div>
          </div>
          <div className="countdown_item">
            <div className="countdown_time">{hours}</div>
            <div className="countdown_tag">Hs</div>
          </div>
          <div className="countdown_item">
            <div className="countdown_time">{minutes}</div>
            <div className="countdown_tag">Min</div>
          </div>
          <div className="countdown_item">
            <div className="countdown_time">{seconds}</div>
            <div className="countdown_tag">Sec</div>
          </div>
        </div>
      </div>
    </Slide>
  );
};

export default Timer;



Если посмотреть в браузер, то все "появляется и тикает". В итоге мы получили меню, которое меняется в зависимости от прокрутки, слайдер, который запускается автоматически и работает с определенным интервалом (кстати это не лишает возможности крутить его вручную мышкой), таймер, который плавно появляется при загрузке страницы и динамически отсчитывает время оставшееся до наступления события.



Все файлы проекта на этом этапе смотрите в репо - react-site-slider-abc
ci -m "TimerUntil event"



PS:
Иногда, бывают такие ситуации, что указывать deadline статически (присвоить постоянное значение) это не очень удобно. Я столкнулся с этим разместив этот учебный макет на surge. Время, в конце концов истекло и на сайте появились унылые нули.

Как исправить? Стоит задать deadline динамически, ну например, прибавлять к сегодняшней дате несколько дней. Я решил эту задачу очень просто - получил текущую дату, назначил новую и потом получил все данные новой даты deadline в нужном формате

Timer.js
import React, { useState, useEffect } from "react";
import Slide from "react-reveal/Slide";

const Timer = () => {
  let deadline = getDeadline();
  function getDeadline() {
    var D = new Date();
    D.setDate(D.getDate() + 50);

    var month = D.getMonth();

    function LitMonth(month) {
      switch (month) {
        case 0:
          month = "Jan";
          break;
        case 1:
          month = "Feb";
          break;
        case 2:
          month = "Mar";
          break;
        case 3:
          month = "Apr";
          break;
        case 4:
          month = "May";
          break;
        case 5:
          month = "Jun";
          break;
        case 6:
          month = "Jul";
          break;
        case 7:
          month = "Aug";
          break;
        case 8:
          month = "Sep";
          break;
        case 9:
          month = "Oct";
          break;
        case 10:
          month = "Nov";
          break;
        case 11:
          month = "Dec";
          break;

        default:
      }
      return month;
    }
    month = LitMonth(month);
    var day = D.getDate();
    var year = D.getFullYear();
    D = month + " " + day + " " + year;
    return D;
  }

  const [days, setDays] = useState("0");
  const [hours, setHours] = useState("0");
  const [minutes, setMinutes] = useState("0");
  const [seconds, setSeconds] = useState("0");

  const getTimeUntil = (deadline) => {
    const time = Date.parse(deadline) - Date.parse(new Date());
    if (time < 0) {
      console.log("Date passed");
    } else {
      const seconds = Math.floor((time / 1000) % 60);
      const minutes = Math.floor((time / 1000 / 60) % 60);
      const hours = Math.floor((time / (1000 * 60 * 60)) % 24);
      const days = Math.floor(time / (1000 * 60 * 60 * 24));

      setDays(days);
      setHours(hours);
      setMinutes(minutes);
      setSeconds(seconds);
    }
  };
  useEffect(() => {
    setInterval(() => getTimeUntil(deadline), 1000);
  }, [seconds]);
  return (
    <Slide left delay={1000}>
      <div className="countdown_wrapper">
        <div className="countdown_top">Event starts in</div>
        <div className="countdown_bottom">
          <div className="countdown_item">
            <div className="countdown_time">{days}</div>
            <div className="countdown_tag">Days</div>
          </div>
          <div className="countdown_item">
            <div className="countdown_time">{hours}</div>
            <div className="countdown_tag">Hs</div>
          </div>
          <div className="countdown_item">
            <div className="countdown_time">{minutes}</div>
            <div className="countdown_tag">Min</div>
          </div>
          <div className="countdown_item">
            <div className="countdown_time">{seconds}</div>
            <div className="countdown_tag">Sec</div>
          </div>
        </div>
      </div>
    </Slide>
  );
};

export default Timer;




Все что я добавил, выделено красным. Я думаю, что объяснять это не надо. С помощью библиотек, это можно было сделать проще и нагляднее, но мы решили идти по пути наименьшего использования сторонних ресурсов, поэтому так.

Все последние изменения вы можете посмотреть на gitHub react-site-slider-abc
ci -m "added dinamic deadline date"
                                                                                                                                                             

Телеграм канал - Full Stack JavaScript Developer

пятница, 22 февраля 2019 г.

React - Интерактивная таблица. (II).

Это будет вторая часть, посвященная созданию динамической интерактивной таблицы на React.js



В первой части мы сделали динамическую таблицу, в которой мы можем сортировать элементы в столбцах и выводить данные отдельного пользователя на страницу.

В этой части нам нужно добавить клиентскую пагинацию (максимум 50 элементов на странице) добавить фильтрацию - поле поиска элемента по каким-то вводимым символам. Будем показывать отфильтрованные данный из этой таблицы. которые будут соответствовать запросу.

В этом разделе нам нужно предоставить пользователю возможность выбора большого (1000 элементов) или малого (32 элемента) объемов данных для загрузки на страницу с таблицей.

Начнем с конца и предоставим пользователю такое право с помощью двух кнопок, которые будут показываться сразу на экране при загрузке самого приложения, и уже при клике на которые, пользователь может загрузик в таблицу большие (1000 элементов) или малые (32) данные.

На этом этапе код по ссылке: react-table-abc
ci -m "data output from the table row to the page"

Выбор данных для загрузки.

В коде, который мы написали в прошлый раз, мы грузим сразу маленький объем данным по ссылке в жизненном цикле компонента асинхронно.(см. код компонента App в componentDidMount())

Начнем с того, что в нашем стейте компонента App мы добавим новый флаг isModeSelected: false и изменим значение флага отвечающего за немедленную загрузку данных на страницу isLoading на false


  state ={
    isModeSelected: false,
    isLoading: false,
    data: [],
    sort: 'asc',  // 'desc'
    sortField: 'id',
    row: null,
  }



Теперь, для того, чтобы таблицы у нас не рисовались, мы воспользуемся данным флагом и в методе render проверим. Если мы не выбрали никакой мод, то есть this.state.isModeSelected мы вырисовываем простой каркас приложения с новым компонентом - ModeSelector.


if(!this.state.isModeSelected){
  return (
    <div className="container">
      <ModeSelector />
    </div>
  )
}



Создаем компонент ModeSelector Как вы догадались, в папке src создадим папку ModeSelector и в ней js-файл с таким же именем.

Хорошей практикой считается хранить компоненты в отдельной папке Components, но если их немного, как в этом проекте, то мы можем смело хранить их в src.

В написании этого компонента нет ничего сложного и вы можете написать его по своему усмотрению, или взять готовый код ниже.

Если коротко, то здесь мы создали простой компонент с использованием {} для создания тела функции, потому что в нем мы будем хранить две константы с ссылками на большие и малые данные.

Вывели две кнопки, классы для их стилизации, я взял у бутстрапа, и навесили на них событие onClick={()=>props.onSelect(smallUrl)}.

Функцию onSelect мы получаем из props в нее мы передаем константу с url-адресом. Это написание - второй способ передать параметры в функцию. В первой части таблиц, мы использовали метод bind(), а в этот раз по типу замыкания. Мы передадим сюда функцию в которой обратимся к методу onSelect из propsи передадим адрес.


import React from 'react';

export default props =>{
    const smallUrl = `http://www.filltext.com/?rows=32&id={number|1000}&firstName={firstName}&lastName={lastName}&email={email}&phone={phone|(xxx)xxx-xx-xx}&address={addressObject}&description={lorem|32}`;
    const bigUrl = `http://www.filltext.com/?rows=1000&id={number|1000}&firstName={firstName}&delay=3&lastName={lastName}&email={email}&phone={phone|(xxx)xxx-xx-xx}&address={addressObject}&description={lorem|32}`;
    return (
        <div style={{display:'flex', justifyContent:'center', padding: '50px 0'}}>
            <button onClick={()=>props.onSelect(smallUrl)} className="btn btn-success">32 элемента</button>
            <button onClick={()=>props.onSelect(bigUrl)} className="btn btn-danger">1000 элементов</button>
        </div>
    )
}


Для более красивого отображения кнопок по центру, добавили инлайн стили.

Теперь нам нужно подключить вновь созданный компонент в App, передать в него название свойства onSelect и написать сам метод, который будет передаваться как свойство onSelect, внутри компонента App.

Подключаем просто:

import ModeSelector from './ModeSelector/ModeSelector';

Передаем в пропсы - еще проше:


if(!this.state.isModeSelected){
  return (
    <div className="container">
      <ModeSelector onSelect={this.modeSelectHandler} />
    </div>
  )
}



Теперь нам нужно написать саму функцию modeSelectHandler в теле компонента:

  modeSelectHandler = url => {
     console.log(url)
  }


Как, мы и договорились, функция будет получать url , который и выведем в консоли.

Посмотрим на работу приложения в консоли:



При клике на кнопки, мы получаем разные ссылки по которым и будут подгружаться большие и малые данные для таблицы.

На этом этапе код по ссылке: react-table-abc
ci -m "Creating a component with buttons for links. Stylization, output to the console url - addresses."

Вывод данных на страницу.

Теперь нам необходимо, после клика скрыть этот элемент и загрузить необходимые данные.

Это мы можем сделать в методе modeSelectHandler изменив состояние компонента, а именно флаги isModeSelected и isLoading на true

И создадим там же какой-либо метод, куда мы и передадим url - this.fetchData(url)


  modeSelectHandler = url => {
    // console.log(url)
    this.setState({
      isModeSelected: true,
      isLoading: true,
    })
    this.fetchData(url)
  }



Самый простой способ реализовать метод fetchData(url) это переписать под него жизненный цикл компонента componentDidMount(). Мы просто изменим это название на название метода. Единственное что, так это то, что он будет принимать некий url, по которому мы должны сделать запрос.


  async fetchData(url) {
    const response = await fetch(url)
    const data = await response.json()

    this.setState({
      isLoading: false,
      data: _.orderBy(data, this.state.sortField, this.state.sort)
    })

  }


Изменения стейта оставили без изменений. Все изменения я отметил в коде красным.

Теперь можно посмотреть на приложение в браузере.

При клике будет показываться лоудер и загружаться большая или малая таблица.

На этом этапе код по ссылке: react-table-abc
ci -m "Data output table by clicking on the buttons"

Рефактринг кода

Немного поменяем метод onSort.

Сейчас он выглядит так:


  onSort = sortField => {
    
    const cloneData = this.state.data.concat();
    const sortType = this.state.sort === 'asc' ? 'desc' : 'asc';
    const orderedData = _.orderBy(cloneData, sortField, sortType);

    this.setState({
      data: orderedData,
      sort: sortType,
      sortField
    })
  }



Мы можем немного изменить названия переменных, для того, чтобы в изменениях стейта у нас остались только флаги, так как их названия будут совпадать.


  onSort = sortField => {
    
    const cloneData = this.state.data.concat();
    const sort = this.state.sort === 'asc' ? 'desc' : 'asc';
    const data = _.orderBy(cloneData, sortField, sort);

    this.setState({
      data,
      sort,
      sortField
    })
  }


Теперь мы можем записать установщик новых стейтов в одну строку:


    onSort = sortField => {
      const cloneData = this.state.data.concat();
      const sort = this.state.sort === 'asc' ? 'desc' : 'asc';
      const data = _.orderBy(cloneData, sortField, sort);
      this.setState({ data, sort, sortField})
   }



На этом этапе код по ссылке: react-table-abc
ci -m "Refactoring onSort function"

Пагинация

Делать пагинацию самостоятельно с ноля мы не буем, так как есть готовые компоненты, а логика там фиксированная, поэтому мы воспользуемся почти готовым решением.

а) Создание компонента пагинации

Можно посмотреть на то, как реализована пагинация в бутстрапе - pagination Bootstrap - здесь есть готовая верстка. Отсюда мы возьмем названия нужных нам классов, в дальнейшем, для некоторой стилизации нашего компонента пагинации.

Для создания пагинации мы будем использовать готовый npm-модуль - react-paginate. Вы можете все почитать на странице, а проще сразу посмотреть на демонстрацию его работы. по ссылке со страницы.

Устанавливаем модуль:

npm install react-paginate --save


Импортируем данный компонент в App:

import ReactPaginate from 'react-paginate';

Сам компонент мы скопируем из этого же кода на странице-демо.


        <ReactPaginate
          previousLabel={'previous'}
          nextLabel={'next'}
          breakLabel={'...'}
          breakClassName={'break-me'}
          pageCount={this.state.pageCount}
          marginPagesDisplayed={2}
          pageRangeDisplayed={5}
          onPageChange={this.handlePageClick}
          containerClassName={'pagination'}
          subContainerClassName={'pages pagination'}
          activeClassName={'active'}
        />



Этот компонент пагинации, нам нужно поставить после таблицы.

Здесь нам нужно задать условие, чтобы пагинация показывалась только когда на странице более 50 элементов.

Количество элементов определим в костанту - pageSize в методе render

this.state.data.length > pageSize

переименуем в скопированном компоненте метод handlePageClick в pageChangeHandler и создадим его в компоненте.

Этот метод будет вызываться при нажатии на кнопку изменения страницы.


  pageChangeHandler = page => (
    console.log(page)
  )



Напишем его в компоненте App и передадим в него некий объект - page и посмотрим на него в консоли при загрузке 1000 элементов:



При клике на страницу нам приходит объект selected с номером страницы. Обраите внимание, что нумерация страниц начинается с ноля. То есть, при клике на 5, в selected придет -4, при клике на 6 - 5, соответственно.

Пагинация выглядит очень неважно. Если посмотреть в раздел Elements разработчика, то можно заметить, что её html-структура очень похожа на разметку Bootstrap.



Таким образом мы для стилизации можем просто добавить нужные классы.

На странице компонента мы можем посмотреть те свойства, которые мы можем использовать для стилизации нашего компонента.


      {
        this.state.data.length > pageSize
        ? <ReactPaginate
        previousLabel={'<'}
        nextLabel={'>'}
        breakLabel={'...'}
        breakClassName={'break-me'}
        pageCount={20}
        marginPagesDisplayed={2}
        pageRangeDisplayed={5}
        onPageChange={this.pageChangeHandler}
        containerClassName={'pagination'}
        activeClassName={'active'}
        pageClassName="page-item"
        pageLinkClassName="page-link"
        previousClassName="page-item"
        nextClassName="page-item"
        previousLinkClassName="page-link"
        nextLinkClassName="page-link"
      /> : null
      }



pageCount={20} пока что сделали = 20, но потом будем задавать динамически.

Строку subContainerClassName={'pages pagination'} убрал полностью. Она нам не нужна.

Добавил стили бутстрапа этим свойствам компонента. Отметил красным.

Посмотрим в браузер и увидим, что нужным элементам нашего компонента мы добавили классы бутстрап.



На этом можно закончить стилизацию компонента.

На этом этапе код по ссылке: react-table-abc
ci -m "creation and stylization of pagination."

б) Связь пагинации с стейтом.

Зная, что номер страницы мы получим в объекте selected, мы можем получить его сразу методом деструктуризации компонента функции - ({selected}).

В теле мы будем изменять номер страницы, поэтому в стейт по умолчанию добавим новый флаг currentPage с номером - 0


  class App extends Component {
  state ={
    isModeSelected: false,
    isLoading: false,
    data: [],
    sort: 'asc',  // 'desc'
    sortField: 'id',
    row: null,
    currentPage: 0,
  }



В теле функции мы будем изменять это поле:


    pageChangeHandler = ({selected}) => (
    this.setState({currentPage: selected})
  )



в) Оживляем пагинацию.

Посмотрим на логику отображения таблицы. У нас есть лимит полей для страницы:

const pageSize = 50;

и в стейтах поле data: [], содержащее все элементы. Поэтому мы в методе render() создадим отдельную переменную, которую мы будем передавать прямо в таблицу.

Нам очень важно не изменять поле data, иначе мы сотрем все данные.

Тысяча элементов пусть хранится в стейте, а отображать мы будем только 50.

Для этого мы создадим переменную

const displayData = _.chunk(filteredData, pageSize)[this.state.currentPage]

Эта константа будет хранить определенную часть данных, которая будет зависеть от номера текущей станицы (currentPage из стейта). Здесь мы воспользовались методом _.chunk библиотеки Lodash.

Этот метод принимает первым аргументом массив, а вторым - число элементов под массива (длина под массивов). Более подробно об этом вы можете прочитать в документации Lodash ._chunk.

Здесь мы получаем результирующий массив данных таблиицы:

_.chunk(filteredData, pageSize)

и нам остается обратиться к его индексу, чтобы забрать нужную часть -

[this.state.currentPage]

Таким образом мы получим в переменную displayData нужные данные. И теперь нам остается передать эту переменную в data таблицы:


  {
    this.state.isLoading 
    ? <Loader />
    : <Table 
          data={displayData}
          onSort={this.onSort}
          sort={this.state.sort}
          sortField={this.state.sortField}
          onRowSelect={this.onRowSelect}
      />
  }


Можно посмотреть на приложение в браузере. Мы увидим, что на одну страницу загрузилось ровно 50 элементов.



Теперь при изменении страницы, будет динамически меняться содержимое таблицы.

На этом этапе код по ссылке: react-table-abc
ci -m "Pagination is connected."

г) Синхронизация номера страницы с состоянием нашего стейта.

Это будет необходимо, потому что в будущем это потребуется. Для этого нам нужно воспользоваться свойством forcePage со страницы компонента пагинации npm react-paginate.

Этот параметр позволяет переписывать текущую страницу с родительским свойством. С помщь. него мы синхронизируем номер страницы с состоянием стейта.

Добавим его в самый низ записи компонента:


{
    this.state.data.length > pageSize
    ? <ReactPaginate
    previousLabel={'<'}
    nextLabel={'>'}
    breakLabel={'...'}
    breakClassName={'break-me'}
    pageCount={pageCount}
    marginPagesDisplayed={2}
    pageRangeDisplayed={5}
    onPageChange={this.pageChangeHandler}
    containerClassName={'pagination'}
    activeClassName={'active'}
    pageClassName="page-item"
    pageLinkClassName="page-link"
    previousClassName="page-item"
    nextClassName="page-item"
    previousLinkClassName="page-link"
    nextLinkClassName="page-link"
    forcePage={this.state.currentPage}
  /> : null
  }



На этом этапе стоит убедиться. что пагинация работает и ничего не сломалось. Работают фильтры и при загрузке 32 элемента, пагинации нет, а при загрузке 1000 эл. пагинация, фильтрация и вывод отдельного компонента на страницу продолжаю работать.

На этом этапе код по ссылке: react-table-abc
ci m "Synchronize page number with state"

Добавляем поиск.

Для этого создадим еще один "глупый" компонент с именем TableSearch. Это подразумеваем создание одноименной папки и файла с расширением .js в папке src.

В этом компоненте мы будем использовать React-Hocks, потому что мы хотим взаимодействовать с состоянием компонента не смотря на то, что он "глупый".

Я, в ближайшее время напишу пост по основным хукам Реакт, а пока что приведу компонент полностью и попробую далее объяснить его действие.


import React, {useState} from 'react'

export default props => {
    const [value, setValue] = useState('')
    const valueChangeHandler = event => {
        setValue(event.target.value)
      }

    return (
        <div className="input-group mb-3 mt-3">
             <div className="input-group-prepend">
                 <button 
                    className="btn btn-outline-secondary"
                    onClick={() => props.onSearch(value)} >Search</button>
            </div>
            <input 
                type="text" 
                className="form-control"
                onChange={valueChangeHandler} 
                value={value}
            />
        </div>
    )
}



Все что возвращает этот компонент, есть ничто иное как обычное поле поиска с кнопкой, которое я взял с сайта Bootstrap Button addons.

Поменял class на className и добавил закрывающий / для input.

Импортируем наш новый компонент в App:

import TableSearch from './TableSearch/TableSearch';

Данный компонент нам нужно отрисовать, только тогда, когда у нас загрузились данные. Поэтому, мы разместим компонент поиска над таблицей и все это вместе обернет в компонент реакта, чтобы не добавлять ненужный div.


{
    this.state.isLoading 
    ? <Loader />
    : <React.Fragment>
        <TableSearch />
        <Table 
          data={displayData}
          onSort={this.onSort}
          sort={this.state.sort}
          sortField={this.state.sortField}
          onRowSelect={this.onRowSelect}
        />
      </React.Fragment>

  }



В компоненте TableSearch.js cсейчас только метод return



import React from 'react'

export default props => {

    return (
        <div className="input-group mb-3 mt-3">
             <div className="input-group-prepend">
                 <button className="btn btn-outline-secondary" >Search</button>
            </div>
            <input 
                type="text" 
                className="form-control"
            />
        </div>
    )
}



Для отступа сверху добавили только mt-3.

Если посмотреть в браузер, то можно увидеть готовый, но совершенно нерабочий компонент.



На этом этапе код по ссылке: react-table-abc
ci -m "Add layout new component - search field"

Хук заменяющий стейт в реакт

Теперь нам нужно сделать так, чтобы мы что-то писали в поле поиска и после нажатия на кнопку, в наших таблицах происходила фильтрация элементов. То есть так, чтобы только соответствующие поисковому запросу элементы показывались на странице.

Так как нам придется отслеживать наш input, то нам потребуется стейт.

И коль скоро, мы используем реакт 16.8, то мы будем использовать хуки.

Импортируем useState


import React, {useState} from 'react'

export default props => {
    const [value, setValue] = useState('')
    const valueChangeHandler = event => {
        setValue(event.target.value)
      }

    return (
        <div className="input-group mb-3 mt-3">
             <div className="input-group-prepend">
                 <button 
                    className="btn btn-outline-secondary"
                    onClick={() => props.onSearch(value)} >Search</button>
            </div>
            <input 
                type="text" 
                className="form-control"
                onChange={valueChangeHandler} 
                value={value}
            />
        </div>
    )
}



value - это и будет само состояние.

Добавили метод, который будет отслеживать изменение состояния (импута) - valueChangeHandler.

В теле компонента создадим саму эту функцию, которая будет менять "стейт", точнее нашу переменную value, с помощью setValue.

Вот таким образом мы смогли написать изменение стейта в "глупом" компоненте используя реакт-хуки.

Теперь, нам нужно при нажатии на кнопку, передавать состояние данной строки. Для этого кнопке добавим:

onClick={() => props.onSearch(value)}

И так как мы получаем onSearch с пропертями, то нам этот метод нужно передать в компоненте App.


{
    this.state.isLoading 
    ? <Loader />
    : <React.Fragment>
        <TableSearch onSearch={this.searchHandler} />
        <Table 
          data={displayData}
          onSort={this.onSort}
          sort={this.state.sort}
          sortField={this.state.sortField}
          onRowSelect={this.onRowSelect}
        />
      </React.Fragment>

  }



В теле компонента App создадим сам метод - searchHandler


 searchHandler = search =>(
    console.log(search)
  )



Проверим в консоли, правильно ли мы получаем строку поиска и что она из себя представляет.

Идем в браузер. Вписываем какую-либо строку, например - User, получаем ее в консоли, после нажатия на кнопку поиска.



На этом этапе код по ссылке: react-table-abc
ci -m"We receive data from impu search in the console."

Добавляем фильтрацию элементов для поиска.

На данном этапе неважно какие поля нам нужно фильтровать, потому что мы всегда сможем их легко изменить или добавить.

Вначале создадим поле search:'' в стейте компонента App


  state ={
    isModeSelected: false,
    isLoading: false,
    data: [],
    search: '',
    sort: 'asc',  // 'desc'
    sortField: 'id',
    row: null,
    currentPage: 0,
  }



Теперь в методе searchHandler мы будем изменять состояние и сбрасывать текущую страницу, для того, чтобы поиск шел по всему документу корректно. - currentPage: 0


  searchHandler = search => {
    this.setState({search, currentPage: 0})
  }



Именно для корректной работы поиска мы создавали параметр forcePage={this.state.currentPage}, ранее.

Мы изменяем стейт и реакт будет заходить в метод рендер, после того, как мы будем изменять search.

Теперь в зависимости от search нам нужно показывать data. То есть в переменную displayData положить данные не только по количеству отсортированные, но и отфильтрованные по полю поиска.

Для этого в рендере, рядом с этой констатой, мы создадим еще одну:

const filteredData = this.getFilteredData()

В нее положим данные, которые мы будем получать, например из getFilteredData()

Теперь напишем этот метод в теле компонента App


  getFilteredData(){
    const {data, search} = this.state

    if (!search) {
      return data
    }

    return data.filter(item => {
      return item['firstName'].toLowerCase().includes(search.toLowerCase())
        || item['lastName'].toLowerCase().includes(search.toLowerCase())
        || item['email'].toLowerCase().includes(search.toLowerCase())
    })
  }



Здесь мы заберем из стейта два поля деструктуризацией - const {data, search} = this.state

Далее, прстая проверка. Если в поле search ничего нет, то возвращаем обычные данные - data

Если в search что-то есть, то мы будем отфильтровывать данные таблицы.

Переменная item попадет в массив, если будет соответствовать условиям фильтрации, иначе мы ее убираем.

Теперь мы берем под-поля - firstName, lastName, email и проверяем, есть ли там нужная подстрока?

Для корректной работы мы приведем все к нижнему регистру. - .toLowerCase

Для проверки на подстроку, мы воспользуемся методом includes(), который вернет true если содержится. Иначе false.

Если нужна фильтрация, только по определенному полю, то ненужные поля можно просто удалить.

Теперь нам остается поменять pageCount={20} в ретурне App компонента - ReactPaginate на pageCount={pageCount} и высчитать его значение динамически:

В рендер метод добавим, ниже const filteredData = this.getFilteredData(), потому что кол-во страниц будет зависить от этой константы:

const pageCount = Math.ceil(filteredData.length / pageSize)

Здесь, я думаю, что все понятно. Округляем, потому что может быть дробное число. Делим количество отфильтрованных элементов на кол-во элементов на странице и получаем - кол-во страниц, которые будут показаны для пагинации отфильтрованных элементов.

Таким образом, мы создали таблицу, в которой можно сортировать столбцы, выводить элемент отдельно, фильтровать элементы по поиску, выводить на страницу определенное количество элементов - пагинация. И предоставили пользователю возможность загружать малое и большое кол-во данных.

На этом этапе код по ссылке: react-table-abc
ci -m "add filtering in search"

Да, и если возникли трудности в коде, не знаете где ошибка, то - debager пишем в коде, и далее, в хроме дебажим все по шагам.

Готовое приложение здесь

Первая часть <-

Хотите освоить самые современные методы написания React приложений? Надоели простые проекты? Нужны курсы, книги, руководства, индивидуальные занятия по React и не только? Хотите стать разработчиком полного цикла, освоить стек MERN, или вы только начинаете свой путь в программировании, и не знаете с чего начать, то пишите через форму связи, подписывайтесь на мой канал в Телеге, вступайте в группу на Facebook.


Удачного кодирования!                                                                                                                                                              

Телеграм канал - Full Stack JavaScript Developer

понедельник, 11 февраля 2019 г.

React - Интерактивная таблица. (I)

Сегодня мы буде создавать интерактивную таблицу с помощью библиотеки React.js



Нам нужно создать простую таблицу, которая будет выводить данные с сервера и предоставит нам некоторые возможности, в частности - вывод всех данных из ячейки на страницу отдельно, сортировку данных в столбцах таблицы. До загрузки данных с сервера, должен показываться слайдер загрузки.

Готовое приложение здесь

Приложение будем создавать с помощью готового решения от разработчиков Facebook - create-react-app.

Я не буду на этом останавливаться, потому что много раз уже рассказывал об этом в постах о React. О самом приложении, вы сможете подробно почитать на странице github - /facebook/create-react-app

Здесь все просто. Создаем папку с проектом переходим в нее и ... Начнем с удаления ненужных файлов и удаления их импортов.

В папке src я оставил только три файла: App.js, index.js и serviceWorker.js

В файле App.js я удалил все ненужные подключения (импорты) и внутренность компонента App, а так как мы будем использовать Bootstrap для отображения наших компонентов, то я сразу добавил компоненту класс Bootstrap - className="container"


import React, { Component } from 'react';


class App extends Component {

  render() {
    return (
      <div className="container">
          MyApp
      </div>
    );
  }
}

export default App;



Устанавливаем и подключаем Bootstrap

Для простоты реализации наших задач с таблицей, мы будем использовать библиотеку Bootstrap. Для ее установки нам необходимо, находиться в папке нашего проекта (Если вы используете Visual Studio Code, то просто - создать терминал, а если КС - командную строку Windows или терминал, то перейти в папку проекта и набрать:

npm install bootstrap


или, если вы используете yarn

yarn add bootstrap


Я буду использовать npm, поэтому здесь и далее команды для npm.

После того, как закончится установка, в файле package.json, в разделе зависимостей - dependencies вы увидите установленные пакеты "bootstrap": "^4.2.1"



Теперь нам нужно его подключить в файле index.js


import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import 'bootstrap/dist/css/bootstrap.min.css'

ReactDOM.render(, document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();



Теперь мы можем запустить сервер и проверить подключение Bootstrap и работоспособность приложения.

В консоли набираем:

npm start


Сервер запустится и наше приложение запустится на порту 3000.

Для остановки сервера, вы можете использовать сочетание клавиш Ctrl + C



На фото выше, вы можете видеть, что сервер запустился, приложение работает и Bootstrap подключен ( на контейнер действуют стили самого Bootstrap - отмечено красным).

На этом этапе код по ссылке: react-table-abc
ci-m (здесь и далее - коммит) "deleted unnecessary files and import bootstrap"

Получаем данные.

Данные для таблицы мы будем получать из сети по этой ссылке. http://www.filltext.com/?rows=32&id={number|1000}&firstName={firstName}&lastName={lastName}&email={email}&phone={phone|(xxx)xxx-xx-xx}&address={addressObject}&description={lorem|32}

Если перейти по ней, то можно увидеть все данные, которые будут переданы нам в формате json



Для читабельности данных и их красивого отображения. я использую расширение для браузера - JSONView

Очень важно здесь понять, что нам нужно получить данные из сети тогда, когда у нас будет уже сформировано DOM дерево.Именно поэтому мы будем использовать жизненный цикл React компонента, а именно - componentDidMount

Написание кода в новом формате, позволяет нам использовать новые возможности, а именно асинхронные запросы- asinc/await. Если вы хотите вспомнить детали, то посмотрите в моем блоге

Для получения данных, мы используем метод fetch() в который передадим эту ссылку внутри обратных кавычек.

Вся конструкция в компоненте App будет выглядеть так:


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

class App extends Component {

  async componentDidMount() {
    const response = await fetch(` http://www.filltext.com/?rows=32&id={number|1000}&firstName={firstName}&lastName={lastName}&email={email}&phone={phone|(xxx)xxx-xx-xx}&address={addressObject}&description={lorem|32}`)
    const data = await response.json()
     console.log(data)
  }
  render() {
    return (
      <div className="container">
         MyApp
      </div>
    );
  }
}

export default App;




Таким образом мы вернем в объект response промис. А в объект data положим результат работы response и его метода - JSON.

Для отображения объекта data мы его выведем в консоль.

Если посмотреть сейчас в консоль приложения, то мы увидим:



Если посмотреть на отдельный элемент, то мы увидим все данные, которые нам нужно будет отобразить на странице в виде таблицы:



Мы увидим, что в объекте data содержатся все те данные, которые мы хотели получить из сети по запросу.

На этом этапе код по ссылке: react-table-abc
ci -m "Asinc request to the date for table"

Индикатор загрузки.

Сейчас перед нами стоит задача отображать индикатор загрузки на странице до тех пор, пока мы не получили данные по сети для формирования таблицы.

Для решения этой задачи, мы не будем выдумывать свой велосипед и возьмем готовое решение с сайта Pure CSS Loaders.

В папке src мы создадим отдельную, одноименную папку для компонента Loader и поместим туда два файла: Loader.js и Loader.css.

Теперь выбираем на сайте понравившийся индикатор, нажимаем на него и получаем код для css и html.

Как вы догадались, css-код мы отправляем в файл Loader.css практически без изменений. Я только изменил цвет индикатора на синий и добавил ему позиционирование по центру страницы:


.lds-dual-ring {
    width: 64px;
    height: 64px;
    position: absolute;
    top: 50%;
    left: 50%;
  }
  .lds-dual-ring:after {
    content: " ";
    display: block;
    width: 46px;
    height: 46px;
    margin: 1px;
    border-radius: 50%;
    border: 5px solid rgb(40, 3, 253);
    border-color: rgb(40, 3, 253) transparent rgb(40, 3, 253) transparent;
    animation: lds-dual-ring 1.2s linear infinite;
  }
  @keyframes lds-dual-ring {
    0% {
      transform: rotate(0deg);
    }
    100% {
      transform: rotate(360deg);
    }
  }



В файле Loader.js мы импортируем сам React для создания компонента и стили.

Далее, создадим "глупый" простой компонент, как безымянную функцию и сразу же ее экспортируем. Функция будет возвращать пустой div элемент, которому мы дадим класс, который взяли из html - кода нашего индикатора загрузки:


import React from 'react';
import './Loader.css';

export default () => <div className="lds-dual-ring" />



При написании компонентов, стоит помнить, что задавать их через классы и делать их "умными" нужно только тогда, когда компонент будет хранить какую-то логику действий. А если он только отображает какой-то элемент на странице, то лучше всего написать его простой функцией.


Это поможет избежать ненужных ошибок и сделает ваше приложение более легким для работы, загрузки и масштабирования, обслуживания в дальнейшем.

Сейчас нам нужно импортировать компонент Loader в компонент App и вывести его на страницу, вместо текста "MyApp".

Это очень простая задача и я не буду на ней останавливаться. Если не получится сделать самостоятельно, то я приведу код App компонента полностью ниже.



На этом этапе код по ссылке: react-table-abc
ci -m "Loader"

Формируем таблицу.

Для таблицы нам потребуется компонент Table для которого мы создадим отдельную папку в папке src и в ней файл Table.js.

Так как, мы договорились создавать "умные компоненты" только там где нужно, то этот компонент будет тоже простым.

В него мы будем выводить данные в виде таблицы. Для этого мы будем использовать готовую таблицу, которую мы возьмем с сайта Bootstrap Bootstrap Tables- отсюда полностью скопируем thead таблицы. В коде ниже - отметил синим.

В компоненте мы будем возвращать саму таблицу table className="table", внутри поместим thead и ниже него создадим tbody, куда мы и будем выводить нами полученные данные из переменной data, которые мы передадим в виде props нашему компоненту Table в компоненте App.

Если я, все-таки вас запутал, то не огорчайтесь, начнем все по порядку.

Представим, что наш компонент Table уже получил эти данные в виде props с именем data. Наша задача вывести их по отдельности в каждую ячейку таблицы. Для этого мы используем метод map() В коде ниже отметил красным.


import React from 'react';

export default props => (
    <table className="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>First Name</th>
                <th>Last Name</th>
                <th>E-mail</th>
                <th>Phone</th>
            </tr>
        </thead>
        <tbody>
            { props.data.map(item =>(
                <tr key={item.id}>
                    <td>{item.id}</td>
                    <td>{item.firstName}</td>
                    <td>{item.lastName}</td>
                    <td>{item.email}</td>
                    <td>{item.phone}</td>
                </tr>
            ))}
        </tbody>
    </table>
)



Таким образом, мы выводим нужные нам ячейки (столбцы) в оду строку таблицы для каждого отдельного объекта данных.

Теперь, переходим в компонент App и там импортируем нашу таблицу - компонент Table

import Table from './Table/Table';

Сейчас нам нужно сделать так, чтобы пока идет загрузка, показывать компонент Loader, как только загрузка закончилась, то Table

Для этого мы в компоненте App создадим состояние - state, в котором создадим поле isLoading: со значением true и поле data, которое равно пустому массиву.


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

class App extends Component {
  state ={
    isLoading: true,
    data: [],
  }
  async componentDidMount() {
    const response = await fetch(` http://www.filltext.com/?rows=32&id={number|1000}&firstName={firstName}&lastName={lastName}&email={email}&phone={phone|(xxx)xxx-xx-xx}&address={addressObject}&description={lorem|32}`)
    const data = await response.json()
   console.log(data)
  }
  render() {
    return (
      <div className="container">
      {
        this.state.isLoading 
        ? <Loader />
        : <Table 
        data={this.state.data}
        />
      }
      </div>
    );
  }
}

export default App;



В методе render мы проверим. Если this.state.isLoading , то мы показываем компонент Loader, а если данные уже загрузили, то показываем таблицу.

В сам компонент Table мы передадим данные, о которых говорили выше - data={this.state.data}

Теперь нам нужно изменить state, как только загрузятся данные.

Для этого мы в методе жизненного цикла компонента, после получения данных, вместо вывода их в консоль, будем менять состояние компонента:


  async componentDidMount() {
    const response = await fetch(` http://www.filltext.com/?rows=32&id={number|1000}&firstName={firstName}&lastName={lastName}&email={email}&phone={phone|(xxx)xxx-xx-xx}&address={addressObject}&description={lorem|32}`)
    const data = await response.json()
    // console.log(data)
    this.setState({
      isLoading: false,
      data
    })

  }



В функции изменения состояния мы имеем право просто написать data, потому что у нас совпадают ключ и значение (data: data).

Все вместе:


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

class App extends Component {
  state ={
    isLoading: true,
    data: [],
  }
  async componentDidMount() {
    const response = await fetch(` http://www.filltext.com/?rows=32&id={number|1000}&firstName={firstName}&lastName={lastName}&email={email}&phone={phone|(xxx)xxx-xx-xx}&address={addressObject}&description={lorem|32}`)
    const data = await response.json()
    // console.log(data)
    this.setState({
      isLoading: false,
      data
    })

  }
  render() {
    return (
      <div className="container">
      {
        this.state.isLoading 
        ? <Loader />
        : <Table 
        data={this.state.data}
        />
      }
      </div>
    );
  }
}

export default App;



Посмотрим на приложение:



Мы увидим предупреждение. Это значит, что нам передали два элемента с одинаковым id, поэтому нам нужно внести некоторые изменения в наш ключ - key в файле Table.js и сделать его равным не просто id, а прибавить к нему, например телефонный номер для большей уникальности:

<tr key={item.id + item.phone}>

Теперь у нас есть таблица с данными.

На этом этапе код по ссылке: react-table-abc
ci -m "create table"

Сортировка элементов в таблице.

Наша задача сделать так, чтобы по клику на заголовок таблицы, данные в каждом столбце сортировались в порядке возрастания (чисел и по алфавиту). При повторном клике, сортировка в обратную сторону.

Для этого нам нужно написать функцию сортировки (мы это сделаем с помощью библиотеки Lodash), передать ее в виде props компоненту Table, повесить событие onClickс этой функцией на заголовок таблицы и передать в эту функцию название столбца ( id, firstName, lastName или phone ).

Начнем в обратном порядке, а именно - на заголовки таблицы повесим события onClickконтекст мы передавать не будем, поэтому первым параметром передадим -null, а вторым параметром передадим название столбца, который нужно сортировать.

onClick={props.onSort.bind(null, 'id')}

Table.js

<tr>
  <th onClick={props.onSort.bind(null, 'id')}>ID</th>
  <th onClick={props.onSort.bind(null, 'firstName')}>First Name</th>
  <th onClick={props.onSort.bind(null, 'lastName')}>Last Name</th>
  <th onClick={props.onSort.bind(null, 'email')}>E-mail</th>
  <th onClick={props.onSort.bind(null, 'phone')}>Phone</th>
</tr>



Теперь, нам нужно передать эту функцию в виде свойства компоненту <Table /> : onSort={this.onSort}


{
  this.state.isLoading 
  ? <Loader />
  : <Table 
  data={this.state.data}
  onSort={this.onSort}
  />
}



Сразу над методом render(), компонента App мы напишем саму эту функцию.

Для сохранения контекста мы напишем ее в виде стрелочной функции.

Название параметру - sortField дали произвольное.


onSort = sortField => (
    console.log(sortField)
  )


В методе мы посмотрим, что из себя будут представлять эти поля, которые мы передаем в эту функцию:



Как видно на фото выше, при клике мы получаем значение столбца. который нам нужно фильтровать.

Собственно сортировка.

Для упорядочивания элементов по алфавиту и в цифровом порядке, мы обратимся к orderBy Lodash.

Добавим ее в наш проект.

npm i --save lodash


После того, как завершится установка, в зависимостях (package.json) появится нужная нам библиотека:



Теперь нам нужно ее импортировать. Для этого идем в файл App.js и добавляем следующую строку:

import _ from 'lodash';

В методе onSort создадим копию нашего массива:

const cloneData = this.state.data.concat();

Это делаем для того, чтобы у нас никак не менялся state.data в состоянии компонента.

Теперь нам нужно отсортировать эту копию массива по определенному полю, которое мы передаем в функцию в виде аргумента - sortField и в определенном направлении

Направление мы сохраним в стейтах как sort:'asc'. Стоит помнить, что оно может меняться на 'desc'


  state ={
    isLoading: true,
    data: [],
    sort: 'asc',  // 'desc'
    sortField: 'id', // поле по умолчанию
  }


Теперь нам нужно определить направление. Назовем переменную - sortType и проверим, что у нас лежит в свойстве sort наших стейт. И если значение переменной будет 'asc', то мы меняем его на 'desc' и наоборот.

const sortType = this.state.sort === 'asc' ? 'desc' : 'asc';

Создадим переменную для отсортированного массив - orderedData. Метод сортировки напишем по примеру документации. передадим туда копию нашего массива - cloneData, вторым параметром нам нужно указать поле по которому мы сортируем - sortField и направление - sortType

const orderedData = _.orderBy(cloneData, sortField, sortType);

В итоге мы получаем отсортированные данные и нам остается изменить состояние нашего компонента - this.setState({}).

Здесь важно определить поля, которые мы будем менять.

Поле data мы меняем на orderedData, sort на sortType и поле sortField на sortField.

sortField нужно для того чтобы мы знали какое поле мы сортировали. Поэтому добавили его в наши стейт с значением 'id' по умолчанию.

Метод onSort компонента App.js полностью:

  onSort = sortField => {
    
    const cloneData = this.state.data.concat();
    const sortType = this.state.sort === 'asc' ? 'desc' : 'asc';
    const orderedData = _.orderBy(cloneData, sortField, sortType);

    this.setState({
      data: orderedData,
      sort: sortType,
      sortField
    })
  }



Теперь можно посмотреть на результат в браузере. Нажимаем на LastName и происходит сортировка в этом столбце по алфавиту.



Еще раз нажимаем и сортировка начинается в обратном порядке:



Таким образом можно нажимать на названия любого столбца и получать сортировку элементов в прямом или обратном порядке.

Визуальное отображение сортировки.

Во первых мы будем передавать в компонент Table два параметра из стейта компонента App. За направление отвечает -sort и за поле - sortField. При изменении сортировки они будут меняться в методе this.setState({}), который мы написали ранее.


      {
        this.state.isLoading 
        ? <Loader />
        : <Table 
        data={this.state.data}
        onSort={this.onSort}
        sort={this.state.sort}
        sortField={this.state.sortField}
        />
      }


Мы их передали в наш компонент и теперь мы их проверим.

{props.sortField === 'id' ? <small>{props.sort}</small> : null}

Проще говоря, если в sortField нам будет передан 'id', то мы покажем некий компонент - <small>{props.sort}</small> на экране (в данном случае в поле заголовка - ID), а если нет, то ничего не покажем - null.

Такие конструкции, только с соответствующими значениями мы добавим во все поля заголовка таблицы:


<thead>
  <tr>
      <th onClick={props.onSort.bind(null, 'id')}>
          ID {props.sortField === 'id' ? <small>{props.sort}</small> : null}
      </th>
      <th onClick={props.onSort.bind(null, 'firstName')}>
          First Name {props.sortField === 'firstName' ? <small>{props.sort}</small> : null}
      </th>
      <th onClick={props.onSort.bind(null, 'lastName')}>
          Last Name {props.sortField === 'lastName' ? <small>{props.sort}</small> : null}
      </th>
      <th onClick={props.onSort.bind(null, 'email')}>
          E-mail {props.sortField === 'email' ? <small>{props.sort}</small> : null}
      </th>
      <th onClick={props.onSort.bind(null, 'phone')}>
          Phone {props.sortField === 'phone' ? <small>{props.sort}</small> : null}
      </th>
  </tr>
</thead>



Посмотрим в браузере. Если нажать на заголовок - ID, то появится метод сортировки - (asc), а при повторном нажатии - (desc)



Единственная проблема, что при перезагрузки страницы, мы по умолчанию в поле ID показываем сортировку asc, хотя в действительности никакой сортировки еще нет.

Поэтому, чтобы не путать пользователя, мы в компоненте App, в момент получения данных в сет-стейт метода componentDidMount() добавим сортировку:


  async componentDidMount() {
    const response = await fetch(` http://www.filltext.com/?rows=32&id={number|1000}&firstName={firstName}&lastName={lastName}&email={email}&phone={phone|(xxx)xxx-xx-xx}&address={addressObject}&description={lorem|32}`)
    const data = await response.json()
   
    this.setState({
      isLoading: false,
      data: _.orderBy(data, this.state.sortField, this.state.sort)
    })

  }



Теперь, при перезагрузке или получении данных с сервера, мы всегда будем видеть отсортированный массив. Таким образом направление добавляется вполне правомерно.

Сейчас мы можем проверить сортировку по всем полям.



На этом этапе код по ссылке: react-table-abc
ci -m "Sort items in columns and display sorting options in the header."

Как задание для тренировки: вы можете заменить строку <small>{props.sort}</small> на отдельный компонент в котором вывести отдельно индикаторы сортировки в виде картинок или иконок.

Вывод информации по клику.

Нам нужно сделать так, чтобы при клике по любой ячейке, строке, выводилась ВСЯ информация о пользователе на страницу, ниже нашей таблицы, отдельно.

Для этого мы в компоненте Table каждому элементу tr добавим обработчик события:

onClick={props.onRowSelect.bind(null, item)}

передадим туда item

Table.js часть:


<tbody>
  { props.data.map(item =>(
      <tr key={item.id + item.phone} onClick={props.onRowSelect.bind(null, item)}>
          <td>{item.id}</td>
          <td>{item.firstName}</td>
          <td>{item.lastName}</td>
          <td>{item.email}</td>
          <td>{item.phone}</td>
      </tr>
  ))}
</tbody>



Сейчас нам нужно написать сам метод в компоненте App и передать его в Table Передаем его в компонент:


{
  this.state.isLoading 
  ? <Loader />
  : <Table 
  data={this.state.data}
  onSort={this.onSort}
  sort={this.state.sort}
  sortField={this.state.sortField}
  onRowSelect={this.onRowSelect}
  />
}


Выше метода render в компоненте App пишем сам метод, который будет принимать в качестве параметра row и выведем его в консоль:


  onRowSelect = row => (
    console.log(row)
  )



Проверим, что же у нас будет в консоли?



При клике по строке, мы получаем все данные пользователя в консоль. Для того, чтобы вывести данные, нам нужно в стейт добавить новое свойство - row


  state ={
    isLoading: true,
    data: [],
    sort: 'asc',  // 'desc'
    sortField: 'id',
    row: null,
  }


И вместо вывода в консоль данных объекта в методе onRowSelectсделать изменение стейта:


  onRowSelect = row => (
    this.setState({row})
  )


Теперь в этом же компоненте мы будем выводить детальное отображение этой строчки. Для этого нам потребуется новый компонент DetailRowView и некоторая логика:


      {
        this.state.row ? <DetailRowView person={this.state.row} /> : null
      }


Если в this.state.row что-то есть, то покажем новый компонент, а если нет, то ничего не покажем.

В компонент мы передадим состояние.

Создадим сам компонент

В папке src создадим папку- DetailRowView с одноименным js-файлом. Создадим простой компонент и выведем какой-либо текст на экран:


import React from 'react';

export default () => (
   <div>
     <h1>DetailRowView</h1>
   </div>
)


Не забываем импортировать этот компонент в App.js

import DetailRowView from './DetailRowView/DetailRowView';

Посмотрим в браузере.



При клике по любому компоненту внутри таблицы, мы будем видеть текст внизу.

Данные из props получим сразу путем деструктуризации. ({props})

Теперь нам стоит посмотреть на то, что получает данный компонент в консоли на вкладке Networks



Зная данные, которые мы получаем из сети, не составляет труда вывести их из props в компоненте. остается просто добавить нормальную верстку для нашего компонента.

DetailRowView.js

import React from 'react';

export default ({person}) => (
    <div>
    <p>Выбран пользователь <b>{person.firstName + ' ' + person.lastName}</b></p>
    <p>
    Описание: <br />
    <textarea defaultValue={person.description} />
    </p>

    <p>Адрес проживания: <b>{person.address.streetAddress}</b></p>
    <p>Город: <b>{person.address.city}</b></p>
    <p>Провинция/штат: <b>{person.address.state}</b></p>
    <p>Индекс: <b>{person.address.zip}</b></p>

  </div>
)


Если теперь мы посмотрим на то, что получилось и нажмем на какие-либо данные любого пользователя, то внизу, под таблицей, мы увидим ВСЕ данные пользователя в удобном формате.



Таким образом мы справились с начальным заданием и создали интерактивную таблицу с помощью React. Предела совершенству нет. Вы можете самостоятельно сделать пагинацию, добавит компонент для вывода порядка сортировки и много чего еще.

На этом этапе код по ссылке: react-table-abc
ci -m "data output from the table row to the page"

Продолжение ->

Хотите освоить самые современные методы написания React приложений? Надоели простые проекты? Нужны курсы, книги, руководства, индивидуальные занятия по React и не только? Хотите стать разработчиком полного цикла, освоить стек MERN, или вы только начинаете свой путь в программировании, и не знаете с чего начать, то пишите через форму связи, подписывайтесь на мой канал в Телеге, вступайте в группу на Facebook.


Удачного кодирования!                                                                                                                                                              

Телеграм канал - Full Stack JavaScript Developer


Хотите освоить самые современные методы написания React приложений? Надоели простые проекты? Нужны курсы, книги, руководства, индивидуальные занятия по React и не только? Хотите стать разработчиком полного цикла, освоить стек MERN, или вы только начинаете свой путь в программировании, и не знаете с чего начать, то пишите через форму связи, подписывайтесь на мой канал в Телеге, вступайте в группу на Facebook.Пишите мне - kolesnikovy70 почта gmail.com