Ответы пользователя по тегу Redux
  • Почему асинхронный запрос не выполняется?

    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 комментарий
  • Как изменить данные из одной "утки" в другой?

    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 комментариев
  • React + Redux, почему не передаются данные?

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

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

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

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    const PrivateRoute = ({ component: Component, isAuth, ...rest }) => (
      <Route {...rest} render={props => (isAuth
          ? <Component {...props} />
          : <Redirect to='/login' />
        )}
      />
    );
    
    const mapStateToProps = state => ({
      isAuth: state.isAuth,
    });
    
    const mapDispatchToProps = {
      authenticated,
    };
    
    export default connect(mapStateToProps, mapDispatchToProps)(PrivateRoute);
    Ответ написан
  • Почему props меняется сразу для всех, а не для каждого отдельно, react/redux?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Можно сделать isLoading и хранилище сущностей объектом:
    const initialState: {
      entities: {}.
      isLoading: {
        'ALL': false, 
      },
       isError: {
        'ALL': false, 
      }
    }


    const someEntityReduser = (state = initalState, action) => {
      const { type, payload } = action;
    
      switch (type) {
        case GET_ALL_ENTITIES_REQUEST:
          return {
            ...state,
            isLoading: {
              ...state.isLoading,
              'ALL': true, 
            },
            isError: {
              ...state.isError,
              'ALL': false, 
            },
          };
    
        case GET_ALL_ENTITIES_SUCCESS:
          return {
            ...state,
            entities: payload,  // либо нормализация если приходят массивом
            isLoading: {
              ...state.isLoading,
             'ALL': false,
            },
          };
    
        case UPDATE_ENTITY_REQUEST:
          return {
            ...state,
            isLoading: {
              ...state.isLoading,
              [payload.id]: true, 
            },
            isError: {
              ...state.isError,
              [payload.id]: false, 
            },
          };
          
        case UPDATE_ENTITY_SUCCESS:
          return {
            ...state,
            entities: {
               ...state.entities,
               [payload.id]: payload,  
            },
            isLoading: {
              ...state.isLoading,
              [payload.id]: false,
            },
          };
        
         /* other cases */
      }
    }
    Ответ написан
  • Почему возвращает undefined is not an object (evaluating 'this.props.users')?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Вы передаете функию колбеком в слушатель события onClick, она теряет контекст и this больше не ссылается на ваш компонент.
    Исправить можно, изменив хандлер с class method на inline class function:
    submitBalance = () => {
      // your stuff
    };

    либо можно забинтить функицю в конструкторе:
    constructor(props) {
      super(props);
      this.submitBalance.bind(this);
    }


    Так же можно обернуть this.submitBalance в стрелочную функцию в render, но этот вариант лучше использовать когда надо передать свои аргументы:
    onClick={e => this.submitBalance(someArg, e)}
    Ответ написан
    1 комментарий
  • Почему возникает ошибка при действии delete?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Вы задаете начальное такое начальное состояние:
    { tweets: tweets.data }
    А потом пытаетесь на нем вызвать метод массива filter, когда состояние является объектом.

    Исправьте редьюсер так:
    const reducer = (state = { tweets: tweets.data }, action) => {
      switch (action.type) {
        case 'DELETE': {
          return {
            ...state,
            tweets: state.tweets.filter(value => value.id !== action.id),
            };
        }
        default:
          return state;
      }
    };


    Демо: https://codesandbox.io/s/n5wlj8wq34
    Ответ написан
    3 комментария
  • Как правильно одновременно работать с cookies и БД?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Вы не с той стороны подходите к задаче.
    Пишите в БД timestamp deadLineDate, по дате создания и определяйте статус.
    Ответ написан
  • Почему не обновляются props после dispatch?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Вы мутируете массив tasks который передаете в компонент. При получении новых свойств, после обновления стора, в компоненте в который вы предаете его происходит проверка:
    this.props.tasks === nextProps.tasks; // true
    Она возвращает true, так как это тот же самый массив и компонент не обновляется.

    Исправьте так:
    export default function addDelete(state = initialState, action){
      switch (action.type) {
        case "ADD_TASK":
          return {
            ...state,
            tasks: [...state.tasks, {{id: action.id, time : action.time, task: action.task}}],
          };
        case "DELETE_TASK":
          return {
            ...state,
            tasks: [...state.tasks.filter(task => task.id != action.id)],
          };
        case "SET_ACSSES":
          return {
            ...state,
            add: !state.add,
          };
        default:
          return state;
      }
    }


    Но будет еще лучше, если поправите свои actionCreators. Кладите полезную нагрузку в ключ payload:
    export default function add(time, task, id){
      if(!task || !time){
        return {
          type: "SET_ERROR",
          payload: "Error, invalid data"
        };
      }
    
      return{
        type: "ADD_TASK",
        payload: {
          id: id,
          time: time,
          task: task,
        },
      }
    }


    export const deleteTask = id => ({
      type: 'DELETE_TASK',
      payload: id,
    });


    Тогда код редьюсера будет такой:
    export default function addDelete(state = initialState, action) {
      const { type, payload } = action;
    
      switch (type) {
        case "ADD_TASK":
          return {
            ...state,
            tasks: [...state.tasks, payload],
          };
        case "DELETE_TASK":
          return {
            ...state,
            tasks: [...state.tasks.filter(task => task.id != payload)],
          };
        case "SET_ACSSES":
          return {
            ...state,
            add: !state.add,
          };
        default:
          return state;
      }
    }
    Ответ написан
    1 комментарий
  • Почему не работают вложенные маршруты в React приложении?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    У вас роут /settings со свойством exact. Роуты с этим свойством срабатывают только при точном совпадении маршрута. Уберите его и все будет работать.
    Ответ написан
    Комментировать
  • Почему не работает redux-saga?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    У вас ошибка тут:
    export const getAccounts = (userID) => {
      fetch(`${BASE_URL}/users/${userID}/accounts/`).then(resp => {
        resp.json().then(accounts => accounts.Items[0].accounts);
      })
    }

    Ничего не возвращаете.

    Исправить так:
    export const getAccounts = userID =>
      fetch(`${BASE_URL}/users/${userID}/accounts/`).then(resp => {
        resp.json().then(accounts => accounts.Items[0].accounts);
      });


    И лучше используйте axios, вместо fetch

    Сущность Service бесполезна в данной архитектуре, импортируйте и используйте функцию напрямую:
    response = yield call(getAccounts, userID);
    Ответ написан
    Комментировать
  • Как правиль загрузить api в react?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Для асинхронных запросов следует использовать redux middleware, например redux-thunk:
    import { fetchInitialDataApi } from './api';
    
    const FETCH_DATA_REQUEST = 'FETCH_DATA_REQUEST';
    const FETCH_DATA_SUCCESS = 'FETCH_DATA_SUCCESS';
    const FETCH_DATA_ERROR = 'FETCH_DATA_ERROR';
    
    const fetchDataRequest = () => ({ type:  FETCH_DATA_REQUEST });
    
    const fetchDataSuccess = data => ({
      type:  FETCH_DATA_SUCCES,
      payload: data,
    });
    
    const fetchDataError = error => ({
      type:  FETCH_DATA_ERROR,
      payload: error,
    });
    
    const fetchData => async dispatch => {
      try {
        dispatch(fetchDataRequest());
        const { data } = await fetchDataApi();
        dispatch(fetchDataSuccess(data));
      } catch error {
        dispatch(fetchDataError(error));
      }
    };
    
    const initialState = {
      data: {},
      isLoading: false,
      isError: false,
      error: null,
    };
    
    export default function(state = initialState, action) {
      const { type, payload } = action;
    
      switch (type) {
        case FETCH_DATA_REQUEST:
          return {
            ...state,
            isLoading: true,
            isError: false,
            error: null,
          };
        case FETCH__DATA_SUCCESS:
          return {
            ...state,
            data: payload,
            isLoading: false,
          };
        case FETCH_DATA_ERROR:
          return {
            ...state,
            isLoading: false,
            isError: true,
            error: payload,
          };
      default:
        return state;
      }
    }
    Ответ написан
    4 комментария
  • Как сделать экспорт в react\redux после запроса API?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Сам вопрос поставлен вразрез с концепцией Redux, да и самой модульной концепцией JavaScript. Вам не надо пытаться экспортировать модуль после запроса, так делать нельзя. Вам надо задать начальное состояние и определить там ключи состояния isLoading, isError. При инициализации приложения произойдет запрос на сервер за данными и пока они не пришли isLoading будет иметь значение true, вы, в идеале, в этот момент должны показывать пользователю прелоадер.
    Ну, а то что вы пытались сделать это в корне не правильно.

    При использовании redux-thunk должно получится, что-то вроде:
    import { fetchInitialDataApi } from './api';
    
    const FETCH_INITIAL_DATA_REQUEST = 'FETCH_INITIAL_DATA_REQUEST';
    const FETCH_INITIAL_DATA_SUCCESS = 'FETCH_INITIAL_DATA_SUCCESS';
    const FETCH_INITIAL_DATA_ERROR = 'FETCH_INITIAL_DATA_ERROR';
    
    const fetchInitialDataRequest = () => ({ type:  FETCH_INITIAL_DATA_REQUEST });
    const fetchInitialDataSuccess = data => ({
      type:  FETCH_INITIAL_DATA_SUCCES,
      payload: data,
    });
    const fetchInitialDataError = error => ({
      type:  FETCH_INITIAL_DATA_ERROR,
      payload: error,
    });
    
    const fetchInitialData => async dispatch => {
      try {
        dispatch(fetchInitialDataRequest());
        const { data } = await fetchInitialDataApi();
        dispatch(fetchInitialDataSuccess(data));
      } catch error {
        dispatch(fetchInitialDataError(error));
      }
    };
    
    const initialState = {
      data: {},
      isLoading: false,
      isError: false,
      error: null,
    };
    
    export default function(state = initialState, action) {
      const { type, payload } = action;
    
      switch (type) {
        case FETCH_INITIAL_DATA_REQUEST:
          return {
            ...state,
            isLoading: true,
            isError: false,
            error: null,
          };
        case FETCH_INITIAL_DATA_SUCCESS:
          return {
            ...state,
            data: payload,
            isLoading: false,
          };
        case FETCH_INITIAL_DATA_ERROR:
          return {
            ...state,
            isLoading: false,
            isError: true,
            error: payload,
          };
      default:
        return state;
      }
    }


    Если же хотите получать начальные данные сразу и они статичные, то пишите их в шаблон страницы приложения в window._data и забирайте через reducer:
    export default (state = window._data) => state;
    Ответ написан
    2 комментария
  • Как обнаружить в чем ошибка загрузки данных с сервера (React/Redux/thunk)?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Итак, у вас отсутствуют условия инициации загрузки комментариев в CommentsList:
    componentWillReceiveProps({ isOpen, article, loadArticleComments }) {
          loadArticleComments(article.id)
      }

    каждый раз когда компонент получает новые свойства он вызывает componentWillReceiveProps. С правильно настроенными параметрами в redux он это делать будет вечно. Исправить можно так:
    componentWillReceiveProps({ isOpen, article, loadArticleComments }) {
          if (
               !article.commentsLoaded &&
               !article.commentsLoading &&
               !this.props.isOpen &&
               isOpen
             ) {
                loadArticleComments(article.id)
          }
      }

    Расшифровывается так: если комментарии не загруженны и еще не загружаются и если список был закрыт и открылся, то можно загрузить комментарии. Как только они будут загруженны, проверка условий будет вылетать на первом шаге.

    В редюсере не помню что было но надо так:
    case "LOAD_ARTICLE_COMMENTS_SUCCESS": {
          return articleState
            .setIn(["entities", action.payload.articleId, "commentsLoading"], false)
            .setIn(["entities", action.payload.articleId, "commentsLoaded"], true);
        }

    По окончанию загрузки комментариев, ставим статье статусы, что комментарии загружены.

    Ну и осталось восстановить рендер в содержимого комментария в Comment и все будет работать:
    return (
        <div>
          <h5>{comment.user}</h5>
          {comment.text}
        </div>
      );
    Ответ написан
    Комментировать
  • Как правильно использовать react redux при отправке формы?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Примерно так это можно сделать:

    Ваш компонент:
    import { login } from './loginDucks';
    
    class UserForm extends Component {
      ...
      handleSubmit = e => {
        this.props.login(this.state.form);
      }
      ...
      render() {
          const { isLoading, isError, shouldRedirect } = this.props;
          if (shouldRedirect) <Redirect to="/index" >;
    
        return (
          ...
        );
      }
    }
    
    const mapStateToProps = state => ({
      isLoading: state.login.isLoading,
      isError: state.login.isError,
      shouldRedirect: state.login.shouldRedirect,
    });
    
    const mapDispatchToProps = {
      login,
    };
    
    export default connect(mapStateToProps, mapDispatchToProps)(UserForm);


    loginDucks.js
    const LOGIN_REQUEST = 'LOGIN_REQUEST';
    const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
    const LOGIN_ERROR = 'LOGIN_ERROR';
    
    const loginSuccess = data => ({
      type: LOGIN_SUCCESS,
      payload: data,
    });
    
    const loginError = data => ({
      type: LOGIN_ERROR,
    });
    
    export const login = form => async dispatch => {
      try {
        const res = await axios.post('/api/user', form);
    
        if (!res.data) dispatch(loginError());
        
        dispatch(loginSuccess());
      } catch e {
        dspatch(loginError());
      }
    }
    
    const initialState = {
      isLoading: false,
      isError: false,
      shouldRedirect: false,
    };
    
    export default (state = initialState, action) => {
      const { type, payload } = action;
    
      switch(type) {
        case LOGIN_REQUEST:
          return { 
            ...state,
            isLoading: true,
            isError: false,
          };
        case LOGIN_SUCCESS:
          return { 
            ...state,
            isLoading: false,
            shouldRedirect: false,
          };
        case LOGIN_ERROR:
          return  { 
            ...state,
            isLoading: false,
            isError: true,
          };
        default:
          return state;
      }
    };


    import React from 'react';
    import ReactDOM from 'react-dom';
    import  { Provider } from 'react-redux';
    import { createStore, combineReducers, compose, applyMiddleware } from 'redux';
    import thunk from 'redux-thunk';
    import { Route, Switch } from 'react-router';
    import { BrowserRouter } from 'react-router-dom';
    import Auth from './component/AuthForm';
    import Helmet from './aplication';
    import loginReducer from './loginDucks';
    
    function user(state = {}, action){
        if(action.type === 'ADD_USER'){
            return [
                ...state,
                action.user
            ];
        }
        return state;
    }
    
    const rootReducer = combineReducers({
      login: loginReducer,
      user,
    });
    
    const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
    
    const store = createStore(rootReducer, composeEnhancers(
      applyMiddleware(thunk),
    ));
    
    ReactDOM.render(
            <div>
                ...
            </div>,
        document.getElementById('content')
    Ответ написан
  • Как вызвать еще один метод при success action-a, react/redux?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Так:
    export const getReq = data => (
      async dispatch => {
        try {
          dispatch({
            type: types.GET_REQUEST,
          });
    
          let res = await api.getReq(data);
          
          dispatch({
            type: types.GET_SUCCESS,
            payload: res,
          });
    
          res = await api.getReq2(data); 
    
        } catch (error) {
          dispatch({
            type: types.GET_ERROR,
          });
        }
      }
    );

    dispatch синхронный вызов, нет смысла перед ним писать await.

    Если надо запустить втрой aync action то так:
    export const getReq = data => (
      async dispatch => {
        try {
          dispatch({
            type: types.GET_REQUEST,
          });
    
          const res = await api.getReq(data);
          
          dispatch({
            type: types.GET_SUCCESS,
            payload: res,
          });
    
          dispatch(req2(res));
        } catch (error) {
          dispatch({
            type: types.GET_ERROR,
          });
        }
      }
    );
    Ответ написан
    5 комментариев
  • Как работает этот AC с async/await?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Нет. В первом случае вы возвращаете из AC функцию c аргументом dispatch.
    Во втором случае вы не возвращаете ничего и убрали аргумент dispatch, заменив названием.
    А сама функция dispatch, которая передается в аргумент, как раз синхронная.

    Добавьте return и будет аналогично первому варианту:
    export const addComponent = data => {
      return async function(dispatch) {     //  тут добавлен return и аргумент
        let comps = await fire.database().ref("components");
        comps.push({
          name: data.component,
          comp: data.component_name
        });
    
         //так же в любое время вы можете вызвать dispatch
         dispatch({ type: 'someAction', payload: 'someValue' });
      };
    }

    Еще пример:
    const foo = x => y => x + y;
    аналогично:
    const foo = x => {
      return  y => {
        return x + y;
      }
    };

    Как это работает:
    const foo = x => y => x + y;
    
    const a = x(5);
    const b = a(6);
    
    console.log(b);
    // => 11

    Значение а можно представить так:
    y => 5 + y;

    Функция у вас асинхронная и пока не зарезолвится comps ее выполнение не продолжится, так как перед вызовом возвращающим значение comps стоит ключевое слово await.
    export const addComponent = data => async dispatch => {
      let comps = await fire.database().ref("components");
      // функция продолжит выполнение только когда вернется значение присвамваемое comps
      comps.push({
        name: data.component,
        comp: data.component_name
      });
    };


    Как я понимаю у вас возникли проблемы при работе с firebase?
    Могу порекомендовать использовать готовую библиотеку, вроде https://github.com/prescottprue/react-redux-firebase
    или разобраться с отличной библиотекой redux-saga и написать с помощью нее удобную реализацию взаимодействия с firebase.
    Ответ написан
    2 комментария
  • Почему не обновляется props?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Мутируете старый объект состояния, а надо возвращать новый.
    Исправить можно так:
    let counterReducer = (state=initialState, action) => {
        let newstate = { ...state };
    
        switch(action.type){
            case "INC":
                newstate.counter+=action.payload
                return newstate
            case "DEC":
                newstate.counter+=action.payload
                return newstate
            default:
                return newstate
        }
    }
    Ответ написан
    4 комментария
  • Как правильно получать обновленный Store в React Reduxe?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    В render такие действия выполнять нельзя. Хоть dispatch и выполняется синхронно, свойства получаемые компонентом иммутабельны, а это значит, что компонент получит новые свойства, выполнит все функции жизненного цикла и получит новый массив men, только в следующий render.

    Для добавления вашей сущности можно использовать хандлер:
    class ActionsReducers extends React.Component {
      handleAddMan = () => {
        this.props.addMan({ name: 'Альберт', family: 'Эйнштейн' });
      };
      
      render() {
        const { men } = this.props;
    
        return (
          <Wrapper>
            <List>
              {men.map(man => <Man key={man.id} man={man} />)}
              {!men.length && <Empty>No data in list.</Empty>}
            </List>
            <Button onClick={this.handleAddMan}>Add man</Button>
          </Wrapper>
        );
      }
    }


    Или один из методов жизненного цикла:
    class ActionsReducers extends React.Component {
      componentDidMount() {
        this.props.addMan({ name: 'Альберт', family: 'Эйнштейн' });
      }
    
      render() {
        const { men } = this.props;
    
        return (
          <Wrapper>
            <List>
              {men.map(man => <Man key={man.id} man={man} />)}
              {!men.length && <Empty>No data in list.</Empty>}
            </List>
          </Wrapper>
        );
      }
    }
    Ответ написан