Ответы пользователя по тегу Redux
  • Как в React Reduxe получить Store переданный через Provider?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Макс Мачез, вам не надо обращаться к store напрямую в компоненте, если вы используете react-redux.
    Вы не должны мапить весь стор, как это делаете в примере в комментарии, мапьте только данные необходимые компоненту:
    const mapStateToProps = state => ({
      userData: state.userData,
    });


    по-хорошему, для получения части объекта состояния, надо использовать selector pattern, отличная его реализация reselect:
    import { createSelector } from 'reselect';
    
    export const userDataSeleror = state => state.userData;
    
    export const userSelector = state => createSelector(
      userDataSelector,
      userData => userData.user,
    );
    
    export const isUserDataLoadingSelector = state => createSelector(
      userDataSelector,
      userData => userData.isLoading,
    );
    
    export const isUserDataErrorSelector = state => createSelector(
      userDataSelector,
      userData => userData.isError,
    );


    тогда контейнер для компонента будет примерно такой:
    import { fetchUserData } from './somePlace';
    import Profile from './Profile';
    import {
      userSelector,
      isUserDataLoadingSelector,
      isUserDataErrorSelector,
    } from './selectors';
    
    const mapStateToProps = state => ({
      user: userSelector(state),
      isLoading: isUserDataLoadingSelector(state),
      isError:  isUserDataErrorSelector(state),
    });
    
    const mapDispatchToProps = {
      fetchUserData,
    };
    
    export default connect(mapStateToProps, mapDispatchToProps)(Profile);


    Компонент внутри вообще ничего не должен знать про redux, он лишь получает набор обязательных свойств и использует, а откуда они приходят ему не важно:
    class Profile extends Component {
      componentDidMount() {
        this.props.fetchUserData();
      }
      
      render() {
        const { user, isLoading, isError } = this.props;
        return (
           ...
        );
      }
    }


    Все, что вы передаете посредством декоратора connect в компонент, доступно в последнем в его свойствах (this.props), аргументы поэтому так и называются mapStateToProps и mapDispatchToProps. Все actionCreators переданные объектом в mapDispatchToProps оборачиваются в dispatch, внутренним вызовом bindActionCreators.
    Ответ написан
    2 комментария
  • Как задать отображение spinner до ответа от сервера reactJS?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Идеальный вариант, создать для этих задач компонент:
    function Block({ 
      children,
      isLoading, 
      isError, 
      isEmpty, 
      emptyText, 
      fetch,
    }) {
      const showPreloader = isLoading && !isError;
      const allDataIsReady = !isLoading && !isError;
      const showEmptyText = allDataIsReady && isEmpty && emptyText;
    
      return (
        <Wrapper>
          {showPreloader && <Preloader />}
          {isError && <TryAgain fetch={fetch} />}
          {showEmptyText && <Empty>{emptyText}</Empty>}
          {allDataIsReady && children}
        </Wrapper>
      );
    }


    И использовать его в коде так:
    render() {
      const { isLoading, isError, orderList } = this.props;
    
      return (
        <Block
          isLoading={isLoading}
          isError={isError}
          isEmpty={!ordersList.length}
          fetch={this.fetchOrdersList}
          emptyText="You don't have any orders."
        >
          {ordersList.map(order => <Order key={order.id} order={order} />}
        </Block>
      )
    }


    Название служебных дочерних компонентов компонента Block условные, но думаю суть их задач передают.
    Ответ написан
    4 комментария
  • Как улучшить Route, react?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Роуты:
    export const routes = [
      {
        component: Home,
        path: '/',
      },
      {
        component: Roster,
        path: '/roster',
     },
     {
        component: Schedule,
        path: '/schedule',
     },
    ];

    Компонент:
    export default function SwitchWithRoutes({ routes }) {
      return (
        <Switch>
          {routes.map((route, i) => (
             <Route
               key={i}
               exact={route.path === '/'}
               path={route.path}
               component={route.component}
             />
           )}
        </Switch>
      );
    }


    Так вы сможете при необходимости написать функцию getRoutes с нужным вам поведением или фильтровать роуты в зависимости от прав, законнектив SwitchRoutes на стор и добавив в роуты дополнительные ключи.
    Ответ написан
    6 комментариев
  • Как должен выглядеть тест для action, react/redux?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Исправленный вариант:
    it('test openModal', () => {
        expect(actions.openModal({ name: 'Some name'}, '1')).toEqual({
          type: types.OPEN_MODAL,
          payload: {
            id: '1',
              initData: {
                name: 'Some name',
            },
          },
        });
      });


    Вы вместо аргументов передавали в actionCreator объект action, который actionCreator должен на основе аргументов создавать и возвращать. В итоге он у вас возвращал:
    {
      type: types.OPEN_MODAL,  // тут естественно значение
      payload: {
        id: undefined,
        initData: {
          id: '1',
          initData: {
            name: 'Some name',
          },
        },
      },
    }
    Ответ написан
    1 комментарий
  • Почему react/redux не рендерит компонент?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Вы мутируете старое состояние, естетсвенно у вас ничего не работает.
    Исправить можно так:
    export default function(state = initialState, action) {
       if (action.type === 'DIALOG_SELECTED') {
          return state;
       }
       if (action.type === 'DIALOG_DELETE') {
          const newState = [...state];
          newState.shift();
    
          return newState;
       }
       else {
          return state;
       }
    }


    bindActionCreators с connect не используют. Бессмысленно, так как connect сам его вызывает. Так будет лаконичней:
    const mapStateToProps = state => ({
      dialogs: state.dialogs,
    });
    
    const mapDispatchToProps = {
      selectDialog: actions.selectDialog,
      deleteDialog: actions.deleteDialog, 
    };
    
    export default connect(mapStateToProps, mapDispatchToProps)(Sidebar);


    Функции биндить каждый рендер не лучшая идея. Используйте class field function:
    class Sidebar extends Component {
    
       selectDialog = () => {
          this.props.selectDialog();
    
       };
       deleteDialog = () => {
          this.props.deleteDialog();
       };
    
       render() {
          console.log('рендер сайдбара');
          return (
             <div className='sidebar'>
                <div className='sidebar__content'>
                   <div className='sidebar__head'></div>
                   <SidebarChatGroup chatName='Общий'>
                      <button onClick={this.selectDialog}>Выбрать</button>
                      <button onClick={this.deleteDialog}>удалить</button>
                   </SidebarChatGroup>
                </div>
                <div className='sidebar__bg'></div>
             </div>
          )
       }
    }
    Ответ написан
    1 комментарий
  • Как в React + Redux обмениваться данными между компонентами?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    По-хорошему, надо код запроса писать вне компонента, а в компонент передавать action инициирующий запрос. Хорошим вариантом будет использовать для этого redux-saga. В Settings вы должны только запрашивать данные и как только они приходят получать их где хотите из store.

    Примерный код:
    Редьюсер и action creators:
    const FETCH_USER_DATA_REQUEST = 'FETCH_USER_DATA_REQUEST';
    const FETCH_USER_DATA_SUCCESS = 'FETCH_USER_DATA_SUCCESS';
    const FETCH_USER_DATA_FAIL = 'FETCH_USER_DATA_FAIL';
    
    export const fetchUserData = () => ({
      type: FETCH_USER_DATA_REQUEST,
    });
    
    export const fetchUserDataSuccess = data => ({
      type: FETCH_USER_DATA_SUCCESS,
      payload: data,
    });
    
    export const fetchUserDataFail = () => ({
      type: FETCH_USER_DATA_FAIL,
    });
    
    const initialState = {
      data: {},
      isLoading: true,
      isError: false,
    };
    
    export default (state = initialState, action) => {
      const { type, payload } = action;
    
      switch (action) {
        case FETCH_USER_DATA_REQUEST:
          return {
            ...state,
            isLoading: true,
          };
       
        case FETCH_USER_DATA_SUCCESS:
          return {
            ...payload,
            isLoading: false,
            isError: false,
          };
    
       case FETCH_USER_DATA_FAIL:
         return {
           ...state,
           isLoading: false,
           isError: true,
         };
       
       default: 
          return state;
      }
    };


    Сага. По вызову функции fetchUserData, тут произойдет вызов api, в случае успеха в store придут данные, в случае ошибки isError примет значение true:
    import { call, put, takeLatest } from 'redux-saga/effects';
    import {
      FETCH_USER_DATA_REQUEST,
      fetchUserDataSuccess,
      fetchUserDataFail,
    } from '../ducks/userDucks';
    import { fetchUserData } from '../api';
    
    export function* fetchUserDataSaga() {
      try {
        const { data } = yield call(fetchUserData);
    
        yield put(fetchUserDataSuccess(data));
      } catch (error) {
        yield put(fetchUserDataFail());
      }
    }
    
    export default function* watchFetchUserDataSaga() {
      yield takeLatest(FETCH_USER_DATA, fetchUserDataSaga);
    }


    Для выборки из store лучше использовать selector:
    export const userDataSelector = state => state.userData;


    Тут провайдим данные пользователя в User:
    import User from './User';
    import { userDataSelector } from '../selectors';
    
    const mapStateToProps = state => ({
      userData: userDataSelector(state),
    });
    
    export default connect(mapStateToProps)(User);


    Тут провайдим данные пользователя и fetchUserData в Settings:
    import Settings from './Settings';
    import { userDataSelector } from '../selectors';
    import { fetchUser } from '../ducks/userDucks';
    
    const mapStateToProps = state => ({
      userData: userDataSelector(state),
    });
    
    const mapDispatchToProps = {
      fetchUserData,
    };
    
    export default connect(mapStateToProps, mapDispatchToProps)(Settings);
    Ответ написан
    Комментировать
  • Как правильно задеплоить приложение с базой данных?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Вы можете сейчас писать в cookie уникальный id. Утилита для генерации.
    Писать в firebase данные по этому ключу.
    При инициализации приложения проверять если cookie есть, то запросить в firebase данные по ключу. Если нет сгенерировать id и записать в cookie. В итоге заметки у всех будут разные. А подобрать чужой id ключ маловероятно.
    Правда логику обновления заметок придется изменить, но вам ее так и так придется менять.
    Тут надо будет по каждому изменению писать их массивом в firebase по одному ключу.

    А потом и с авторизацией разберетесь.

    Вместо cookie можно использовать localstorage.

    Еще проще пока писать заметки пользователей в localstorage, не используя firebase
    Ответ написан
    1 комментарий
  • Почему не могу оперировать со стандартными пропсами из store'a?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    combineReducers принимает на вход объект с функциями вида:
    (state: S, action: A) => S
    а вы передаете boolean.

    Создайте для isInputActive функцию reducer, что-то вроде
    const isInputActiveReducer = (state = false, action) => {
      const { payload, type } = action;
    
      switch(type) {
        case TOGGLE_INPUT:
          return payload;
      default:
          return state;
      }
    }


    Но вообще передавать состояние input в store плохая идея. Для этого с лихвой хватит state компонента.
    Ответ написан
    2 комментария
  • Почему AC не вызывает reducer?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Nikita Shchypylov, у тебя в коде точно что-то не правильно. Ты его сам писал или что-то копировал?
    Не хватает полных примеров кода, чтобы понять в чем причина.
    Вот что точно надо поменять:
    1. inputIdeaHandle надо передать в компонент через декоратор connect:
    import { connect } from 'react-redux';
    import { inputIdeaHandle } from '../actions';
    
    const mapDispatchToProps = {
      inputIdeaHandle,
    };
    
    export default connect(null, mapDispatchToProps)(Dashboard);

    2. changeHandle должен быть inlineFunction:
    changeHandle = e => {
      const { value } = e.target;
      this.props.inputIdeaHandle(value);
    };
    Ответ написан
    Комментировать
  • При подключении loadable-components - получаю ошибку Unexpected token?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Цитата из официальной документации:

    Use babel-plugin-syntax-dynamic-import on the client.
    Use babel-plugin-dynamic-import-node on the server.


    Вы не используете и получаете ошибку, так как в ES6 import может находиться только в верхнем скоупе и нигде более.
    Ответ написан
    1 комментарий
  • Что за ошибка при попытке выполнить action?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Видимо ошибка тут:
    import {ADD_USER} from '../constans/appConstants';
    Попробуйте вывести в консоль ADD_USER, скорей всего там undefined.
    Возможно опечатка в пути импорта.
    И еще, старайтесь в actionCreator возвращать полезную нагрузку в ключе payload

    export const addUser = (name, age) => {
        return {
            type: ADD_USER,
            payload: {
                name: name,
                age: age,
            },
        }
    };
    Ответ написан
    3 комментария