Ответы пользователя по тегу React
  • Как решить проблему в React.js c checkbox?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Причина бага в том, что вы рендерите списки моделей через map и передаете в key индекс, а индекс в вашем случае всегда равен 0. Само решение показывать списки таким способом очень плохое.
    Как можно переделать. Во-первых использовать для mark не массив, а строку. Использование массива для значения mark необоснованно. Во-вторых все модели автомобилей с фильтров хранить в объекте вида:
    const allCars = {
      bmw: [
        { name: 'x1',  ... },
         ...
      ],
      ...
    }

    Тогда:
    render() {
      const { mark } = this.props;
      const models = allCars[mark]; 
      
      return (
        <div>
          {mark && (
            <div>
              <label>{mark}</label>
              {models.map(model => (
                <div key={model.name}>
                  <label>{model.name}</label>
                  <input
                    name="model"
                    value={model.name}
                    type="checkbox"
                    onClick={this.props.changemodel}
                  />
                </div>
              ))}
            <div>
          )}
        </div>
      );
    }

    Менять состояние так как это делаете вы нельзя, так как вызов setState асинхронный. Например код метода changeMark можно переписать так:
    changeMark (e) {
      const { value } = e.target;
    
      this.setState({ 
        mark: value === 'all' ? [] : [...prevState.mark, value],
        model: [],
        page: 1,
      });
      }
    }

    Для изменения состояния на основе предыдущего передавайте в setState функцию возвращающую состояние, на момент вызова первым аргументом(prevState в примере) в нее придет текущее состояние компонента.

    Для переменных значение которых не переопределяется правильней использовать const, а не let.

    Хандлеры changeBody и changeModel можно объединить в один, а использовав xor из lodash сократить до такого вида:
    handleCheckboxCheck(e) {
        const { name, value } = e.target;
    
        this.setState(prevState => ({
          [name]: _.xor(prevState[name], +value), 
        }));
    }


    Черную магию с array.push и array.length = 0 для обновления состояния, забудьте и никогда не вспоминайте.

    Метод render это самое худшее место для бинда хандлера. Антипатерн. Так можно делать только если надо передать аргументы.
    Вместо этого бинд лучше сделать в конструкторе:
    constructor(props) {
      super(props);
      this.changeModel = this.changeModel.bind(this);
    }

    Либо переписав хандлер с метода класса в поле класса:
    метод класса:
    changeModel (e) {
        // some actions
    }

    поле класса:
    changeModel = e => {
      // some actions
    };
    Ответ написан
  • Какие способы правильно загрузить данные в react компонент?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Вообще использование в render конструкций типа:
    {this.state.data[0].title}
    очень плохой стиль. По возможности избегайте его.
    Чтобы компонент рендерился поле загрузки данных:
    render() {
      const { data } = this.state;
      const isAllDataReady = data && data.length > 0;
    
      return (
        <div>
           {!isAllDataReady && <Preloader />}
           {isAllDataReady && (
             <div>
               <h4>App component</h4>
               {data[0].title}
             </div>
           )}
        </div>
      );
    }
    Ответ написан
    4 комментария
  • Тестирование в React, в чем ошибка?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Как минимум у вас при попытке вызова render выпадает ошибка:
    Cannot convert undefined or null to object
    Так как вы не передаете total в store.
    Зачем вам вообще оборачивать компонент в connect при тестировании?
    Попробуйте изменить файл компонента так:
    import React from 'react';
    import { connect } from 'react-redux';
    
    export class Balance extends React.Component {
      // some code
    }
    
    const mapStateToProps = ({ total }) => ({ total });
    
    export default connect(mapStateToProps)(Balance);

    То, что вы назвали компонент BalanceCont, семантически неверно. Так как контейнер это HOС поверх вашего компонента, вызовом connect вы оборачиваете компонент в контейнер.

    Теперь вы можете импортировать компонент для тестов, как:
    import { Balance } from './Balance';
    А контейнер в приложение как:
    import Balance from './Balance';

    Измененный тест:
    import React from 'react';
    import { shallow, mount } from 'enzyme';
    import { expect } from 'chai';
    import { Balance } from '../app/containers/balance/src/balance';
    
    describe('<Balance />', () => {
      it('Balance test', function () {
        
        const mockTotal = { /* mock code */ };
        
        const wrapper = shallow(<Balance total={mockTotal} />);
        expect(wrapper.contains(<h3>Мои балансы</h3>)).to.equal(true);
      });
    });
    Ответ написан
    2 комментария
  • Почему запрос в react - вечный?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Вы в методе render не передаете метод getData, а вызываете его и передаете результат, который метод даже не возвращает. Исправить можно так:
    class App extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          tweets: []
        };
      }
    
      getData = () => { 
        axios.post('/', {
          username: this.state.username
        })
        .then(({data: tweets}) => {
          console.log(tweets)
          this.setState({ tweets });
        })
        .catch(console.error);
      };
    
      render () {
        const { tweets } = this.state;
        console.log(tweets.length);
    
        if (tweets.length > 0 && tweets[0].user) {
          console.log(tweets.length);
          return (
            <div>
              <header>
                <Form getData={this.getData} />
              </header>
              <main>
                <User user={tweets[0].user} />
                {tweets.map((tweet, i) => <Tweet key={i} tweet={tweet} />)}
              </main>
            </div>
          );
        } else {
          return (
            <Form getData={this.getData} /> 
          );
        }
      }
    }


    Заметьте что я пределал getData из метода класса в свойство класса. Теперь когда getData стрелочная функция, она не потеряет контекст при передаче в другой компонент. Альтернативным решением проблемы с потерей контекста при передаче метода, будет бинд этого метода в конструкторе:
    constructor(props) {
        super(props);
        this.state = {
          tweets: []
        };
    
        this.getData = this.getData.bind(this);
      }
    Ответ написан
  • Как отобразить POST data в react?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    В самом простом варианте:
    class Capture extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          form: {
            username: '',
          },
          tweets: [],
        }
    
        this.onFormSubmit = this.onFormSubmit.bind(this);
        this.handleChange = this.handleChange.bind(this);
      }
    
      onFormSubmit(e) {
        e.preventDefault()
    
        axios.post('/', {
          username: this.state.form.username
        })
        .then(({data: tweets}) => {
          this.setState({ tweets });
        })
        .catch(console.error);
      }
    
      handleChange(event) {
        const { form } = this.state;
    
        form.username = event.target.value;
    
        this.setState({ form });
      }
    
      render () {
        const { tweets } = this.state;
    
        return (
          <div>
            <form onSubmit={this.onFormSubmit}>
              <p className="leadform-component-container">
                <input
                  type="text"
                  placeholder="Username"
                  required
                  onChange={this.handleChange}
                />
              </p>
              <p className="leadform-component-container">
                <input type="submit" value="Press me" />
              </p>
            </form>
            <div>
              {tweets.map((tweet, i) => <Tweet key={i} tweet={tweet} />)}
            </div>
          </div>
        )
      }
    }
    
    ReactDOM.render(<Capture />, document.getElementById("app"));


    Если у твита есть id или любое другое уникальное поле, то следует передавать его в свойство key вместо индекса массива.
    Ответ написан
  • Почему пропсы из Редакса в обработчике Реакт не видит, когда в render(){} видит?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Вы передаете метод clickHandle в колбек слушателя события click, при вызове метод теряет контекст, так как вызывается не на вашем объекте. this в таком случае ссылается не на ваш объект, а на undefined. Так как babel при трансляции добавляет 'use strict' (иначе, при выполнении в браузере, ссылался бы на window). Исправить это можно несколькими способами:
    1. переделать обработчик из метода класса в class field arrow function:
    было:
    clickHandle(e) {
      // some code
    }

    стало:
    clickHandle = e => {
      // some code
    };

    class field arrow function получает контекстом экземпляр класса при инициализации и всегда ссылается на него, куда бы вы ее не передали. Это экспериментальная возможность JavaScript и в спецификации ее пока нет. За трансляцию этой конструкции в валидный код отвечает babel.
    Результат, который будет получен после трансляции, можно посмотреть тут. Строки с 23 по 28.

    2.забиндить его в конструкторе на экземпляр класса:
    constructor(props) {
      super(props);
      this.clickHandle = this.clickHandle.bind(this);
    }

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

    3. обернуть в стрелочную функцию в render:
    <SomeComponent onClick={() => this.сlickHandle()} /> // контекст не будет потерян

    При оборачивании в стрелочную функцию происходит следующее: сама функция использует как контекст ваш объект, поэтому при вызове метод будет вызван на вашем объекте и this в самом методе будет ссылаться на объект. Этот вариант разумно использовать, только тогда, когда в хандлер необходимо передать свои аргументы, помимо event:
    <ListItem
      key={item.id}
      onClick={e => this.сlickHandle(item.id, e)}
    >
      {item.name}
    </ListItem>

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

    Как метод теряет контекст.
    Разберем на примере объекта. То же самое происходит в случае с классом, но мы будем использовать в примере объект:
    var john = {
      firstName: 'John',
      getName() {
        return this.firstName;  // this - контекст вызова и это не всегда наш объект
      }
    }


    Сценарий 1:
    console.log(john.getName()); // john
    Тут мы вызываем метод на объекте. Контекстом будет наш объект.

    Сценарий 2:
    var foo = {
      firstName: 'foo',
    };
    var foo.getName = john.getName;
    console.log(foo.getName()); // foo

    Тут мы передаем метод объекта john в свойство объекта foo без вызова и следующей строкой вызываем его на нем. Контекстом в этот раз будет объект foo. Ошибки не будет только потому, что у объекта foo есть свойство fullName

    Сценарий 3:
    var bar = john.getName;
    console.log(bar()); // undefined

    В данном случае в стандартном режиме контекстом будет window, а в строгом режиме вылетит исключение:
    Cannot read property 'firstName' of undefined
    так как this в строгом режиме будет ссылаться на undefined

    Когда вы передаете метод в колбек onClick или в любой другой колбек события, вызов идет подобно третьему сценарию. Поэтому вы должны позаботиться о том, чтобы ваш метод не терял контекст, используя один из способов приведенных выше.
    Ответ написан
    2 комментария
  • Как динамически добавить Route'ы в Реакте?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Используйте route с параметром:
    <Route path="/stories/:slug" component={Stories}/>
    где slug это поле записи новости в БД, основанная ее имени. Подробнее о slug.
    Ответ написан
    3 комментария
  • Почему асинхронный запрос не выполняется?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    1. Поправьте mapStateToProps:
    const mapStateToProps = state => {
      return {
        items: state.items.items,
        error: state.items.error,
        loading: state.items.loading
      };
    };

    По-хорошему напишите селекторы.

    2. Измените кейс FETCH_SUCCESS:
    export function items(state = initialState, action) {
      const { type, payload } = action;
    
      switch (type) {
    
        // some code
    
        case "FETCH_SUCCESS":
          return { 
            ...state,
            items: [ ...state.items, ...payload.items ],
            loading: false,
          };
    
         // some code
    
      }
    }


    3. По возможности сокращенный вариант:
    export const fetchProductsBegin = () => ({ type: FETCH_BEGIN });
    
    const mapStateToProps = state => ({
      items: state.items.items,
      error: state.items.error,
      loading: state.items.loading,
    });


    https://codesandbox.io/s/jprm2omlr5
    Ответ написан
    1 комментарий
  • Как импортировать несколько компонентов с app.js в react?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    app.js
    export Gap;

    index.js
    ReactDOM.render(<Gap /> , document.getElementById('example'));


    А вообще Gap лучше вынести в отдельный файл.
    Ответ написан
    1 комментарий
  • Как нормально перебрать json?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Вам бы перед тем как браться такие вещи реализовывать, хорошенько основы JavaScript изучить.
    В первом случае у вас в json массив, во втором объект содержащий массив.
    Перебрать элементы во втором случае можно так:
    {cont.continents.map(el => (
      <li key={el.code}>{el.name}</li>
    ))}


    Я вам настоятельно рекомендую пока отложить React и потратить пару дней на изучение основ JavaScript, а так же синтаксиса ES6, JSX и экспериментальных фич языка используемых в React.
    Ответ написан
    Комментировать
  • Как мне сделать переход на другую страницу через Link?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    А зачем вы ссылку в роутер оборачиваете? Исправьте:
    <BrowserRouter>
      <Link to="/registration">Зарегестрироваться</Link>
    </BrowserRouter>

    На:
    <Link to="/registration">Зарегестрироваться</Link>
    Ответ написан
    Комментировать
  • Api запрос из react приложения выполняется только один раз, что делать?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    render() {
      const { res } = this.state;
      const shouldFlimDisplayShown = !!res;
    
      return (
        <div>
          <form onSubmit={this.handleSubmit}>
            <label htmlFor="title">Enter title</label>
            <input id="title" name="title" type="text" />
            <button>Send data!</button>
          </form>
          {shouldFlimDisplayShown && <FilmDisplay filmName={res}/>}
       </div>
      );
    }


    import React from 'react';
    
    class FilmDisplay extends React.Component {
       constructor() {
        super();
    
        this.state = {
          filmData: null
        };
      }
    
      componentDidMount() {
        this.fetchMovie();
      }
    
      componentDidUpdate(prevProps) {
        if (prevProps.filmData !== this.props.filmData) {
          this.fetchMovie();
        }
      }
     
      fetchMovie() {
        const { filmName } = this.props;
        const URL = "http://www.omdbapi.com/?t=" + filmName + "&apikey=6540f2ec&";
    
        fetch(URL).then(res => res.json()).then(json => {
          this.setState({ filmData: json });
        });
      }
    
      render() {
        const { filmData } = this.state;
    
        if (!filmData) return <div>Loading</div>;
      
        return <div>{JSON.stringify(filmData)}</div>;
      }
    }
    
    export default FilmDisplay;
    Ответ написан
    Комментировать
  • Когда в методе render React.JS отрисовываю список, и хочу отобразить только например 'name', выводить id елем. +name, с чем это связано?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Вам необходимо перед вызовом map преобразовать OrderedMap в массив. Для этого можно использовать метод toArray:
    return arr.delete(id).toArray().map(item => <li key={item.id}>{item.name}</li>);
    Ответ написан
    1 комментарий
  • Почему вылетает ошибка при использовании if?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Потому что это JSX. Можно исправить так:
    render() {
      const { _sharedData: { auth_user } } =  this.props;
    
      const shouldShowIconMenu = +auth_user === 1;
    
      return (
        <AppBar>
          {shouldShowIconMenu && (
            <IconMenu>
              ...
            </IconMenu>
          )}
        </AppBar>
      );
    }


    JSX это все-таки синтаксический сахар над React.createElement. Вызов if-else, как и for внутри JSX транслировался бы в нерабочий код. Пример:
    JSX:
    <div id={if (condition) { 'msg' }}>Hello World!</div>

    В результате трансляции получаем невалидный код:
    React.createElement("div", { id: if (condition) { 'msg' } }, "Hello World!");
    Ответ написан
    2 комментария
  • Как изменить данные из одной "утки" в другой?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Зачем вообще в товаре свойство addedToCart?
    Представьте, что у вас в приложении есть или будет пагинация. Вы перешли вперед-назад и загрузили товары заново. Ключи при этом будут потеряны.

    Сделайте селекторы checkIsProductAddedToCartSelector и isProductAddedToCartSelector. Первый пусть возвращает функцию принимающую id товара и возвращающую true если товар найден в store корзины. Второй пусть принимает id товара и возвращает булево значение, если товар найден или не найден в store корзины. Используйте для этого reselect.
    И добавляйте их в mapStateToProps в connect.
    В списке можно вызывать так:
    const ProductsList = ({
      productsList,
      checkIsProductAddedToCart,
    }) => (
      <ul>
        {productsList.map(product => (
          <Product
            addedToCart={checkIsProductAddedToCart(product.id)}
            product={product}
          />
        ))}
      </ul>
    );
    
    const mapStateToProps(state => ({
      productsList: productListSelector(state),
      checkIsProductAddedToCart: checkIsProductAddedToCartSelector(state),
    }));
    
    export default connect(mapStateToProps)(ProductsList);

    В детализации так:
    const mapStateToProps((state, ownProps) => ({
      isAddedToCart: isProductAddedToCartSelector(state, ownProps),
    }));
    
    export default connect(mapStateToProps)(ProductDetails);


    Примерные реализации селекторов:
    import { createSelector } from 'reselect';
    
    const cartSelector = state => state.cart;
    
    const cartProductsSelector = createSelector(
      cartSelector,
      cart => cart.products,
    );
    
    // возвращает функцию, принимающую id, которую можно использовать при построении списков
    const checkIsProductAddedToCartSelector = createSelector(
      cartProductsSelector,
      products => id => products.some(product => product.id === id),
    );
    
    const productIdSelector = (_, props) => props.product.id;
    
    // возвращает булево значение, важно чтобы в компоненте было свойство product
    const isProductAddedToCartSelector = createSelector(
      cartProductsSelector,
      productIdSelector,
      (products, id) => products.some(product => product.id === id),
    );
    Ответ написан
    7 комментариев
  • Как собрать заготовку на webpack под react?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    А вас не смущает, что в руководстве первый webpack, а у вас четвертая версия? Остальное даже не смотрел. Ищите, что-нибудь посвежей, c тех пор как вышла эта статья многое сильно изменилось.
    На github полно готовых боилерплейтов на любой вкус. В англоязычном сегменте полным полно свежих статей.
    Ответ написан
    Комментировать
  • Как связать компоненты?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Такие задачи легко решаются через родителя:
    class Parent extends Component {
      state = {
        text: 'some text',
      };
    
      handleClick = () => {
        this.setState({ text: 'some other text'});
      };
    
      render() {
        const { text } = this.state;
    
        return (
          <Wrapper>
            <Button onClick={this.handleClick} />
            <Text>{text}</Text>
          </Wrapper>
        );
      }
    }

    В компонентной структуре , как и в любой другой древовидной, физически невозможно расположить два компонента, чтобы они не находились в одном общем родителе. Это факт.
    Если вы не используете redux и задача носит частный характер, то передача колбека через родителя и даже через несколько компонентов, может быть простым и эффективным решением.
    Другое дело, что в более менее сложном react приложении хорошо использовать redux или аналоги.
    Ответ написан
    Комментировать
  • React + Redux, почему не передаются данные?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Исправьте:
    const mapStateToProps = (state) => {
      projects: state.projects
    }

    на:
    const mapStateToProps = (state) => ({
      projects: state.projects
    });

    JavaScript парсер, в вашем случае, распознает фигурные скобки ни как возвращаемый функцией объект, а как тело блока функции, которая ничего не возвращает. Чтобы возвращать объект в короткой записи его необходимо заключать в скобки.
    Ответ написан
    6 комментариев
  • Что выбрать для server side rendering react?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Лучше сразу писать изоморфный проект, так как есть множество ньюансов которые стоит учитывать при разработке таких приложений. На github можно найти готовые боилерплейты. На написание своего может уйти от пары часов до пары дней, в зависимости от требуемого набора инструментов. Так же вы можете посмотреть в сторону таких инструментов как next.js, razzle, after . Но точно не знаю, насколько легко, при необходимости, мигрировать с таких решений.
    Ответ написан
    6 комментариев
  • Можно ли при обработке события submit формы, узнать введённые данные?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    class Example extends Component {
      state = {
        inputValue: '',
      };
    
      handleChange = e => { 
        const { name, value } = e.target;
        
        this.setState({
          [name]: value,
        });
      };
    
      handleSubmit = e => {
        e.preventDefault();
        //  получить состояние формы можно обратившись к this.state
      };
    
      render() {
        const { inputValue } = this.state;
    
        return (
          <form onSubmit={this.handleSubmit}>
            <input
              name="inputValue"
              value={inputValue}
              onChange={this.handleChange}
            />
            ...
          </form>
        );
      }
    Ответ написан
    Комментировать