Ответы пользователя по тегу React
  • Как сделать, что бы ховер на элемент применялся и к детям?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Не стоит полагаться на эти события, они не всегда срабатывают.
    В первую очередь надо избавиться от зазора между родительским и дочерним элементом. Меню dropdown по-хорошему должно иметь позицию top: 100%. Для имитации зазора можно использовать контейнер с padding.
    А можно что-то вроде:
    .user-logout {
      border-top: 5px solid transparent;
      background-clip: padding-box;
    }

    Путей много на самом деле.

    Саму задачу проще решить через css, без лишнего пререндера компонента.
    Ответ написан
    1 комментарий
  • Как сделать отображение страницы после ссылки полностьюзанимающая страницу?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    На сколько я понял вопрос, вам надо переделать так:
    import { BrowserRouter, Route, Link, Switch } from 'react-router-dom';
    
    export default class App extends Component {
      render() {
        return (
          <BrowserRouter>
            <div>
              <ul>
                <li><Link to='/'>Home</Link></li>
                <li><Link to='/about'>About</Link></li>
                <li><Link to='/contact'>Contact</Link></li>
              </ul>
              <hr/>
              <Switch>
                <Route exact path='/' component={Home}/>
                <Route path='/about' component={About}/>
                <Route path='/contact' component={Contact}/>
                <Route path='/test/:id' component={HomePage}/>
              </Switch>
            </div>
          </BrowserRouter>
        );
      }
    }


    export default class Home extends Component{
      render(){
        return(
          <div>
            <h1>Home</h1>
            <div>
              <ul>
                <li><Link to='/test/1'>Фото на документы</Link></li>
                <li><Link to='/test/2'>Принтер</Link></li>
                <li><Link to='/test/3'>Печать на документы</Link></li>
              </ul>
            </div>
          </div>
        );
      }
    }
    Ответ написан
    Комментировать
  • Переменная NODE_ENV на продакшене должна быть установлена в 'production' только во время сборки проекта или же и во время его жизни?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Для того чтобы сделать production сборку, запускайте webpack с ключoм -p:
    webpack -p
    либо устанавливайте переменную process.env.NODE_ENV и используйте UglifyJsPlugin:
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
    }),
    new webpack.optimize.UglifyJsPlugin({
           // options 
        }),

    при запуске с ключом -p, в кофиг добавляются именно эти строки.

    Еще вариант:
    plugins: [
      new webpack.EnvironmentPlugin(['NODE_ENV']),
      new webpack.optimize.UglifyJsPlugin({
          // options 
        }),
    ],


    Строка:
    new webpack.EnvironmentPlugin(['NODE_ENV']),
    аналогична:
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
    }),


    А c NODE_ENV вы production сборку не сделаете, надо использовать именно process.env.NODE_ENV.
    Ответ написан
    1 комментарий
  • Как работает этот код с axios?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Я вам это пример тогда скинул, чтобы показать приближенный к жизни кейс использования этого компонента. Все совсем не обязательно должно быть реализовано именно так.

    1. Смотрите у вас компонент реализует бесконечную подгрузку информации с сервера. Но как вы собрались реализовывать взаимодействие клиента и сервера? Как серверу узнать сколько данных вы получили и какие отдавать дальше? Простейший и проверенный временем вариант пагинация. Надо использовать пагинатор на стороне сервера.
    Допустим на сервере 500 изображений. Как запросить первые 20?
    GET 'api/images?limit=20&page=1'
    Сервер должен вернуть что-то вроде:
    {
      data: [...],
      quantity: 500,
    }

    Теперь вы знаете, что изображений всего 500.
    Запрос на следующие 20 изображений будет:
    GET 'api/images?limit=20&page=2'

    Как только вы загрузите 25-ую страницу, условие в render:
    const hasMore = page * limit < total;
    вернет false, так как:
    25 * 20 < 500 // false
    И ваш компонент перестанет обращаться к серверу и показывать прелоадер при прокрутке в конец списка, так как он полностью загружен.
    Как-то так. Клиент видит бесконечный список, а по факту сервер возвращает данные постранично.

    2. spread operator
    https://developer.mozilla.org/ru/docs/Web/JavaScri...
    es6-features.org/#SpreadOperator
    jsraccoon.ru/es6-spread-rest
    const arr1 = ['a'];
    const arr2 = ['b'];
    const arr3 = [...arr1, ...arr2];
    const arr4 = [...arr1, 1, ...arr2, 'c'];
    
    console.log(arr3); 
    // => ['a', 'b'];
    
    console.log(arr4); 
    // => ['a', 1, 'b', 'c'];
    
    const obj1 = { a: 'a', b: 'old b' };
    const obj2 = { b: 'new b', c: 'c' };
    const obj3 =  { ...obj1, ...obj2 };
    const obj4 = { ...obj2, ...obj1 };
    const obj5 =  { ...obj1, ...obj2, d: 'd' };
    
    console.log(obj3);
    // => { a: 'a', b: 'new b', c: 'c' };
    
    console.log(obj4);
    // => { a: 'a', b: 'old b', c: 'c' };
    
    console.log(obj5);
    // => { a: 'a', b: 'new b', c: 'c', d: 'd' };


    3. Хорошо изучить JavaScript, можно пройти уроки на learn.javascript.ru. Изучайте пути решения типовых задач веба: валидация, авторизация, пагинация, CRUD, REST, передача и хранение файлов и прочее. И пробуйте это на практике.
    Ответ написан
    Комментировать
  • Как правильно одновременно работать с 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);
    Ответ написан
    Комментировать
  • С this или без?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Потому что в первом случае вы обращаетесь к аргументу функции, а во втором к полю класса. Все по канонам JavaScript.
    Ответ написан
    Комментировать
  • Как правиль загрузить 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 component?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    1. Функция map превращает в колбеке элемент вашего массива данных в JSX код описывающий элемент и на выходе выдает массив таких элементов. Это лучший и общепринятый способ рендерить списки в react.
    Банальный пример:
    const employees =  ['Bill', 'Matt', 'Sarah'];
    
    return (
      <ul>
        {employees.map((employee, i) => (
          <li key={i}>employee</li>
        )}
      <ul>
    );

    Результат:
    <ul>
      <li>Bill</li>
      <li>Matt</li>
      <li>Sarah</li>
    </ul>


    2. "Element" как видите строка. Тут разработчики просто пушат в массив строку, но в идеале тут должна вызываться функция иницирующая запрос к серверу.
    Вот более приближенный к жизни пример:
    class Example extends Component {
      state = {
        elements: [],
        page: 1,
        limit: 20,
        total: 0,
        isLoading: false,
        isError: false,
      };
      
      componentDidMount() {
        this.loadThumbnails();
      }
     
      loadThumbnails = () => {
        const { page, limit } = this.state;
        
        this.setState({
          isLoading: true,
          isError: false,
        }, () => {
          axios.get(
            `api/somePath?limit=${limit}&page=${page + 1}`
          ).then(response => {
            const { data: { elements, total } } = response;
          
            this.setState(prevState => ({
              elements: [
                ...prevState.elements,
                ...elements
              ],
              page: page + 1,
              isLoading: false,
              total,
            }));
          }).catch(error => {
            this.setState({
              isLoading: false,
              isError: true,
            });
          });
        });
      };
      
      render() {
        const {
          elements,
          page,
          limit,
          total,
          isLoading,
        } = this.state;
    
        const hasMore = page * limit < total;
    
        return(
          <MasonryInfiniteScroller
            hasMore={hasMore}
            loadMore={this.loadThumbnails}
          >
            {elements.map(element => (
              <img
                key={element.id}
                src={element.thumbnail}
                style={{
                  width: element.width + 'px',
                  height: element.height + 'px',
                }}
              />
            )}
            {isLoading && <div>...Loading</div>}
            {isError && (
              <div>
                Can't load data. Please, check your internet connection.
                <button onClick={this.loadThumbnails}>
                  Try again
                </button>
              </div>
            )}
          </MasonryInfiniteScroller>
        ); 
      }
    }


    Тут как только вы пролистнете список
    Ответ написан
    1 комментарий
  • Почему не работает атрибут ref?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    У вас хандлер неправильный.
    Во-первых переименуйте с Mousedown в handleClick.
    В JavaSсript не принято называть методы с большой буквы, а хандлеры принято называть в формате handleSomeEvent или someEventHandler.

    Во-вторых либо используйте class field function:
    handleClick => e {
      console.log(this.ourDiv);
    };


    либо оберните хандлер в анонимную функцию при передаче в onClick:
    <div
      style={this.state.move}
      onClick ={() => this.handleClick()}
      ref={ourDiv => this.ourDiv = ourDiv}
    ></div>


    Проблема в том, что метод класса при передаче в другое место теряет контекст и this больше не указывает на ваш объект.
    Ответ написан
    1 комментарий
  • Как сделать экспорт в 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 комментария
  • Почему у меня выводит в запросе html страницу?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Потому что у вас первый роут всегда срабатывает:
    .get('*', async ctx => {
      await ctx.render('index');
    })


    Поменяйте местами:
    .get('/api/logout', async ctx => {
      console.log('router');
      ctx.logout();
      console.log(ctx.state.user);
    })
    .get('*', async ctx => {
      await ctx.render('index');
    })
    Ответ написан
    Комментировать
  • Как сделать меню в Semantic UI?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    return (
      <Menu text vertical>
        <Menu.Item
          name="feed"
          active={activeItem === 'feed'}
          onClick={this.handleItemClick}
        >
          <Link to="/feed">Feed</Link>
        </Menu.Item>
      </Menu>
    );
    Ответ написан
    Комментировать
  • Как передать текущего user через socket io react?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    componentDidMount() {
      this.initSocket();
    }
    
    initSocket() {
    
      // set user
      socket.on('init', data => {
        this.setState({
          player: data.nickname,
        }, () => {
          alert(this.state.player);
        });
      });
    }


    1. Используйте componentDidMount, вместо componentWillMount.
    2. Никогда не меняйте state напрямую, для этого есть асинхронный метод setState. Иначе ваш компонент не обновится.
    3. Эту логику лучше вынести из компонента в redux-saga или redux-thunk.
    4. В самом компоненте пока не получили пользователя показывайте лоадер:
    render() {
      const { user } = this.state;
      
      if (!user) return <Loader />  
      
      return (
       ...
      );
    }
    Ответ написан
    1 комментарий
  • Как проверить e.target элемент на наличие onClick?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Вызывайте e.stopPropagation по клику по кнопке:
    handleButtonClick = e => {
      e.stopPropagation();
      // do something else
    };

    Это остановит всплытие события.
    Ответ написан
    Комментировать
  • В чем ошибка сборки webpack?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Замените babel на babel-loader:
    module: {
        loaders: [
          {
            test: /\.jsx?$/,
            loader: 'babel-loader?presets[]=es2015,presets[]=react,presets[]=stage-0'
          },
          ... 
        ]
      }

    Ну а по-хорошему перепишите конфиг по-современному:
    module: {
        rules: [
          {
            test: /\.js$/,
            exclude: /\/node_modules\//,
            use: [
              {
              loader: 'babel-loader',
              options: {
                presets: ['es2015', 'stage-0', 'react']
              }
            }]
          },
          ...
      ]
    }
    Ответ написан
  • Как получать данные с сервера для компонентов?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Во-первых метод componentWillMount скоро будет deprecated. Не надо его использовать.
    Лучшее место для запроса к серверу componentDidMount, пока не пришел ответ показывайте прелоадер.
    Если используете redux то добавьте ключи isLoading и isError для каждой сущности/списка.
    Ответ написан
    Комментировать
  • Как соединить два Switch, с разными состояниями, когда при включении одного должен выключатся другой?

    rockon404
    @rockon404 Куратор тега React
    Frontend Developer
    Cамый правильный вариант создать компонент SwitchGroup:
    const options = [
      {
        value: 'wifi',
        title: 'WiFi',
      },
      {
        value: 'isBT',
        title: 'BT',
      },
    ];
    
    class Expample extends Component {
      state = {
        switch = '',
      };
    
      handleChange = e => {
        const { name, value } = e.target;
        
        this.setState({
          [name]: value,
        });
      };
    
      render() {
        
        return (
          <Wrapper>
            <SwitchGroup
              onChange={this.handleChange}
              name="switch"
              value={this.state.switch}
              options={options}
            />
          </Wrapper>
        );
      }
    }


    Сам компонент написать просто

    class SwitchGroup extends Component {
      ...
      handleClick(value) {
         const { name, onChange } = this.props;
         ...
         onChange({ 
           e: {
             target {
               name,
               value,
             },
           }
         });
      }
    
      render() {
        return(
          <Wrapper>
            {this.props.options.map((el, i) => (
              <Switch
               key={i}
               onClick={() => handleClick(el.value)}
               isActive={el.value === this.props.value}
               ...
              >
                {el.title}
              </Switch>
            ))}
          <Wrapper />
        );
      }
    }

    Тут надо немного дописать реализацию, показана сама идея.
    Чем такое решение лучше. Оно просто масштабируется. handleChange абстрактный слушатель и может слушать все. А SwitchGroup вы сможете добавлять на страницу сколько захотите и с любым набором опций.
    Ответ написан
    5 комментариев