На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: ElcnU, ANDLL, fatalist
  
> Изучаю React , modern style: ES7, Babel, webpack, bootstrap
    Изучаю React, а вы? :huh:
    Вот набросал примерчик может кому пригодится для изучения
    в нем я максимльно следовал современому стилю программирования на React

    Сам пока до конца не изучил все тонкости React, но думаю заброшу Backbone и перейду на React :D

    /public/index.html:
    ExpandedWrap disabled
      <!DOCTYPE html>
      <html>
       
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <meta http-equiv="x-ua-compatible" content="ie=edge">
        <title>React in action</title>
      </head>
       
      <body>
        <div id="app">
          React in action
        </div>
       
        <script src="/app.js" charset="utf-8"></script>
      </body>
       
      </html>


    /source/app.js:
    ExpandedWrap disabled
      import React from 'react';
      import {render} from 'react-dom';
      import Menu from './menu';
      import data from './data/recipes';
      import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
       
      const App = () =>
        <div className="container">
          <Menu recipes={data}/>
        </div>
       
      render(<App/>, document.getElementById('app'));


    /source/menu.js:
    ExpandedWrap disabled
      import React from 'react';
      import Recipe from './recipe';
       
      const Menu = ({recipes}) =>
        <article>
          <header>
            <h1>Delicious Recipes</h1>
          </header>
          <div className="recipes">
            {recipes.map((recipe, i) =>
              <Recipe key={i} {...recipe}/>)}
          </div>
        </article>;
       
      export default Menu;


    /source/recipe.js:
    ExpandedWrap disabled
      import React from 'react';
      import IngredientsList from './ingredients-list';
      import Instructions from './instructions';
       
      const Recipe = ({name, ingredients, steps}) =>
        <section id={name.toLowerCase().replace(/\s/g, '-')}>
          <h2>{name}</h2>
          <IngredientsList list={ingredients}/>
          <Instructions title="Cooking Instructions" steps={steps}/>
        </section>;
       
      export default Recipe;


    /source/ingredient-list.js:
    ExpandedWrap disabled
      import React from 'react';
      import Ingredient from './ingredient';
       
      const IngredientsList = ({list}) =>
        <ul className="ingredients">
          {list.map((ingredient, key) =>
            <Ingredient key={key} {...ingredient}/>)}
        </ul>;
       
      export default IngredientsList;


    /source/intructions.js:
    ExpandedWrap disabled
      import React from 'react';
       
      const Instructions = ({title, steps}) =>
        <section className="instructions">
          <h3>{title}</h3>
          {steps.map((step, i) =>
            <p key={i}>{step}</p>)}
        </section>;
       
      export default Instructions;


    /source/ingredient.js
    ExpandedWrap disabled
      import React from 'react';
       
      const Ingredient = ({amount, measurement, name}) =>
        <li>
          <span className="amount">{amount}</span>
          <span className="measurement">{measurement}</span>
          <span className="name">{name}</span>
        </li>;
       
      export default Ingredient;



    package.json:
    ExpandedWrap disabled
      {
        "name": "learn-react",
        "version": "1.0.0",
        "description": "",
        "private": true,
        "dependencies": {
          "axios": "^0.18.0",
          "bootstrap": "^4.1.1",
          "express": "^4.16.3",
          "react": "^16.3.2",
          "react-dom": "^16.3.2",
        },
        "devDependencies": {
          "babel-core": "^6.26.0",
          "babel-loader": "^7.1.4",
          "babel-plugin-transform-object-rest-spread": "^6.26.0",
          "babel-preset-env": "^1.6.1",
          "babel-preset-react": "^6.24.1",
          "css-loader": "^0.28.11",
          "nodemon": "^1.17.4",
          "style-loader": "^0.21.0",
          "webpack": "^4.8.3",
          "webpack-cli": "^2.0.15"
        },
        "scripts": {
          "start": "nodemon server.js",
          "build": "webpack --config webpack.config.js"
        },
        "author": "Mudosoft",
        "license": "UNLICENSED"
      }


    webpack.config.js:
    ExpandedWrap disabled
      const path = require('path');
       
      module.exports = {
        mode: 'development',
        entry: './source/app.js',
        output: {
          path: path.resolve(__dirname, 'public'),
          filename: 'app.js',
          sourceMapFilename: 'app.map'
        },
        devtool: '#source-map',
        module: {
          rules: [
            {
              test: /\.js$/,
              use: [
                {
                  loader: 'babel-loader',
                  options: {
                    presets: ['react', 'env'],
                    plugins: [
                      'transform-object-rest-spread'
                    ]
                  }
                }
              ],
              exclude: /(node_modules)/
            },
            {
              test: /\.css$/,
              use: [
                'style-loader',
                'css-loader'
              ]
            }
          ]
        }
      };


    Прикреплённый файлПрикреплённый файлreact_in_action.zip (4,46 Кбайт, скачиваний: 71)

    пс. чтобы все заработало надо выполнить команду npm install, далее npm run build, далее npm start, далее в броузере Chrome набрать localhost:3000 ВСЕ!
    Сообщение отредактировано: Cfon -
      Цитата Cfon
      думаю заброшу Backbone


      что, так много на нём уже сделал, что аж бросать приходится? :whistle:
        Цитата K313 @
        что, так много на нём уже сделал, что аж бросать приходится? :whistle:

        да много всего уже написано на Backbone ~10k строк гавнокода :D
          хотя Backbone ешо на плаву поскольку можно юзать его в связке с React
          надо будет переделать Simple Blog с React+Backbone для практики :D
            Итак продолжаю изучать React!
            добавил два компонента Summary и StarRating.
            в первом показано как в React работать с типами и значениями по умолчанию,
            второй вводит интерактивность в приложение, в связи в этим было введено состояние (state) у компонента App!

            смотрите и учитесь, ну или критикуйте :D

            summary.js:
            ExpandedWrap disabled
              import React, {Component} from 'react';
              import PropTypes from 'prop-types';
               
              class Summary extends Component {
                static propTypes = {
                  ingredientsCount: PropTypes.number,
                  stepsCount: PropTypes.number
                }
                static defaultProps = {
                  ingredientsCount: 0,
                  stepsCount: 0
                }
                render() {
                  const {ingredientsCount, stepsCount} = this.props;
                  return (
                    <div className="summary">
                      <p>{ingredientsCount} ingredients | {stepsCount} steps</p>
                    </div>
                  );
                }
              }
               
              export default Summary;


            star-rating.js:
            ExpandedWrap disabled
              import React from 'react';
              import '../css/star.css'
               
              const Star = ({selected=false, onClick=()=>{}}) =>
                <div className={selected ? 'star selected' : 'star'}
                  onClick={onClick}>
                </div>;
               
              const StarRating = ({starsSelected=0, totalStars=5, onRate=()=>{}}) =>
                <div className="star-rating">
                  {[...Array(totalStars)].map((n, i) =>
                    <Star key={i} selected={i < starsSelected} onClick={()=>onRate(i+1)}/>
                  )}
                  <p>{starsSelected} of {totalStars} stars</p>
                </div>;
               
              export default StarRating;


            recipe.js:
            ExpandedWrap disabled
              import React from 'react'
              import IngredientsList from './ingredients-list'
              import Instructions from './instructions'
              import StarRating from './star-rating'
               
              const Recipe = ({name, ingredients, steps, rating, onRate}) =>
                <section id={name.toLowerCase().replace(/\s/g, '-')}>
                  <h2>{name}</h2>
                  <StarRating starsSelected={rating} onRate={onRate}/>
                  <IngredientsList list={ingredients}/>
                  <Instructions title="Cooking Instructions" steps={steps}/>
                </section>
               
              export default Recipe;


            menu.js:
            ExpandedWrap disabled
              import React from 'react';
              import Recipe from './recipe';
              import Summary from './summary';
               
              const Menu = ({recipes=[], onRate=()=>{}}) => {
                return <article>
                  <header>
                    <h1 className="text-white bg-dark">Delicious Recipes</h1>
                  </header>
                  <div className="recipes">
                    {recipes.map((recipe, i) =>
                      <div key={i}>
                        <Recipe {...recipe} onRate={rating => onRate(recipe.id, rating)}/>
                        <Summary ingredientsCount={recipe.ingredients.length}
                          stepsCount={recipe.steps.length}/>
                        <hr/>
                      </div>)}
                  </div>
                </article>
              }
               
              export default Menu;


            app.js:
            ExpandedWrap disabled
              import React, {Component} from 'react';
              import {render} from 'react-dom';
              import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
              import Menu from './components/menu';
              import data from '../db/recipes';
               
              class App extends Component {
                constructor(props) {
                  super(props);
                  this.state = {
                    recipes: props.data.recipes
                  };
                  this.doRate = this.doRate.bind(this);
                }
                doRate(id, rating) {
                  const {recipes} = this.state;
                  this.setState({
                    recipes: recipes.map(recipe => {
                      return (id !== recipe.id)
                        ? recipe
                        : {
                          ...recipe,
                          rating
                        }
                    })
                  });
                }
                render() {
                  const {recipes} = this.state;
                  return (
                    <div className="container">
                      <Menu recipes={recipes} onRate={this.doRate}/>
                    </div>
                  );
                }
              }
               
              render(<App data={data}/>, document.getElementById('app'));


            Прикреплённый файлПрикреплённый файлreact_in_action_v2.zip (6,9 Кбайт, скачиваний: 137)
            Сообщение отредактировано: Cfon -
              А теперь самое интересное!
              Добавляем форму для добавления рецепта, она будет состоять из трех частей: AddRecipeForm, AddIngredients и AddInstructions.
              Здесь продемонстрировано как получить доступ к элементам DOM через this.refs для сбора информации из формы, также еще раз поработаем с состоянием, но локальным, оно необходимо для временного хранения списка инградиентов и инструкций добавляемого рецепта!
              Вот смотрите мой перл что получилось :D

              add-recipe-form.js:
              ExpandedWrap disabled
                import React, {Component} from 'react';
                import IngredientList from './ingredients-list';
                import AddIngredients from './add-ingredients';
                 
                class AddRecipeForm extends Component {
                  static defaultProps = {
                    onNewRecipe() {}
                  }
                 
                  constructor() {
                    super();
                    this.state = {
                      ingredients: []
                    };
                    this.doSubmitRecipe = this.doSubmitRecipe.bind(this);
                    this.doAddIngredient = this.doAddIngredient.bind(this);
                    this.doClearIngredientList = this.doClearIngredientList.bind(this);
                  }
                 
                  doSubmitRecipe(event) {
                    event.preventDefault();
                    const {_recipeName} = this.refs;
                    const {onNewRecipe} = this.props;
                    const {ingredients} = this.state;
                    onNewRecipe(_recipeName.value, ingredients);
                    this.setState({
                      ingredients: []
                    });
                    _recipeName.value = '';
                    _recipeName.focus();
                  }
                 
                  doAddIngredient(ingredient) {
                    const {ingredients} = this.state;
                    this.setState({
                      ingredients: [
                        ...ingredients,
                        ingredient
                      ]
                    });
                  }
                 
                  doClearIngredientList() {
                    this.setState({
                      ingredients: []
                    });
                  }
                 
                  render() {
                    const {ingredients} = this.state;
                    return (
                      <div>
                        <h3>Add Recipe</h3>
                        <form onSubmit={this.doSubmitRecipe}>
                          <input className="form-control" ref="_recipeName" type="text" placeholder="Recipe name" required/>
                          <div className="card mt-2">
                            <div className="card-body">
                              {
                                (ingredients.length !== 0)
                                  ? <IngredientList list={ingredients}/>
                                  : <p>No ingredients</p>
                              }
                              <AddIngredients onNewIngredient={this.doAddIngredient}
                                onClearIngredientList={this.doClearIngredientList}/>
                            </div>
                          </div>
                          {/* TODO: add AddInstructions Element */}
                          <button className="btn btn-primary mt-2">Add Recipe</button>
                        </form>
                      </div>
                    );
                  }
                }
                 
                export default AddRecipeForm;


              add-ingredients.js:
              ExpandedWrap disabled
                import React, {Component} from 'react';
                 
                class AddIngredients extends Component {
                  static defaultProps = {
                    onNewIngredient(ingredient) {},
                    onClearIngredientList() {}
                  }
                 
                  constructor() {
                    super();
                    this.doAddIngredient = this.doAddIngredient.bind(this);
                    this.doClearIngredientList = this.doClearIngredientList.bind(this);
                  }
                 
                  doAddIngredient(event) {
                    event.preventDefault();
                    const {_ingredientName, _amount, _measurement} = this.refs;
                    const {onNewIngredient} = this.props;
                    onNewIngredient({
                      name: _ingredientName.value,
                      amount: _amount.value,
                      measurement: _measurement.value
                    });
                    this.clearInputs();
                  }
                 
                  doClearIngredientList(event) {
                    event.preventDefault();
                    const {onClearIngredientList} = this.props;
                    onClearIngredientList();
                    this.clearInputs();
                  }
                 
                  clearInputs() {
                    const {_ingredientName, _amount, _measurement} = this.refs;
                    _ingredientName.value = '';
                    _amount.value = '';
                    _measurement.value = '';
                    _amount.focus();
                  }
                 
                  render() {
                    return (
                      <div className="ingredient-form">
                        <input className="form-control" ref="_amount" type="text" placeholder="Amount"/>
                        <input className="form-control" ref="_measurement" type="text" placeholder="Measurement"/>
                        <input className="form-control" ref="_ingredientName" type="text" placeholder="Ingredient name"/>
                        <div className="mt-2">
                          <button className="btn btn-secondary mr-2" onClick={this.doAddIngredient}>Add Ingredient</button>
                          <button className="btn btn-secondary mr-2" onClick={this.doClearIngredientList}>Clear Ingredient List</button>
                        </div>
                      </div>
                    );
                  }
                }
                 
                export default AddIngredients;


              app.js (с внесеными изменениями):
              ExpandedWrap disabled
                import React, {Component} from 'react';
                import {render} from 'react-dom';
                import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
                import Menu from './components/menu';
                import data from '../db/recipes';
                import AddRecipeForm from './components/add-recipe-form';
                import {v4} from 'uuid';
                 
                class App extends Component {
                 
                  constructor(props) {
                    super(props);
                    this.state = {
                      recipes: props.data.recipes
                    };
                    this.doRate = this.doRate.bind(this);
                    this.doAddRecipe = this.doAddRecipe.bind(this);
                  }
                 
                  doRate(id, rating) {
                    const {recipes} = this.state;
                    this.setState({
                      recipes: recipes.map(recipe => {
                        return (id !== recipe.id)
                          ? recipe
                          : {
                            ...recipe,
                            rating
                          }
                      })
                    });
                  }
                 
                  doAddRecipe(name, ingredients) {
                    const {recipes} = this.state;
                    this.setState({
                      recipes: [
                        ...recipes,
                        {
                          id: v4(),
                          name,
                          ingredients,
                          steps: [],
                          rating: 0
                        }
                      ]
                    });
                  }
                 
                  render() {
                    const {recipes} = this.state;
                    return (
                      <div className="container">
                        <Menu recipes={recipes} onRate={this.doRate}/>
                        <AddRecipeForm onNewRecipe={this.doAddRecipe}/>
                      </div>
                    );
                  }
                }
                 
                render(<App data={data}/>, document.getElementById('app'));

              Прикреплённый файлПрикреплённый файлreact_in_action_v3.zip (8,58 Кбайт, скачиваний: 143)

              Нет элемента AddInstructions? Предлагаю самостоятельно добавить его, как домашнее задание :D
              Ну а если у вас не получиться, то я позже добавлю :jokingly:

              пс. возможно лучше состояние перенести из AddRecipeForm в AddIngredients? тут надо подумать как лучше :blush:
              Сообщение отредактировано: Cfon -
                Эх, ещё с год назад пытался вразумить на счёт ES6+, донести что прототипы мертвы... А сегодня ты уже на реакте фигачишь :-?

                Куда катится мир.
                  Цитата Serafim @
                  Эх, ещё с год назад пытался вразумить на счёт ES6+, донести что прототипы мертвы... А сегодня ты уже на реакте фигачишь :-?

                  Куда катится мир.

                  ну без прототипов знание JS не будет не полным, поэтому я все правильно изучал последовательно и планомерно, скоро перейду на Angular :D
                    Цитата Cfon @
                    Нет элемента AddInstructions? Предлагаю самостоятельно добавить его, как домашнее задание :D

                    Ответ на домашнее задание:
                    Вместо класса юзал функцию, в реакте такой стиль написания кода поощряется и если вы будете так писать вас возмут в Facebook :D
                    собственно instruction-form.js
                    ExpandedWrap disabled
                      import React from 'react';
                       
                      const InstructionForm = ({onNewInstruction=f=>f, onClear=f=>f}) => {
                        let _step;
                        const add = (e) => {
                          e.preventDefault();
                          onNewInstruction(_step.value);
                          clearInputs();
                        }
                        const clear = (e) => {
                          e.preventDefault();
                          onClear();
                        }
                        const clearInputs = () => {
                          _step.value = '';
                        }
                        return (
                          <div className="instruction-form">
                            <input className="form-control" ref={i => _step = i} type="text" placeholder="Step"/>
                            <div className="mt-2">
                              <button className="btn btn-secondary mr-2" onClick={add}>Add Instruction</button>
                              <button className="btn btn-secondary mr-2" onClick={clear}>Clear Instruction List</button>
                            </div>
                          </div>
                        );
                      };
                       
                      export default InstructionForm;


                    изменения в recipe-form.js:
                    ExpandedWrap disabled
                      import React, {Component} from 'react';
                      import IngredientList from './ingredients-list';
                      import IngredientForm from './ingredient-form';
                      import Instructions from './instructions';
                      import InstructionForm from './instruction-form';
                       
                      class RecipeForm extends Component {
                        static defaultProps = {
                          onNewRecipe() {}
                        }
                       
                        constructor() {
                          super();
                          this.state = {
                            ingredients: [],
                            instructions: [] //<--
                          };
                          this.submitRecipe = this.submitRecipe.bind(this);
                          this.addIngredient = this.addIngredient.bind(this);
                          this.clearIngredientList = this.clearIngredientList.bind(this);
                          
                          this.addInstruction = this.addInstruction.bind(this); //<--
                          this.clearInstructions = this.clearInstructions.bind(this); //<--
                        }
                       
                        submitRecipe(event) {
                          event.preventDefault();
                          const {_recipeName} = this.refs;
                          const {onNewRecipe} = this.props;
                          const {ingredients, instructions} = this.state;
                                               // ^^^
                          onNewRecipe(_recipeName.value, ingredients, instructions);
                                                                       // ^^^
                          this.clearIngredientList();
                          this.clearInstructions(); //<--
                          _recipeName.value = '';
                          _recipeName.focus();
                        }
                       
                        addIngredient(ingredient) {
                          const {ingredients} = this.state;
                          this.setState({
                            ingredients: [
                              ...ingredients,
                              ingredient
                            ]
                          });
                        }
                       
                        clearIngredientList() {
                          this.setState({
                            ingredients: []
                          });
                        }
                       
                        addInstruction(step) { //<--
                          const {instructions} = this.state;
                          this.setState({
                            instructions: [
                              ...instructions,
                              step
                            ]
                          });
                        }
                       
                        clearInstructions() { //<--
                          this.setState({
                            instructions: []
                          });
                        }
                       
                        render() {
                          const {ingredients, instructions} = this.state;
                          return (
                            <div>
                              <h3>Add Recipe</h3>
                              <form onSubmit={this.submitRecipe}>
                                <input className="form-control" ref="_recipeName" type="text" placeholder="Recipe name" required/>
                                <div className="card mt-2">
                                  <div className="card-body">
                                    {
                                      (ingredients.length !== 0)
                                        ? <IngredientList list={ingredients}/>
                                        : <p>No ingredients</p>
                                    }
                                    <IngredientForm onNewIngredient={this.addIngredient}
                                      onClear={this.clearIngredientList}/>
                                  </div>
                                </div>
                                {/* AND HERE */}
                                <div className="card mt-2">
                                  <div className="card-body">
                                    {
                                      (instructions.length !== 0)
                                        ? <Instructions steps={instructions}/>
                                        : <p>No instructions</p>
                                    }
                                    <InstructionForm onNewInstruction={this.addInstruction}
                                      onClear={this.clearInstructions}/>
                                  </div>
                                </div>
                                <button className="btn btn-primary mt-2">Add Recipe</button>
                              </form>
                            </div>
                          );
                        }
                      }
                       
                      export default RecipeForm;


                    изменения в app.js:
                    ExpandedWrap disabled
                      import React, {Component} from 'react';
                      import {render} from 'react-dom';
                      import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
                      import Menu from './components/menu';
                      import data from '../db/recipes';
                      import RecipeForm from './components/recipe-form';
                      import {v4} from 'uuid';
                       
                      class App extends Component {
                       
                        constructor(props) {
                          super(props);
                          this.state = {
                            recipes: props.data.recipes
                          };
                          this.doRate = this.doRate.bind(this);
                          this.addRecipe = this.addRecipe.bind(this);
                        }
                       
                        doRate(id, rating) {
                          const {recipes} = this.state;
                          this.setState({
                            recipes: recipes.map(recipe => {
                              return (id !== recipe.id)
                                ? recipe
                                : {
                                  ...recipe,
                                  rating
                                }
                            })
                          });
                        }
                       
                        addRecipe(name, ingredients, steps) {
                                                   // ^^^
                          const {recipes} = this.state;
                          const recipe = {
                            id: v4(),
                            name,
                            ingredients,
                            steps, //<--
                            rating: 0
                          };
                          this.setState({
                            recipes: [
                              ...recipes,
                              recipe
                            ]
                          });
                        }
                       
                        render() {
                          const {recipes} = this.state;
                          return (
                            <div className="container">
                              <Menu recipes={recipes} onRate={this.doRate}/>
                              <RecipeForm onNewRecipe={this.addRecipe}/>
                            </div>
                          );
                        }
                      }
                       
                      render(<App data={data}/>, document.getElementById('app'));


                    Прикреплённый файлПрикреплённый файлreact_in_action_v3_1.zip (9,43 Кбайт, скачиваний: 139)

                    Пока все! Продолжение следует...
                    Сообщение отредактировано: Cfon -
                      Так продолжаю изучать React и сегодня я разбираюсь c жизненным циклом обновления компоненты (updating component lifecycle)
                      а именно с методом componentWillReceiveProps, который как мне показалось является самым сложным в понимании его использования. В качестве примера решил взять уже избитый мною класс Person, но переложеный на React
                      Короче вот пример:
                      person.js
                      ExpandedWrap disabled
                        class Person extends Component {
                          constructor() {
                            super();
                            this.state = {
                              isYoung: false
                            };
                          }
                         
                          // А ВОТ И ОН!
                          componentWillReceiveProps(nextProps) {
                            this.setState({
                              isYoung: nextProps.age < 20
                            });
                          }
                         
                          render() {
                            const {name, isSelected, onClick} = this.props;
                            const {isYoung} = this.state;
                            return (
                              <li className={isSelected ? (isYoung ? 'young-selected' : 'selected') : ''} onClick={onClick}>
                                {name}
                              </li>
                            );
                          }
                        }

                      persons.js
                      ExpandedWrap disabled
                        const Persons = ({persons=[], onSelect=f=>f}) => (
                          <ul className="persons-list">
                            {persons.map((person, i) =>
                              <Person key={i} {...person} onClick={() => onSelect(i)}/>
                            )}
                          </ul>
                        );

                      person-form.js
                      ExpandedWrap disabled
                        const PersonForm = ({onSubmit=f=>f}) => {
                          let nameInput, ageInput;
                         
                          const submit = e => {
                            e.preventDefault();
                            onSubmit(nameInput.value, ageInput.value);
                            clearInputs();
                          };
                         
                          const clearInputs = () => {
                            nameInput.value = '';
                            ageInput.value = '';
                            nameInput.focus();
                          };
                         
                          return (
                            <form onSubmit={submit}>
                              <input type="text" ref={i => nameInput = i} placeholder="Name"/>
                              <input type="text" ref={i => ageInput = i} placeholder="Age"/>
                              <button>Submit</button>
                            </form>
                          );
                        };

                      app.js
                      ExpandedWrap disabled
                        class App extends Component {
                          constructor() {
                            super();
                            this.state = {
                              persons: []
                            };
                            this.addPerson = this.addPerson.bind(this);
                            this.selectPerson = this.selectPerson.bind(this);
                          }
                         
                          addPerson(name, age) {
                            const {persons} = this.state;
                            const person = {
                              name,
                              age,
                              isSelected: false
                            };
                            this.setState({
                              persons: [
                                ...persons,
                                person
                              ]
                            });
                          }
                         
                          selectPerson(index) {
                            const {persons} = this.state;
                            this.setState({
                              persons: persons.map((person, i) => {
                                return (index !== i)
                                  ? person
                                  : {
                                    ...person,
                                    isSelected: !person.isSelected
                                  }
                              })
                            });
                          }
                         
                          render() {
                            const {persons} = this.state;
                            return (
                              <div>
                                <PersonForm onSubmit={this.addPerson}/>
                                <Persons persons={persons} onSelect={this.selectPerson}/>
                              </div>
                            );
                          }
                        }
                         
                        render(
                          <App />,
                          document.getElementById('app')
                        );

                      Объяснение этого кода я отложу не потом, а пока просто изучите его, возможно что вы и сами поймете наконец как юзать componentWillReceiveProps :D
                      Если вкраце то его юзат как правило для обновления состояния дочернего компонента при изменении его свойств родительским компонентом :blink: :lool:
                      Сообщение отредактировано: Cfon -
                        Меня многие спрашивают а как перенести данные на сервер?! :blink:
                        Отвечаю например постаринке юзать AJAX и REST API :D
                        В качестве обертки AJAX можно юзать пакеты isomorphic-fetch или axios. Или тот же jQuery :D
                        утсанавливаем их командой yarn add isomorphic-fetch или yarn add axios
                        Хотя в последней версии хрома isomorphic-fetch уже поддержвается так что можно не устанавливать.
                        Также в примере показано использование метода жизненного цикла componentWillMount
                        Вот код изменения в коде сервера и клиента, в качестве БД взял NoSQL Bourne, при желании можно заменить на MongoDB:

                        server.js
                        ExpandedWrap disabled
                          import express from 'express';
                          import logger from 'morgan';
                          import Bourne from 'bourne';
                           
                          const app = express();
                          const recipes = new Bourne('db/recipes.json');
                           
                          app.use(logger('dev'));
                          app.use(express.static('public'));
                           
                          app.get('/', (req, res) => {
                            res.sendFile('index.html');
                          });
                           
                          // REST API
                          app.get('/recipes.json', (req, res) => {
                            recipes.find((err, results) => {
                              res.json(results);
                            });
                          });
                           
                          app.listen(3000, () => console.log(`Application running at 'http://localhost:3000'`));


                        app.js
                        ExpandedWrap disabled
                          import React, {Component} from 'react';
                          import {render} from 'react-dom';
                          import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
                          import Menu from './components/menu';
                          import RecipeForm from './components/recipe-form';
                          import {v4} from 'uuid';
                          import fetch from 'isomorphic-fetch';
                          // import axios from 'axios';
                           
                          class App extends Component {
                           
                            constructor(props) {
                              super(props);
                              this.state = {
                                recipes: []
                              };
                              this.doRate = this.doRate.bind(this);
                              this.addRecipe = this.addRecipe.bind(this);
                            }
                           
                            // Получаем данные с сервера
                            componentWillMount() {
                              fetch(`/recipes.json`)
                                .then(res => res.json())
                                .then(recipes => this.setState({recipes}))
                                .catch(err => console.error(err));    
                            }
                           
                            doRate(id, rating) {
                              const {recipes} = this.state;
                              this.setState({
                                recipes: recipes.map(recipe => {
                                  return (id !== recipe.id)
                                    ? recipe
                                    : {
                                      ...recipe,
                                      rating
                                    }
                                })
                              });
                            }
                           
                            addRecipe(name, ingredients, steps) {
                              const {recipes} = this.state;
                              const recipe = {
                                id: v4(),
                                name,
                                ingredients,
                                steps,
                                rating: 0
                              };
                              this.setState({
                                recipes: [
                                  ...recipes,
                                  recipe
                                ]
                              });
                            }
                           
                            render() {
                              const {recipes} = this.state;
                              return (
                                <div className="container">
                                  <Menu recipes={recipes} onRate={this.doRate}/>
                                  <RecipeForm onNewRecipe={this.addRecipe}/>
                                </div>
                              );
                            }
                          }
                           
                          render(<App />, document.getElementById('app'));


                        Прикреплённый файлПрикреплённый файлreact_in_action_v3_2.zip (9,63 Кбайт, скачиваний: 141)
                        Сообщение отредактировано: Cfon -
                          Цитата Cfon @
                          Отвечаю например постаринке юзать AJAX и REST API


                          REST API REST IN PEACE :whistle:
                            Цитата Serafim @
                            REST API REST IN PEACE :whistle:

                            Hail to the GraphQL? :D
                              Цитата Астарот @
                              Hail to the GraphQL?

                              О, а вот ты шаришь в трендах этого грешного мира, а притворялся джавистом. :D
                                Цитата Serafim @
                                О, а вот ты шаришь в трендах этого грешного мира, а притворялся джавистом. :D

                                Так те же яйца, только в профиль :D
                                  Цитата Астарот @
                                  Так те же яйца, только в профиль

                                  Не совсем. Это как использовать ФС вместо БД. Причём эта аналогия, если подумать и с переносом с сервера на клиент-серверное взаимодействие - похожа на реальность.
                                    Цитата Serafim @
                                    Не совсем

                                    В том плане, что все едино, что для REST нужно читать доку, что для GraphQL - совсем :) Как не было реальной унификации, так ее и нет.
                                      Цитата Астарот @
                                      Как не было реальной унификации, так ее и нет.

                                      А что есть "реальная" унификация? :huh:
                                        Цитата Serafim @
                                        А что есть "реальная" унификация? :huh:

                                        Ничто не есть, в том и дело.
                                          Цитата Serafim @
                                          REST API REST IN PEACE :whistle:

                                          ну пока ешо не умер, много где юзается :D
                                          Добавлено
                                          Цитата Астарот @
                                          Hail to the GraphQL? :D

                                          ну или Falcor :D
                                          Сообщение отредактировано: Cfon -
                                            Кстати кто-нибудь юзал или юзит GraphQL + React?
                                              Цитата Cfon @
                                              Кстати кто-нибудь юзал или юзит GraphQL + React?

                                              Да, у нас на проде в 3х проектах)
                                                Цитата Serafim @
                                                Цитата Cfon @
                                                Кстати кто-нибудь юзал или юзит GraphQL + React?

                                                Да, у нас на проде в 3х проектах)

                                                Ок. Я тоже решил не тупить :D
                                                Переделал пример выше с использованием GraphQL :blush:
                                                Зацените что у меня получилось

                                                app.js
                                                ExpandedWrap disabled
                                                  import React, {Component} from 'react';
                                                  import {render} from 'react-dom';
                                                  import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
                                                  import Menu from './components/menu';
                                                  import RecipeForm from './components/recipe-form';
                                                  import {v4} from 'uuid';
                                                  // import fetch from 'isomorphic-fetch';
                                                   
                                                  class App extends Component {
                                                    state = {
                                                      recipes: []
                                                    }
                                                   
                                                    constructor() {
                                                      super();
                                                      this.doRate = this.doRate.bind(this);
                                                      this.addRecipe = this.addRecipe.bind(this);
                                                    }
                                                   
                                                    componentWillMount() {
                                                      // GraphQL query
                                                      fetch(`/graphql?query={
                                                        recipes {
                                                          id,
                                                          name,
                                                          ingredients {
                                                            name,
                                                            amount,
                                                            measurement
                                                          },
                                                          steps,
                                                          rating
                                                        }
                                                      }`)
                                                      .then(res => res.json())
                                                      .then(json => {
                                                        this.setState(json.data)
                                                      })
                                                      .catch(err => console.error(err));
                                                    }
                                                   
                                                  ........
                                                   
                                                    render() {
                                                      const {recipes} = this.state;
                                                      return (
                                                        <div className="container">
                                                          <Menu recipes={recipes} onRate={this.doRate}/>
                                                          <RecipeForm onNewRecipe={this.addRecipe}/>
                                                        </div>
                                                      );
                                                    }
                                                  }
                                                   
                                                  render(
                                                    <App/>,
                                                    document.getElementById('app')
                                                  );


                                                schema/main.js
                                                ExpandedWrap disabled
                                                  import {
                                                    GraphQLSchema,
                                                    GraphQLObjectType,
                                                    GraphQLString,
                                                    GraphQLInt,
                                                    GraphQLFloat,
                                                    GraphQLList
                                                  } from 'graphql';
                                                   
                                                   
                                                  const IngredientType = new GraphQLObjectType({
                                                    name: 'Ingredient',
                                                    fields: {
                                                      name: {
                                                        type: GraphQLString
                                                      },
                                                      amount: {
                                                        type: GraphQLFloat
                                                      },
                                                      measurement: {
                                                        type: GraphQLString
                                                      }
                                                    }
                                                  });
                                                   
                                                  const RecipeType = new GraphQLObjectType({
                                                    name: 'Recipe',
                                                    fields: {
                                                      id: {
                                                        type: GraphQLInt
                                                      },
                                                      name: {
                                                        type: GraphQLString
                                                      },
                                                      ingredients: {
                                                        type: new GraphQLList(IngredientType)
                                                      },
                                                      steps: {
                                                        type: new GraphQLList(GraphQLString)
                                                      },
                                                      rating: {
                                                        type: GraphQLInt
                                                      }
                                                    }
                                                  });
                                                   
                                                  const QueryType = new GraphQLObjectType({
                                                    name: 'Query',
                                                    fields: {
                                                      recipes: {
                                                        type: new GraphQLList(RecipeType),
                                                        resolve: (arg1, arg2, {recipes}) => {
                                                          let res = [];
                                                          recipes.find((err, results) => {
                                                            res = [...results];
                                                          });
                                                          return res;
                                                        }
                                                      }
                                                    }
                                                  });
                                                   
                                                  export const schema = new GraphQLSchema({
                                                    query: QueryType
                                                  });


                                                server.js
                                                ExpandedWrap disabled
                                                  import express from 'express';
                                                  import logger from 'morgan';
                                                  import Bourne from 'bourne';
                                                  import graphqlHTTP from 'express-graphql';
                                                  import {schema} from '../schema/main';
                                                   
                                                  const app = express();
                                                  const recipes = new Bourne('db/recipes.json');
                                                   
                                                  app.use(logger('dev'));
                                                  app.use(express.static('public'));
                                                  app.use('/graphql', graphqlHTTP({
                                                    schema,
                                                    context: {recipes},
                                                    graphql: true
                                                  }));
                                                   
                                                  app.listen(3000, () => console.log(`Application running at 'http://localhost:3000'`));


                                                пс. этот пример не юзает фреймворк Relay, его пока не копал времени не было, сделал через супермодный fetch :D

                                                Прикреплённый файлПрикреплённый файлreact_in_action_v3_3.zip (10,28 Кбайт, скачиваний: 116)
                                                Сообщение отредактировано: Cfon -
                                                  Итак продолжаю расти :blush:
                                                  Вот новая версия нашего учебного приложения под названием Recipes, но на этот раз я его переложил на что вы думаете?
                                                  На Relay! да да не удивляйтесь! :D
                                                  С Реле пришлось повозиться, сильный зверек, но таки я его одолел! Ну или почти одолел :lool:
                                                  Короче вот сорцы:

                                                  app.js
                                                  ExpandedWrap disabled
                                                    import React, {Component} from 'react';
                                                    import {render} from 'react-dom';
                                                    import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
                                                    import Menu from './components/menu';
                                                    import RecipeForm from './components/recipe-form';
                                                    import {v4} from 'uuid';
                                                    import {QueryRenderer, graphql} from 'react-relay';
                                                    import environment from './environment';
                                                     
                                                    class App extends Component {
                                                      // state = {
                                                      //   recipes: []
                                                      // }
                                                     
                                                      constructor() {
                                                        super();
                                                        this.doRate = this.doRate.bind(this);
                                                        this.addRecipe = this.addRecipe.bind(this);
                                                      }
                                                     
                                                      ........
                                                     
                                                      render() {
                                                        return (
                                                          // ТУТ МАГИЯ!
                                                          <QueryRenderer
                                                            environment={environment}
                                                            query={graphql`
                                                              query appQuery {
                                                                recipes {
                                                                  id
                                                                  name
                                                                  ingredients {
                                                                    name
                                                                    amount
                                                                    measurement
                                                                  }
                                                                  steps
                                                                  rating
                                                                }
                                                              }
                                                            `}
                                                            render={({props}) => {
                                                              return (
                                                                <div className="container">
                                                                  <Menu recipes={props ? props.recipes : []} onRate={this.doRate}/>
                                                                  <RecipeForm onNewRecipe={this.addRecipe}/>
                                                                </div>
                                                              );
                                                            }}
                                                          />
                                                        );
                                                      }
                                                    }
                                                     
                                                    render(
                                                      <App/>,
                                                      document.getElementById('app')
                                                    );


                                                  environment.js
                                                  ExpandedWrap disabled
                                                    import {
                                                      Environment,
                                                      Network,
                                                      RecordSource,
                                                      Store
                                                    } from 'relay-runtime';
                                                     
                                                    function fetchQuery(operation, variables) {
                                                      return fetch('/graphql', {
                                                        method: 'POST',
                                                        headers: {
                                                          'Content-Type': 'application/json',
                                                        },
                                                        body: JSON.stringify({
                                                          query: operation.text,
                                                          variables,
                                                        }),
                                                      }).then(response => {
                                                        return response.json();
                                                      });
                                                    }
                                                     
                                                    export default new Environment({
                                                      network: Network.create(fetchQuery),
                                                      store: new Store(new RecordSource()),
                                                    });


                                                  schema.graphql
                                                  ExpandedWrap disabled
                                                    type Ingredient {
                                                      name: String
                                                      amount: Float
                                                      measurement: String
                                                    }
                                                     
                                                    type Query {
                                                      recipes: [Recipe]
                                                    }
                                                     
                                                    type Recipe {
                                                      id: ID!
                                                      name: String
                                                      ingredients: [Ingredient]
                                                      steps: [String]
                                                      rating: Int
                                                    }


                                                  Прикреплённый файлПрикреплённый файлreact_in_action_v4_1.zip (10,82 Кбайт, скачиваний: 108)

                                                  Для Реле требуются несколько пакетов которые указаны в package.json. Технология создания Реакт-Реле приложения предусматрисает различние генераторы для создания заготовок, но я их не юзал, по простой причине чтобы лучше разобраться в них. Но одну фиговину а таки заюзал называется она get-graphql-schema при создания schema.graphql.

                                                  Сразу огаварюсь что приложение не закончено ибо необходимо еще реализовать мутации :blink:
                                                  Какие мутации? таки! поскольку к приложению прикручен Relay необходимость в state отпала :D
                                                  ибо все изменения будут сохраняться непосредственно в БД через эти буть они не ладны мутиции, которые я еще кстати не изучил :D
                                                  Спокойно! спокойно, я изучаю и скоро выложу версию с мутациями :crazy:
                                                  А пока чисто пример загрузки данных из БД в наше приложение и отображение на экран, естественно теперь интеракритновть пропала поскольку state не задействован :D

                                                  пс. чуть не забыл добавилась еще одна команда yarn relay, она указана в package.json, с ее помощью мы запускаем Реле компилятор (relay-compiler), который создаст папку __generated__ в которой будет в нашем случае один файл с именем appQuery.graphql.js. Нами он не юзается но Реле-компилятор в какой то момент компиляции его цепляет, так что если его нет то будет ошибка компиляции. Вооот! Если ни че не поняли забейте, просто запустите yarn relay, а затем yarn build и будет вам счастье! :whistle:

                                                  Кстати заметили в файле environment.js функцию fetchQuery, а в ней вызов fetch? Да это та самая fetch, что я юзал в предыдущем примере, и в Реле без этой супермодной фунси далеко не уйдешь :D

                                                  Есть разные варианты работы с GraphQL например через graphcool + Apollo как я почитал он более простой, но мы не ищем простых путей так ведь? :D
                                                  А кроме того Реле оно детище Фейсбук, а мы ведь хотим там работать! Ну или быть около Фейсбук :jokingly:
                                                  Сообщение отредактировано: Cfon -
                                                    1) Relay - это полное дерьмо. Выкинь его на помойку.
                                                    2) А теперь ставь Apollo, это на порядок лучше Realy.


                                                    Можешь ещё в сторону Graphcool или Prisma посмотреть в качестве сервера. А ещё лучше мой фреймворк, но это уже другая история :D

                                                    Добавлено
                                                    Цитата Cfon @
                                                    Есть разные варианты работы с GraphQL например через graphcool + Apollo как я почитал он более простой, но мы не ищем простых путей так ведь?

                                                    Блин, я не дочитал до этой строки. Увидел ключевое слово "Relay" и сразу пошёл брюзжать :lol: Короче, правильно люди говорят. Relay - шлак.
                                                    Сообщение отредактировано: Serafim -
                                                      Цитата Serafim @
                                                      1) Relay - это полное дерьмо. Выкинь его на помойку.

                                                      Возможно речь о Relay Classic?
                                                      А Relay Modern вроде нормуль :)

                                                      Цитата Serafim @
                                                      1) Можешь ещё в сторону Graphcool или Prisma посмотреть в качестве сервера. А ещё лучше мой фреймворк, но это уже другая история :D

                                                      в учебных целях имхо лучше все ручками :D
                                                      поэтому с помощью express-graphql создаю свой GraphQL-сервер.
                                                      потом можно Graphcool или Prisma поюзать.
                                                      Сообщение отредактировано: Cfon -
                                                        Цитата Cfon @
                                                        Возможно речь о Relay Classic?
                                                        А Relay Modern вроде нормуль


                                                        Не возможно, а точно) Modern - возможно, не смотрел. Там ведь нет этих грёбанных edges и прочего?

                                                        Добавлено
                                                        Цитата Cfon @
                                                        потом можно Graphcool или Prisma поюзать.

                                                        Юзай Railt :lol:

                                                        Правда я его ещё не зарелизил. Но вот как начнёшь смотреть на призму, тогда может и зарелижу)))
                                                          Цитата Serafim @
                                                          Не возможно, а точно) Modern - возможно, не смотрел. Там ведь нет этих грёбанных edges и прочего?

                                                          есть, а что они мешают? :blink:
                                                          это же вроде относится к пагинации (pagination) GraphQL, а не Relay?
                                                          Пагинация в Реле реализуется через соединения (Connections) и если пагинация не нужна то не юзим соединения и edges не будет.
                                                          Или я не правильно тебя понял? :huh:

                                                          Цитата Serafim @

                                                          Юзай Railt :lol:

                                                          Правда я его ещё не зарелизил. Но вот как начнёшь смотреть на призму, тогда может и зарелижу)))

                                                          Ок почекаю если че :D
                                                          хотя я и Графкуль с Призмой врядли буду ща юзать
                                                          Сообщение отредактировано: Cfon -
                                                            Цитата Cfon @
                                                            есть, а что они мешают?
                                                            это же вроде относится к пагинации (pagination) GraphQL, а не Relay?

                                                            Это относится непосредственно к этому Relay. Потому что эти edges и connections суперкостыль этого релея для пагинации, которую можно было сделать по-нормальному, вместо того дерьма, что они предлагают.
                                                            Сообщение отредактировано: Serafim -
                                                              Цитата Serafim @
                                                              Цитата Cfon @
                                                              есть, а что они мешают?
                                                              это же вроде относится к пагинации (pagination) GraphQL, а не Relay?

                                                              Это относится непосредственно к этому Relay. Потому что эти edges и connections суперкостыль этого релея для пагинации, которую можно было сделать по-нормальному, вместо того дерьма, что они предлагают.

                                                              http://graphql.github.io/learn/pagination/
                                                              а это статья о чем? разве она про Реле? :huh:
                                                              Сообщение отредактировано: Cfon -
                                                                Цитата Cfon @
                                                                а это статья о чем? разве она про Реле?

                                                                Да, она про Relay реализацию.
                                                                  А почему это костыль то? Написано же best practice :)

                                                                  Цитата

                                                                  Pagination
                                                                  The GraphQL type system allows for some fields to return lists of values, but leaves the pagination of longer lists of values up to the API designer. There are a wide range of possible API designs for pagination, each of which has pros and cons.

                                                                  Typically fields that could return long lists accept arguments "first" and "after" to allow for specifying a specific region of a list, where "after" is a unique identifier of each of the values in the list.
                                                                  Ultimately designing APIs with feature-rich pagination led to a best practice pattern called "Connections". Some client tools for GraphQL, such as Relay, know about the Connections pattern and can automatically provide automatic support for client-side pagination when a GraphQL API employs this pattern.
                                                                    Немного изменил пример и заюзал Relay connection вместо массива :D
                                                                    Кроме того переписал код GraphQL-сервера с использованием GraphQL схемы (schema.graphql), для этого заюзал пакеты graphql-import и graphql-relay. Также пришлось немного изменить код app.js и menu.js в связи с использование вместо массива реле-соединения, которое юзает edges и node :D.
                                                                    Пагинацию не стал реализовывать, возможно потом, но заюзал один из параметров соединения а именно first для указания количества записей, которых в исходной базе тока две :D
                                                                    Почекал на получение 1 или 2 записей все пучком работает! :D

                                                                    schema.graphql:
                                                                    ExpandedWrap disabled
                                                                      type Ingredient {
                                                                        name: String
                                                                        amount: Float
                                                                        measurement: String
                                                                      }
                                                                       
                                                                      type Query {
                                                                        # recipes: [Recipe]!
                                                                        recipes(
                                                                          first: Int,
                                                                          after: String,
                                                                          last: Int,
                                                                          before: String
                                                                        ): RecipeConnection
                                                                      }
                                                                       
                                                                      type Recipe {
                                                                        id: ID!
                                                                        name: String!
                                                                        ingredients: [Ingredient]
                                                                        steps: [String]
                                                                        rating: Int!
                                                                      }
                                                                       
                                                                      # A connection to a list of items.
                                                                      type RecipeConnection {
                                                                        # A list of edges.
                                                                        edges: [RecipeEdge]
                                                                        # Information to aid in pagination.
                                                                        pageInfo: PageInfo
                                                                      }
                                                                       
                                                                      # An edge in a connection.
                                                                      type RecipeEdge {
                                                                        # The item at the end of the edge.
                                                                        node: Recipe
                                                                        # A cursor for use in pagination.
                                                                        cursor: String
                                                                      }
                                                                       
                                                                      # Information about pagination in a connection.
                                                                      type PageInfo {
                                                                        # When paginating forwards, are there more items?
                                                                        hasNextPage: Boolean
                                                                        # When paginating backwards, are there more items?
                                                                        hasPreviousPage: Boolean
                                                                        # When paginating backwards, the cursor to continue.
                                                                        startCursor: String
                                                                        # When paginating forwards, the cursor to continue.
                                                                        endCursor: String
                                                                      }


                                                                    server.js
                                                                    ExpandedWrap disabled
                                                                      import express from 'express';
                                                                      import logger from 'morgan';
                                                                      import Bourne from 'bourne';
                                                                      import path from 'path';
                                                                      import graphqlHTTP from 'express-graphql';
                                                                      import {buildSchema} from 'graphql';
                                                                      import {importSchema} from 'graphql-import'; //<-- 1
                                                                      import {connectionFromArray} from 'graphql-relay'; //<-- 2
                                                                       
                                                                      const app = express();
                                                                       
                                                                      app.use(logger('dev'));
                                                                      app.use(express.static('public'));
                                                                       
                                                                      const recipes = new Bourne('db/recipes.json');
                                                                       
                                                                      // Construct a schema, using GraphQL schema language
                                                                      const schemaDef = importSchema(path.resolve('schema.graphql')); //<-- 4
                                                                      const schema = buildSchema(schemaDef); //<-- 5
                                                                       
                                                                      // The root provides a resolver function for each API endpoint
                                                                      const root = { //<-- 6
                                                                        recipes(connArgs) {
                                                                          let conn = {};
                                                                          recipes.find((err, results) => {
                                                                            conn = connectionFromArray(results, connArgs);
                                                                          });
                                                                          return conn;
                                                                        }
                                                                      };
                                                                       
                                                                      app.use('/graphql', graphqlHTTP({
                                                                        schema,
                                                                        rootValue: root, //<-- 7
                                                                        graphiql: true
                                                                      }));
                                                                       
                                                                      app.listen(3000, () => console.log(`Application running at 'http://localhost:3000'`));


                                                                    app.js:
                                                                    ExpandedWrap disabled
                                                                      import React, {Component} from 'react';
                                                                      import {render} from 'react-dom';
                                                                      import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
                                                                      import Menu from './components/menu';
                                                                      import RecipeForm from './components/recipe-form';
                                                                      import {QueryRenderer, graphql} from 'react-relay';
                                                                      import environment from './environment';
                                                                       
                                                                      const ITEMS_PER_PAGE = 10;
                                                                       
                                                                      class App extends Component {
                                                                        // state = {
                                                                        //   recipes: []
                                                                        // }
                                                                       
                                                                        constructor() {
                                                                          super();
                                                                          this.doRate = this.doRate.bind(this);
                                                                          this.addRecipe = this.addRecipe.bind(this);
                                                                        }
                                                                       
                                                                        doRate(id, rating) {
                                                                          // TODO:
                                                                        }
                                                                       
                                                                        addRecipe(name, ingredients, steps) {
                                                                          // TODO:
                                                                        }
                                                                       
                                                                        // ТУТ МАГИЯ!
                                                                        render() {
                                                                          return (
                                                                            <QueryRenderer
                                                                              environment={environment}
                                                                              query={graphql`
                                                                                query appQuery($count: Int!) {
                                                                                  recipes(first: $count) {
                                                                                    edges {
                                                                                      node {
                                                                                        id
                                                                                        name
                                                                                        ingredients {
                                                                                          name
                                                                                          amount
                                                                                          measurement
                                                                                        }
                                                                                        steps
                                                                                        rating
                                                                                      }
                                                                                    }
                                                                                  }
                                                                                }
                                                                              `}
                                                                              variables={{
                                                                                count: ITEMS_PER_PAGE
                                                                              }}
                                                                              render={({props}) => {
                                                                                return (
                                                                                  <div className="container">
                                                                                    <Menu recipes={props ? props.recipes.edges : []} onRate={this.doRate}/>
                                                                                    <RecipeForm onNewRecipe={this.addRecipe}/>
                                                                                  </div>
                                                                                );
                                                                              }}
                                                                            />
                                                                          );
                                                                        }
                                                                      }
                                                                       
                                                                      render(
                                                                        <App/>,
                                                                        document.getElementById('app')
                                                                      );


                                                                    menu.js:
                                                                    ExpandedWrap disabled
                                                                      import React from 'react';
                                                                      import Recipe from './recipe';
                                                                      import Summary from './summary';
                                                                       
                                                                      const Menu = ({recipes=[], onRate=()=>{}}) => {
                                                                        return (
                                                                          <article>
                                                                            <header>
                                                                              <h1 className="text-white bg-dark">Delicious Recipes</h1>
                                                                            </header>
                                                                            <div className="recipes">
                                                                              {recipes.map(({node}, i) => {
                                                                                          // ^^^
                                                                                return (
                                                                                  <div key={i}>
                                                                                    <Recipe {...node} onRate={rating => onRate(node.id, rating)}/>
                                                                                    <Summary ingredientsCount={node.ingredients.length}
                                                                                      stepsCount={node.steps.length}/>
                                                                                    <hr/>
                                                                                  </div>
                                                                                );
                                                                              })}
                                                                            </div>
                                                                          </article>
                                                                        );
                                                                      }
                                                                       
                                                                      export default Menu;


                                                                    Прикреплённый файлПрикреплённый файлreact_in_action_v4_2.zip (10,81 Кбайт, скачиваний: 122)
                                                                    Сообщение отредактировано: Cfon -
                                                                      Давайте немного отойдем от Реле и вернемся к варианту программы, где юзалось состояние (state), в котором сохранялись данные (см. пост #11)
                                                                      Если вы его внимательно изучали, то вероятно заметили как компоненты передают свои данные головному компоненту App, они передают их через параметры колбэков. Ничего в этом плохого нет, но вот в случае сложной иерархии компонентов, легко потерять нить вызовов этих колбэков, как например в случае вызовы doRate! :wacko:
                                                                      Если вкраце то там doRate передается по след цепочке Menu => Recipe => StarRating! Причем сама doRate никакого отношения не имеет ни к Menu ни к Recipe, его цель StartRating, зачем тогда передавать по цепочке его? Мы бы его и не передавали если бы состояние не принадлежало App, но поскольку нам нужно сохранять данные о рейтинге в состоянии App, то поэтому мы вынуждены передавать колбэки по цепочке вниз, с тем чтобы потом вернуть данные в состояние App :blink: :wacko: :D
                                                                      Короче к чему я это? А к тому что с переносом данных на сервер и подключением Реле, мы естественным путем пришли к другому решению, которое заключается в том что теперь нам не надо передавать данные по цепочке колбэков, мы можем сразу обратиться к данным через запросы к БД. В нашем случае через Реле + Графкуль :D
                                                                      Смотрите изменения в нашей программе (см. комментарии в коде), пока тока без реализации самих методов

                                                                      ExpandedWrap disabled
                                                                        class App extends Component {
                                                                          // выкидываем нафик все это
                                                                          //constructor() {
                                                                          //  super();
                                                                          //  this.doRate = this.doRate.bind(this);
                                                                          //  this.addRecipe = this.addRecipe.bind(this);
                                                                          //}
                                                                         
                                                                          //doRate(id, rating) {
                                                                          //......
                                                                          //}
                                                                         
                                                                          //addRecipe(name, ingredients, steps) {
                                                                          //.......
                                                                          //}
                                                                         
                                                                          // остается тока рендер, кстати обычно я юзю функции вместо классов, если не надо работать с состоянием
                                                                          // так что можно заменить класс App на функцию
                                                                          render() {
                                                                            return (
                                                                              <QueryRenderer
                                                                                environment={environment}
                                                                                query={graphql`
                                                                                  query appQuery($count: Int!) {
                                                                                    recipes(first: $count) {
                                                                                      edges {
                                                                                        node {
                                                                                          id
                                                                                          name
                                                                                          ingredients {
                                                                                            name
                                                                                            amount
                                                                                            measurement
                                                                                          }
                                                                                          steps
                                                                                          rating
                                                                                        }
                                                                                      }
                                                                                    }
                                                                                  }
                                                                                `}
                                                                                variables={{
                                                                                  count: ITEMS_PER_PAGE
                                                                                }}
                                                                                render={({props}) => {
                                                                                  return (
                                                                                    <div className="container">
                                                                                      <Menu recipes={props ? props.recipes.edges : []}    />
                                                                                                                                   // ^^^ удалили onRate
                                                                                      <RecipeForm    />
                                                                                               // ^^^ удалили onNewRecipe
                                                                                    </div>
                                                                                  );
                                                                                }}
                                                                              />
                                                                            );
                                                                          }
                                                                        }
                                                                         
                                                                        const Menu = ({recipes=[]) => {
                                                                          return (
                                                                            <article>
                                                                              <header>
                                                                                <h1 className="text-white bg-dark">Delicious Recipes</h1>
                                                                              </header>
                                                                              <div className="recipes">
                                                                                {recipes.map(({node}, i) => {
                                                                                  return (
                                                                                    <div key={i}>
                                                                                      <Recipe {...node} />
                                                                                                    // ^^^ удалили onRate
                                                                                      <Summary ingredientsCount={node.ingredients.length}
                                                                                        stepsCount={node.steps.length} />
                                                                                      <hr/>
                                                                                    </div>
                                                                                  );
                                                                                })}
                                                                              </div>
                                                                            </article>
                                                                          );
                                                                        }
                                                                         
                                                                        const Recipe = ({name, ingredients, steps, rating    }) => {
                                                                                                                       // ^^^ удалили OnRate
                                                                          // собственно тут все и происходит теперь не надо никуда ничего передавать
                                                                          const onRate = rating => {
                                                                            // TODO: call relay mutation here
                                                                          }
                                                                          return (
                                                                            <section id={name.toLowerCase().replace(/\s/g, '-')}>
                                                                              <h2>{name}</h2>
                                                                              <IngredientsList list={ingredients} />
                                                                              <Instructions title="Cooking Instructions" steps={steps} />
                                                                              <StarRating starsSelected={rating} onRate={onRate}/>
                                                                            </section>
                                                                          )
                                                                        }

                                                                      Тоже относится и к методу addRecipe, теперь он расположиться в компоненте RecipeForm и оттуда будет обращаться к мутациям Реле :victory:
                                                                      В итоге наши связи упрощаются. Конечно наше приложение не самое сложное, но даже оно я думаю вызвало головную боль, когда вы изучали код вызовов doRate и addRecipe :wall: особенно doRate :D

                                                                      пс. кстати это не заслуга Реле само по себе, это заслуга выноса состояния в отдельный объект,
                                                                      существуют много подобных решений, например Redux. С ним мы тоже поработаем когда закончим с Relay! :D
                                                                      Сообщение отредактировано: Cfon -
                                                                        Очередной релиз :D
                                                                        Реализовал мутацию! Пока одну :D
                                                                        Мутация меняет рейтинг рецепта, теперь звездочки щелкают :D
                                                                        Поскольку теперь состояние находится не в App, удалил из свойств компонентов (не всех) колбэки, те что описал в предыдущем посте.
                                                                        По просьбе читателей заменил поля в запросах на реле-фрагменты (см. код Recipe, Ingredient)

                                                                        schema.graphql:
                                                                        ExpandedWrap disabled
                                                                          .........
                                                                           
                                                                          type Mutation {
                                                                            changeRecipeRate(input: ChangeRecipeRateInput!): Recipe
                                                                          }
                                                                           
                                                                          input ChangeRecipeRateInput {
                                                                            rating: Int!
                                                                            id: ID!
                                                                          }


                                                                        server.js:
                                                                        ExpandedWrap disabled
                                                                          ..........
                                                                           
                                                                          // The root provides a resolver function for each API endpoint
                                                                          const root = {
                                                                           
                                                                            .........
                                                                           
                                                                            // Mutations
                                                                            changeRecipeRate: ({input}) => {
                                                                              let recipe = {};
                                                                              recipes.findOne({
                                                                                id: input.id
                                                                              }, (err, result) => {
                                                                                recipe = {
                                                                                  ...result,
                                                                                  rating: input.rating
                                                                                };
                                                                                recipes.update({id: recipe.id}, recipe);
                                                                              });
                                                                              return recipe;
                                                                            }
                                                                          };
                                                                           
                                                                          ............


                                                                        app.js:
                                                                        ExpandedWrap disabled
                                                                          const ITEMS_PER_PAGE = 10;
                                                                           
                                                                          const App = () => (
                                                                            <QueryRenderer
                                                                              environment={environment}
                                                                              query={graphql`
                                                                                query app_Query($count: Int!) {
                                                                                  recipes(first: $count) {
                                                                                    edges {
                                                                                      node {
                                                                                        ...recipe_recipe
                                                                                      }
                                                                                    }
                                                                                  }
                                                                                }
                                                                              `}
                                                                              variables={{
                                                                                count: ITEMS_PER_PAGE
                                                                              }}
                                                                              render={({props}) => {
                                                                                return (
                                                                                  <div className="container">
                                                                                    <Menu recipes={props ? props.recipes.edges : []} />
                                                                                    <RecipeForm />
                                                                                  </div>
                                                                                );
                                                                              }}
                                                                            />
                                                                          );


                                                                        recipe.js:
                                                                        ExpandedWrap disabled
                                                                          const Recipe = ({recipe}) => {
                                                                            // ВЫЗЫВАЕМ МУТАЦИЮ!
                                                                            const onRate = rating => {
                                                                              changeRecipeRate(environment, rating, recipe.id);
                                                                            }
                                                                            const {name, ingredients, steps, rating} = recipe;
                                                                            return (
                                                                              <section id={recipe.name.toLowerCase().replace(/\s/g, '-')}>
                                                                                <h2>{recipe.name}</h2>
                                                                                <IngredientsList list={ingredients}/>
                                                                                <Instructions title="Cooking Instructions" steps={steps}/>
                                                                                <StarRating starsSelected={rating} onRate={onRate}/>
                                                                                <Summary ingredientsCount={ingredients.length}
                                                                                  stepsCount={steps.length}/>
                                                                              </section>
                                                                            )
                                                                          }
                                                                           
                                                                          // РЕЛЕ-ФРАГМЕНТ!
                                                                          export default createFragmentContainer(
                                                                            Recipe,
                                                                            graphql`
                                                                              fragment recipe_recipe on Recipe {
                                                                                id
                                                                                name
                                                                                ingredients {
                                                                                  ...ingredient_item
                                                                                }
                                                                                steps
                                                                                rating
                                                                              }
                                                                            `
                                                                          );


                                                                        change-recipe-rate.js:
                                                                        ExpandedWrap disabled
                                                                          import {commitMutation, graphql} from 'react-relay';
                                                                           
                                                                          export default (environment, rating, id) => {
                                                                            const variables = {
                                                                              input: {
                                                                                id,
                                                                                rating,
                                                                              }
                                                                            };
                                                                            return commitMutation(
                                                                              environment,
                                                                              {
                                                                                mutation: graphql`
                                                                                  mutation changeRecipeRate_Mutation($input: ChangeRecipeRateInput!) {
                                                                                    changeRecipeRate(input: $input) {
                                                                                      rating
                                                                                    }
                                                                                  }
                                                                                `,
                                                                                variables,
                                                                                onError: err => console.error(err)
                                                                              }
                                                                            )
                                                                          }


                                                                        Что тут интересного? Интересное тут то что когда мы щелкаем по звездочкам рейтинга мы видим их реакцию на клики, т.е. интерфейс сам перерисовывается! Вы спросите как это происходит без React this.setState? Да хз :D
                                                                        Знаю то что все происходит в хранилище (Store) реле, но глубоко не капал. Думаю что где там делается вызов this.setState ну или вызов ReactDOM.render.

                                                                        пс. на самом деле с фрагментами и мутациями я долго мудохался :wall:
                                                                        там не все так ясно, как может показаться, поскольку многое скрывает от нас реле-компилятор. Лично я не сразу понял например как формировать имя фрагмента, я долго менял его имя и ловил ошибки то реле-компилятора, то рантайма.
                                                                        Также с мутациями, как реле обновляет интерфейс мне не ясно, предстоит еще разобраться с updater и optimisticUpdater для кастомной настройки обновления хранилища.

                                                                        Прикреплённый файлПрикреплённый файлreact_in_action_v4_4.zip (11,55 Кбайт, скачиваний: 114)
                                                                        Сообщение отредактировано: Cfon -
                                                                          Все разобрался с updater и optimisticUpdater, первая вызывается когда приходит ответ с сервера, вторая - до прихода ответа :D
                                                                          Я все думал от чего зависит автоматическое обновление реакт-компонентов, оказывается все просто, ну или почти просто :D
                                                                          дело в том что чтобы Реле вызвало автоматический апдейт надо чтобы мутационый запрос возвращал глобальный ID, вместе с данными, в моем случае как раз я и возвращал его в объекте Recipe при мутации changeRecipeRate. Ну а поскольку есть глобал ID, то Реле-хранилище имеет возможность обнаружить место куда вносить локальные изменения!
                                                                          Я решил проверить свою гениальную идею! :blush: :lool:
                                                                          Для этого немного изменил код сервера и схемы, возвращая не весь объект Recipe, а часть вернее тока поле rating, главное не возвращать глобал АЙДИ! :whistle:

                                                                          server.js
                                                                          ExpandedWrap disabled
                                                                            ........
                                                                             
                                                                            const root = {
                                                                              .......
                                                                             
                                                                              // Mutations
                                                                              changeRecipeRate: ({input}) => {
                                                                                let recipe = {};
                                                                                recipes.findOne({
                                                                                  id: input.id
                                                                                }, (err, result) => {
                                                                                  recipe = {
                                                                                    ...result,
                                                                                    rating: input.rating
                                                                                  };
                                                                                  recipes.update({id: recipe.id}, recipe);
                                                                                });
                                                                             
                                                                                // 1
                                                                                // return recipe;
                                                                                return {
                                                                                  // NO ID!  
                                                                                  rating: recipe.rating
                                                                                };
                                                                              }
                                                                            };


                                                                          schema.graphql:
                                                                          ExpandedWrap disabled
                                                                            type Mutation {
                                                                              # 2
                                                                              changeRecipeRate(input: ChangeRecipeRateInput!): ChangeRecipeRatePayload
                                                                              # changeRecipeRate(input: ChangeRecipeRateInput!): Recipe
                                                                            }
                                                                             
                                                                            # 3
                                                                            type ChangeRecipeRatePayload {
                                                                              rating: Int
                                                                              # NO ID!
                                                                            }


                                                                          Скомпилирил запустил и вуаля нифига уже зведочки не кликаются :D
                                                                          К сведению, что такое глобал ID что это за зверь. Глобал ID это уникальный ID который дается объекту при его создании, например в нашем случае он есть у объекта Recipe (см. schema.graphql)

                                                                          Теперь к тому как нам в этом случае помогут updater и optimicticUpdater. Надо юзать методы Реле-хранилища, чтобы получить доступ к соотвествующим объектам хранилища, не буду ща их перечислять и что они делают, скажу лишь то что через store.getRootField('changeRecipeRate') получаем возращеный объект мутационого запроса, а через store.get(id) получаем доступ к существующему объекту в Реле-хранилище по его глобал ID, ну и меняем там нужные значения. Короче в коде все написано!

                                                                          change-recipe-rate.js:
                                                                          ExpandedWrap disabled
                                                                            import {commitMutation, graphql} from 'react-relay';
                                                                             
                                                                            export default (environment, rating, id) => {
                                                                              const variables = {
                                                                                input: {
                                                                                  rating,
                                                                                  id
                                                                                }
                                                                              };
                                                                              return commitMutation(
                                                                                environment,
                                                                                {
                                                                                  mutation: graphql`
                                                                                    mutation changeRecipeRate_Mutation($input: ChangeRecipeRateInput!) {
                                                                                      changeRecipeRate(input: $input) {
                                                                                        rating
                                                                                      }
                                                                                    }
                                                                                  `,
                                                                                  variables,
                                                                                  // 1
                                                                                  optimisticUpdater: store => {
                                                                                    const recipe = store.get(id);
                                                                                    recipe.setValue(rating, 'rating');
                                                                                  },
                                                                                  // 2
                                                                                  updater: store => {
                                                                                    const root = store.getRootField('changeRecipeRate');
                                                                                    const rating = root.getValue('rating');
                                                                                    const recipe = store.get(id);
                                                                                    recipe.setValue(rating, 'rating');
                                                                                  },
                                                                                  onError: err => console.error(err)
                                                                                }
                                                                              )
                                                                            }


                                                                          Как вы уже догадались зведочки рейтинга закликали :D

                                                                          пс. поскольку кликать по звездам можно много и быстро, то можно легко посадить сервер! :jokingly:
                                                                          поэтому надо предусмотреть защиту от повторных запросов.

                                                                          Прикреплённый файлПрикреплённый файлreact_in_action_v4_4_1.zip (11,9 Кбайт, скачиваний: 139)
                                                                          Сообщение отредактировано: Cfon -
                                                                            Добавил мутацию addRecipe! Пришлось попотеть надеюсь оцените :D

                                                                            Кроме того изменил код GraphQL-сервера, поскольку допустил ряд ошибок и не учел асинхроность операций работы с БД.
                                                                            Код работал, потому что в моем случае все операции выполнялись синхронно и поэтому не было ошибок рантайма.
                                                                            В следущем релизе я учел асинхронность, заюзав промисификацию к БД методам и упорядочил их вызовы через async + await! :D
                                                                            Для того что работали async await надо подключить пакет babel-polyfill через webpack.config.js.

                                                                            server.js:
                                                                            ExpandedWrap disabled
                                                                              import express from 'express';
                                                                              import logger from 'morgan';
                                                                              import Bourne from 'bourne';
                                                                              import path from 'path';
                                                                              import graphqlHTTP from 'express-graphql';
                                                                              import {buildSchema} from 'graphql';
                                                                              import {importSchema} from 'graphql-import';
                                                                              import {connectionFromArray} from 'graphql-relay';
                                                                              import {promisify} from 'util';
                                                                               
                                                                              const app = express();
                                                                              const recipes = new Bourne('db/recipes.json');
                                                                               
                                                                              app.use(logger('dev'));
                                                                              app.use(express.static('public'));
                                                                               
                                                                              // Construct a schema, using GraphQL schema language
                                                                              const schemaDef = importSchema(path.resolve('server', 'schema.graphql'));
                                                                              const schema = buildSchema(schemaDef);
                                                                               
                                                                              // promisify db methods
                                                                              const findRecipes = promisify(recipes.find.bind(recipes));
                                                                              const findOneRecipe = promisify(recipes.findOne.bind(recipes));
                                                                              const updateRecipe = promisify(recipes.update.bind(recipes));
                                                                              const insertRecipe = promisify(recipes.insert.bind(recipes));
                                                                               
                                                                              // The root provides a resolver function for each API endpoint
                                                                              const root = {
                                                                                // Queries
                                                                                recipes: async (args) => {
                                                                                  const conn = await findRecipes()
                                                                                    .then(recipes => connectionFromArray(recipes, args));
                                                                                  return conn;
                                                                                },
                                                                                // Mutations
                                                                                changeRecipeRate: async ({input}) => {
                                                                                  const id = parseInt(input.id, 10);
                                                                                  let recipe = await findOneRecipe({id});
                                                                                  recipe = await updateRecipe({id}, {
                                                                                    ...recipe,
                                                                                    rating: input.rating
                                                                                  }).then(recipes => recipes[0]);
                                                                                  return {recipe};
                                                                                },
                                                                                addRecipe: async ({input}) => {
                                                                                  const {name, rating} = input;
                                                                                  const ingredients = [...input.ingredients];
                                                                                  const instructions = [...input.instructions];
                                                                                  const recipe = await insertRecipe({
                                                                                    name,
                                                                                    ingredients,
                                                                                    instructions,
                                                                                    rating
                                                                                  });
                                                                                  return {recipe};
                                                                                }
                                                                              };
                                                                               
                                                                              app.use('/graphql', graphqlHTTP({
                                                                                schema,
                                                                                rootValue: root,
                                                                                graphiql: true
                                                                              }));
                                                                               
                                                                              app.listen(3000, () => console.log(`Application running at 'http://localhost:3000'`));


                                                                            остальные изменения см в архиве
                                                                            schema.graphql
                                                                            webpack.config.js
                                                                            server.js
                                                                            add-recipe.js
                                                                            recipe-form.js
                                                                            app.js

                                                                            С добавлением нового рецепта прошлось тоже мудохацца! :wall:
                                                                            То одно то другое то вапще жрать захотелось :lool:

                                                                            Объясняю по шагам, вы в надежных руках, ваш моск не пострадает :D
                                                                            1. Пишем код root API для сервера, метод addRecipe
                                                                            2. Добавляем в GraphQL схему новую мутацию addRecipe и сопутствующие типы (см schema.graphql)
                                                                            тестим мутацию addRecipe на localhost:3000/graphql, если запрос добавляет новую запись то продолжаем
                                                                            3. Добавляем директиву @connection(key: "app_Query_recipes") в app.js
                                                                            (имя ключ произвольное) это необходимо если мы юзаем коннекшен
                                                                            4. Пишем код мутации addRecipe (см add-recipe.js) согласно ее определению в схеме (см. schema.graphql)
                                                                            поскольку в отличие от мутации changeRecipeRate тут возвращется новый объект, Реле не сможет автоматом добавить его в хранилище, поэтому надо определить updater, который передается в commitMutation.
                                                                            ВСЕ!
                                                                            да еще надо изменить код submitRecipe в RecipeForm поскольку теперь мы не передаем колбэк через свойтсво onNewRecipe, а напрямую вызываем мутацию addRecipe предварительно импортировав ее.
                                                                            УФФ! Теперь ВСЕ! :D

                                                                            Прикреплённый файлПрикреплённый файлreact_in_action_v4_5.zip (12,54 Кбайт, скачиваний: 112)

                                                                            пс. заметьте размер архива увеличивается постепенно :popcorn:
                                                                            Сообщение отредактировано: Cfon -
                                                                              Продолжаю расширять функционал нашего учебного примера и добавил мутацию removeRecipe! :D

                                                                              Теперь можно добавлять и удалять записи рецептов, правда не стал писать предупреждение перед удалением, все это можно потом добавить, а наша задача разобраться в сути Реле не так ли?

                                                                              Также заюзал библу reactstrap, хотя можно обойтись и чисто bootstrap, но так для разминки заменил на reactstrap, вроде как чуть меньше кодить и запоминать атрибутов bootstrap. Немного изменил оформление приложения, как по мне оно получилось довольно презентабельно, хотя я еще тот спец по офомлению :jokingly:

                                                                              Правда при замене пришлось помудохаться (ну как же без этого >:() с атрибутом ref в <Input>, точнее его отсутствием :D, как оказалось его надо заменить на innerRef, но это еще не все, прежняя форма записи <Input innerRef="_nameInput" /> не сработала и я никак не мог понять как запомнить ссылку на DOM элемент <input> и потом обращаться к ней через this.refs как раньше. Как оказалось надо юзать <Input innerRef={input=>this._nameInput=input}/> и потом обращаться напрямую через this._nameInput, хотя с <input> можно юзать оба способа. Не стал копаться в причинах этого поведения, хотя краем глаза глянул в исходники reactstrap его Input.js и как я понял он также допускает обе формы записи, т.е. в свойство innerRef можно передавать строку или функцию или объект :scratch:

                                                                              см в архиве:
                                                                              schema.graphql
                                                                              server.js
                                                                              remove-recipe.js (реализация мутации на стороне клиента)
                                                                              recipe.js

                                                                              Прикреплённый файлПрикреплённый файлreact_in_action_v4_6.zip (13,41 Кбайт, скачиваний: 109)
                                                                              Сообщение отредактировано: Cfon -
                                                                                Тебе бы посты на хабр или стримчки пилить :whistle:
                                                                                  Заждались меня робяты :D
                                                                                  Или нет? да пофик :D

                                                                                  о чем я хотел сегодня поговорить...

                                                                                  ранее я запостил пост #10 про componentWillReceiveProps, так вот он уже устарел и вместо него в начиная с 16.3 версии юзается getDerivedStateFromProps, он вроде как более безопаснее, чем componentWillReceiveProps, в чем? да хз я сам запуталси :D
                                                                                  вот ссылка сами читайте getDerivedStateFromProps
                                                                                  и кроме того их вапще не рекомендуют юзать! типо есть альтернативы им см тут You Probably Dont Need Derived State

                                                                                  Далее разобрался в непонятках с refs см Refs and the DOM
                                                                                  Как оказалось refs тоже устарел и больше не юзается, вместо него надо юзать React.createRef() или колбэк как в моем примере из поста #38
                                                                                  Кроме того в статье описывается новая фича React.forwardRef() пока на практике мне не пригодилась, но смысл понял.
                                                                                  Если вкраце, то это способ выставления ссылки на внутрений DOM элемент дочернего компонента для использования его из родительского компонета :blink: :D
                                                                                  Вот цитирую :whistle:
                                                                                  Цитата
                                                                                  Ref forwarding is a technique for automatically passing a ref through a component to one of its children. This is typically not necessary for most components in the application. However, it can be useful for some kinds of components, especially in reusable component libraries. The most common scenarios are described below.

                                                                                  Перевожу :D
                                                                                  Цитата
                                                                                  Ref forwarding (пересылка ссылки :jokingly:) - это способ автоматической передачи ref через компонент к одному из его дочерних элементов. Обычно это не требуется для большинства компонентов приложения. Однако это может быть полезно для некоторых видов компонентов, особенно в библиотеках компонентов многократного использования. Наиболее распространенные сценарии описаны ниже.

                                                                                  Теперь понятно почему мне оно еще не пригодилось. Я не пишу библиотеки :D
                                                                                  Что я пишу? Чат-боты для форум на исходниках :lool:

                                                                                  Добавлено
                                                                                  Цитата Serafim @
                                                                                  Тебе бы посты на хабр или стримчки пилить :whistle:

                                                                                  тут пока буду упражняться :D
                                                                                  Сообщение отредактировано: Cfon -
                                                                                    Цитата Cfon @
                                                                                    Я не пишу библиотеки

                                                                                    А я вот пишу) Написал компиль для SDL :whistle:

                                                                                    У меня у енамов можно значения проставлять, а у тебя нет :tong:


                                                                                    Кстати, а во что в JS собирается вот такой код?
                                                                                    ExpandedWrap disabled
                                                                                      input A {
                                                                                          a: B! = {
                                                                                              b: {
                                                                                                  c: {
                                                                                                      a: {}
                                                                                                  }
                                                                                              }
                                                                                          }
                                                                                      }
                                                                                       
                                                                                      input B { b: C! }
                                                                                      input C { c: A! }


                                                                                    Я просто довольно много попарился в компиляторе с детектом рекурсивных отношений во всяких инпутабл типах:
                                                                                    Скрытый текст
                                                                                    user posted image
                                                                                    Сообщение отредактировано: Serafim -
                                                                                      Цитата Serafim @
                                                                                      Кстати, а во что в JS собирается вот такой код?
                                                                                      ExpandedWrap disabled
                                                                                        input A {
                                                                                            a: B! = {
                                                                                                b: {
                                                                                                    c: {
                                                                                                        a: {}
                                                                                                    }
                                                                                                }
                                                                                            }
                                                                                        }
                                                                                         
                                                                                        input B { b: C! }
                                                                                        input C { c: A! }


                                                                                      у меня не собирается, ошибка компиляции в function isType(type) из node_modules/graphql/type/definition.js:
                                                                                      ExpandedWrap disabled
                                                                                        RangeError: Maximum call stack size exceeded
                                                                                        Цитата Cfon @
                                                                                        RangeError: Maximum call stack size exceeded

                                                                                        :crazy:
                                                                                        0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                                                                        0 пользователей:


                                                                                        Рейтинг@Mail.ru
                                                                                        [ Script execution time: 0,1596 ]   [ 35 queries used ]   [ Generated: 19.04.24, 06:48 GMT ]