Ответы пользователя по тегу Redux
  • Почему два раза вызывается console.log?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Метод componentDidMount вызывается после render. Видимо, при первом вызове render у вас свойство productDetail.loading стоит в положении false.
    Если собираетесь рендерить список, то ничего страшного в этом нет, так как с пустым массивом ошибки [].map не будет, а после render сразу начнется загрузка.
    В других случаях надо делать дополнительные проверки.
    Ответ написан
    Комментировать
  • Ассинхроность в react - redux?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Не находите странным, что компонент у вас называется ProductDetailContainer, при этом вы делаете запрос по id, но пишите полученные данные в редьюсер для списка, заменяя массив объектом и переписывая ключи состояния списка?
    Ошибка с map как раз из-за того, что вы массив объектом подменяете.
    Вы неправильно спроектировали хранилище и пытаетесь использовать одни и те же ключи для разных целей.

    И кто вас научил таким бессмысленным кульбитам вроде dispatchGetProductDetailWired? Так, видимо, до выхода react-redux делали.
    Ваш action можно сократить до:
    export const getProductDetail = id => async dispatch => {
      try {
        dispatch(fetching());
        const data = await ProductsService.getProductById(id);
        dispatch(receiveData(data));
      } catch (error) {
         dispatch(fetchingError(error);
      }
    };


    В компоненте:

    class ProductDetailContainer extends Component {
      componentWillMount() {
        const { match: { params: { id } }, getProductDetail } = this.props;
    
        getProductDetail(id);
      }
      /* ... */
    }
    
    const mapStateToProps = state => ({
      productDetail: state.products,
    });
    
    const matchDispatchToProps = {
      getProductDetail,
    }
    
    export default connect(mapStateToProps, matchDispatchToProps)(ProductDetailContainer);
    Ответ написан
    1 комментарий
  • Как дождаться всех данных при данной конструкции React Redux Async Recursion?

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

    Перерисовка наступает тогда когда вы обновляете store. Ничего удивительного.

    Если так хотите оставить и использовать свой подход. У вас есть два пути:
    1. Добавить в store ключ isAllDataLoaded и обновлять его в редьюсере когда приходит последняя часть данных.
    2. Добавить в проект redux-thunk. Переписать свою функцию в async action:
    const fetchData = () => async dispatch => {
      const data = [];
      try {
        while(true) {
          const response = await Api.getSomeData({ from: data.length });
          data.push(response.item); // если item это массив, то data.push(...response.item); 
          if(!response.items.length) {
            break;
          }
        }
        
        dispatch(fetchDataSuccess(data));
        
        return data;
      } catch (e) {
        dispatch(fetchDataFail(e));
      }
    };


    Еще учитесь писать поддерживаемый и читаемых код. Соблюдайте стандарты комьюнити, такие запросы, при использовании redux, принято выносить в async action или сагу.

    В API принято реализовывать эндпоинты для получения как одного экземпляра данных, так и целой коллекции.
    Возможно, вам стоит написать эндпоинт для получении всей коллекции данных. Потому что сейчас ваш код выглядит как набор костылей, написанных, в попытке заполнить именно эту брешь в API.
    Ответ написан
    6 комментариев
  • Что такое mapDispatchToProps?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Это объект или функция передаваемая в connect вторым аргументом.
    Если вы передаете в connect вторым аргументом объект с AC, то connect оборачивает ваши AC в функцию-обертку () => store.dispatch(AC) и передаёт в props компонента. Если передаете вторым аргументом функцию, connect выполняет ее, передавая на вход первым аргуменом store.dispatch, вторым props компонента, и передаёт в props компонента результат выполнения. Все.
    Ответ написан
  • Как получать данные redux?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Хорошее руководство: Introduction to React with Redux and Redux Thunk
    redux-thunk
    Ответ написан
    Комментировать
  • Как получить доступ к [[PromiseValue]]?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Не правильно используете Fetch API.
    Простой пример использования:
    fetch('https://example.com/api')
      .then(response => response.json())
      .then(json => dispatch(someAction(json)))
      .catch(console.log);
    Ответ написан
    2 комментария
  • Redux - ошибка "Objects are not valid as a React child" при объединении редюсеров через combineReducers. Как можно исправить?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Вы пытаетесь отрендерить объект состояния и ловите исключение.
    У вас в свойство count передается объект:
    {
      counterReducer: 0,
    }

    Исправьте mapSateToProps на:
    function mapStateToProps(state) {
        return {
            count: state.counterReducer,
        };
    }

    или:
    const mapStateToProps = state => ({
      count: state.counterReducer,
    });
    Ответ написан
    Комментировать
  • Return: React Component: будет ли утечка памяти?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Бесполезный велосипед изобретаете.

    Вариант 1. Если используете redux-thunk:
    export const handleError = error => ({ type: 'NEW_ERROR', payload: error });
    export const doSomething = data => ({ type: 'DO_SOMETHING', payload: data });

    import { apiCall } from './api';
    import { handleError, doSomething } from './actionCreators';
    
    export const asyncAction = () => dispatch => {
      try {
        const result = await apiCall();
        dispatch(doSomething(result));
        
        return result;
      } catch (e) {
        dispatch(hanldeError(e));
      }
    };

    аналогичным образом можно использовать блок catch если используете redux-saga.
    Если еще не используете, одно из этих middleware, то рекомендую скорей познакомиться с ними и начать использовать.

    Вариант 2. Если не используете:
    export const handleError = error => ({ type: 'NEW_ERROR', payload: error });
    export const doSomething = data => ({ type: 'DO_SOMETHING', payload: data });

    import { connect } from 'react-redux';
    import { apiCall } from './api';
    import { handleError, doSomething } from './actionCreators';
    
    class ExampleComponent extends React.Component {
      componentDidMount() {
        const { dispatch } = this.props;
    
        apiCall().then(
          res => dispatch(doSomething(res)),
          err => dispatch(handleError(err)),
        );
      }
      
      render() { /* ... */ }
    }
    
    export default connect()(ExampleComponent);

    или:
    import { connect } from 'react-redux';
    import { apiCall } from './api';
    import { handleError, doSomething } from './actionCreators';
    
    class ExampleComponent extends React.Component {
      componentDidMount() {
        const { doSomething, handleError } = this.props;
    
        apiCall().then(
          res => doSomething(res),
          err => handleError(err),
        );
      }
      
      render() { /* ... */ }
    }
    
    mapDispatchToProps = {
      handleError,
      doSomething,
    };
    
    export default connect(null, mapDispatchToProps)(ExampleComponent);


    Вообще, самый простой способ сделать dispatch в store это:
    store.dispatch(someAction());

    Прежде чем начнете изобретать очередной велосипед, настоятельно рекомендую хорошо изучить документацию react, redux и react-redux.
    Ответ написан
    1 комментарий
  • Почему возвращает undefined а state?

    rockon404
    @rockon404 Куратор тега Redux
    Frontend Developer
    const mapStateToProps = state => ({
      user: state.users.user,
    });


    Тут у вас какая-то ерунда:
    return {
       ...state,
      user: state.user.concat({
        surname: action.surname,
        name: action.name,
        patronymic: action.patronymic,
        phone: action.phone,
        isLoggingIn: action.isLoggingIn
      })
    };


    Вы что вообще тут сделать хотите?
    Ответ написан
  • Почему возникает Actions must be plain objects без ReduxThunk?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Потому что redux-thunk это middleware и если бы вы заглянули в его исходники(всего 14 строк), то вы бы увидели, что он перехватывает и вызывает функции, передавая в них нужные аргументы и возвращает результат, не предавая его дальше. Без него их перехватывать нечему и они попадают туда куда не должны попадать и провоцируют ошибку, так как Redux без мiddleware на вход в dispatch принимает только объекты, о чем и говорится в тексте ошибки, которую вы получаете.

    Возвращаемое значение(return) в асинхронных действиях используется только вами, его возвращает вызов dispatch. В редьюсеры, как было озвучено выше, оно не попадает.
    Так как возвращается Promise, его можно использовать как-то так:
    Ваш Async action:
    export const asyncAction = (...someArgs) => async dispatch => {
      const res = await someAsyncCall(...someArgs);
      dispatch({ type: SOME_ACTION_TYPE, payload: res });
    
      return res;
    };

    Использование в коде(явный пример с dispatch):
    componentDidMount() {
      const { dispatch } = this.props;
    
      dispatch(asyncAction(...optionalArgs)).then(result => doSomething(result));
    }

    То же самое с проброской async action через connect:
    componentDidMount() {
      const { asyncAction } = this.props;
    
      asyncAction(...optionalArgs).then(result => doSomething(result));
    }


    Вообще, для использования then значение из асинхронной функции возвращать совсем не обязательно и такой код тоже будет прекрасно работать:
    export const asyncAction = (...someArgs) => async dispatch => {
      const res = await someAsyncCall(...someArgs);
      dispatch({ type: SOME_ACTION_TYPE, payload: res });
    };

    Использование в коде(явный пример с dispatch):
    componentDidMount() {
      const { dispatch } = this.props;
    
      dispatch(asyncAction(...optionalArgs)).then(() => doSomething());
    }
    Ответ написан
    8 комментариев
  • Как будет выглядеть аналогичный код на любом action creator?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Вопрос поставлен некорректно. Action creators - это функции возвращающие объекты действия(Actions) те, что у вас в actions.js
    Action это объект со свойством type(типом действия) и опционально полезной нагрузкой, который передается в dispatch.

    redux-actions и redux-act это бойлерплейт утилиты для генерации action creators и редьюсеров. Советую пока даже не смотреть в их сторону(а можно и вообще не смотреть), лучше хорошо изучите голый API Redux.
    Еще советую потратить время на знакомство с immutable.js

    И еще, ваш код в actions.js не будет работать, так как функции loading, success, error используются раньше, чем определены.
    Либо поменяйте местами, так чтобы send был объявлен под ними:
    const loading = () => ({ ... });
    const success = () => ({ ... });
    const error = (message) => ({ ... });
    
    export const send = ({ name, phone }) => { ... };

    либо используйте для них ключевое слово function:
    export const send = ({ name, phone }) => { ... };
    
    function loading() { 
      return { ... };
    };
    
    function success() { 
      return { ... };
    };
    
    function error(message) { 
      return { ... };
    };

    А тут можете почитать о разнице между Function Declaration и Function Expression.
    Ответ написан
    Комментировать
  • Как вернуться к начальному состоянию редюсера в redux?

    rockon404
    @rockon404 Куратор тега Redux
    Frontend Developer
    const RESET = 'MODULE_NAME::RESET';
    
    const reset => () => ({ type: RESET });
    
    const initialState = {
      // someState
    };
    
    const reducer = (state = initialState, action) => {
      const { type, payload } = action;
    
      switch (type) {
        case RESET:
          return initialState;
        // ...
      }
    };
    Ответ написан
    Комментировать
  • Является ли корректным в идеологии Redux диспатчить экшен постфактум?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    А зачем вам понадобилась подобная интеграция роутинга с Redux? Я к тому, что многие люди пихают в стор все подряд, сами не понимая зачем.

    смена роута > dispatch > обновление state

    Непонятно зачем вообще в этой схеме dispatch > обновление state, почему бы не использовать
    dispatch(push('/somePlace')); раз уж в проекте используетеся react-router-redux?

    Насколько я знаю, redux предполагает несколько иную очередность: dispatch > смена роута в ответ на dispatch > обновление state.


    Непосредственно Redux предполагает только dispatch > обновление state. Остальное это middleware и сайд эффекты.

    Мы в последних двух проектах вообще не использовали react-router-redux.
    Ответ написан
    1 комментарий
  • Как реализировать подобный функционал на react + REDUX?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    В самом простом варианте.
    1. При загрузке приложения по опеределеному роуту, разбирать query string на составляющие и по ним делать запрос к API и рендерить состояние фильтров.
    2. По клику на фильтр, обновление его состояния и push нового location с query string, собранной по новому состоянию фильтра.
    Ответ написан
    9 комментариев
  • Как настроить маршрутизацию?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Вы можете добавить в store ключ isLoaded и проверять:
    const { isLoaded, fetchData } = this.props;
    
    if (!isLoaded) fetchData();


    Не понятно зачем action fetchData абстрактный. Переделайте в fetchTodos и пропишите url в нем, а не передавайте в компоненте.

    Контейнер CreateTodo можно переписать так:
    import React, {Component} from 'react';
    import { connect } from "react-redux";
    import { addData } from '../actions';
    import Form from '../components/Form';
    
    const mapDispatchToProps = {
        addData,
    };
    
    export default connect(null, mapDispatchToProps)(Form);
    Ответ написан
    2 комментария
  • ReactJS: Почему компонент не обновляется при изменении хранилища?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Вы передаете в компонент один и тот же объект. Вот он и не обновляется. Изменения хранилища ваш компонент не интересуют, только изменение данных которые вы туда передаете.
    Храните плоские данные. Тогда таких проблем возникать не будет.
    Ответ написан
    3 комментария
  • Как быть в рамках Redux, когда экшены раскиданы по компонентам?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Решение плохое. В таком случае можно было и вовсе обойтись без Redux. Обернуть в connect можно любой компонент. И чем ближе вы будете передавать части состояния к месту их использования, тем меньше в вашем приложении будет лишних перерисовок.
    Одно модальное окно на все приложение не лучшее решение.
    Ответ написан
    Комментировать
  • Хранение (для вёрстки) данных состояния приложения в Redux. Адекватно ли?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Если, что-то проще сделать без redux, не надо это туда тянуть. Это касается всего: форм, модалок, фильтров и прочего.
    Определять версию лучше по user agent, а передавать эти данные через контекст.
    Ответ написан
    Комментировать
  • Почему выдает ошибку cannot read propery of undefined?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Видимо в initialState пустой массив items не задаете или уничтожаете его в редьюсере в кейсе FETCH_MENU. Без кода редьюсера точно не сказать. Но думаю дело именно в этом.

    На будущее. Прикладывайте к вопросу примеры кода, а не скриншоты. И примеры именно того кода который вызывает ошибку, а не какого-то другого.
    Ответ написан
    Комментировать
  • Почему пропсы из Редакса в обработчике Реакт не видит, когда в 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 комментария