Ответы пользователя по тегу Redux
  • Как использовать combineReducers redux?

    devellopah
    @devellopah
    начальное состояние
    const initialState = {auth: {}, lotteries:{}, categories: {}, tickets:{}}

    сократи количество редьюсеров до 4(по одному на каждый кусок состояния)
    const reducer = combineReducers({ auth, lotteries, categories, tickets})
    Ответ написан
  • Если в action нет асинхронных запросов нужно ли использовать middleware?

    devellopah
    @devellopah
    посчитай прямо в компоненте

    import { sum } from 'lodash'
    ...
    // где-то в методе какого-то компонента
    this.props.action(sum([a, b, c]))
    ...
    const action = payload => ({
        type: ACTION_TYPE,
        payload
    });
    Ответ написан
  • Как сохранить данные в хранилище, передаваемые из метода компонента, используя mapDispatchToProps?

    devellopah
    @devellopah
    // actions.js
    export const increaseNumber = value => ({ type: 'INCREASE_NUMBER', value })
    
    //reducer.js
    ...
    case 'INCREASE_NUMBER':
        return {...state, value: state.value + action.value}
    ...
    // SmallApp.js
    import { increaseNumber } from '..путь до/actions'
    ...
      render(){
    
        return (
          <div>
            <AddComponent add = {this.props.increaseNumber} />
          </div>
        )
      }
    ...
    
    export default connect(mapStateToProps, { increaseNumber })(SmallApp)


    p.s. подключение AddComponent к хранилищу излишнее
    Ответ написан
  • Зачем нужен redux-thunk?

    devellopah
    @devellopah
    Я бы сказал, что redux-thunk, redux-saga или какое-то другое "решение" для организации сайд-эффектов не является необходимой зависимостью для любого приложения, написанного на reactjs.
    Точно так же как и не является необходимостью сам redux. Но если ваше приложение нуждается в инструменте для управления состоянием(redux), то автоматически из этого следует, что оно так же нуждается в инструменте для организации сайд-эффектов.

    В redux-thunk вы диспатчите функцию словно это action. Это очень важно. В контейнере вне зависимости от "природы" экшена, вы будете писать this.props.dispatch(whateverAction).

    А ваш коллега, напротив, будет вынужден синхронные экшены диспатчить через this.props.dispatch(syncAction), а асинхронный просто вызовом функции, содержащей setTimeout.

    Как-то не очень красиво, да и самому контейнеру не обязательно знать какие экшены синхронные, а какие асинхронные.
    Ответ написан
  • Где можно посмотреть пример кода компонента React взаимодействующий с API?

    devellopah
    @devellopah
    в идеале компонент не "взаимодействует" с api.
    если используешь redux-thunk, то запрос на api совершает оператор(action creator), сам же контейнер всего лишь дёргает этот оператор в методе componentDidMount.
    Ответ написан
  • Лучшая библиотека для asynchronous data fetching?

    devellopah
    @devellopah Автор вопроса
    Прошу прощения, не совсем верно сформулировал влпрос. Я имел ввиду библиотеку, которая сохраняет полученные ассинхронным запросом данные в редуксе и затем снабжаете этими данными компоненты.
    Ответ написан
  • Прокомментируете приложение на React/Redux?

    devellopah
    @devellopah
    1) ты можешь использовать spread operator из-под коробки

    вместо

    case 'SWITCH_EDIT_MODE':
          return Object.assign({}, state, {
            editMode: action.value
          })


    так

    case 'SWITCH_EDIT_MODE':
          return { ...state, editMode: action.value}


    2) weatherApiId спрятать в .env, а .env кинуть в .gitignore (dotenv-webpack to the rescue)

    3)MainContainer на мой взгляд
    import React from 'react';
    import { connect } from 'react-redux';
    import { bindActionCreators } from 'redux';
    import * as actionCreators from './../../actions/actionCreators';
    import Main from './../Main/Main';
    
    class MainContainer extends React.Component {
    
    	componentDidMount() {
        this.props.actions.detectLocation();
      }
    
      render() {
      	return <Main {...this.props.store}  {...this.props.actions} />
      }
    }
    
    const mapStateToProps = state => ({ store: state })
    const mapDispatchToProps = dispatch => ({ actions: bindActionCreators(actionCreators, dispatch) })
    
    export default connect(mapStateToProps, mapDispatchToProps)(MainContainer)


    и Main соответсвенно
    import React from 'react';
    import PropTypes from 'prop-types';
    
    import styles from './Main.css';
    
    import PageBackground from './../PageBackground/PageBackground';
    import WeatherIcon from './../WeatherIcon/WeatherIcon';
    import Temperature from './../Temperature/Temperature';
    import Location from './../Location/Location';
    import Loader from './../Loader/Loader';
    import Error from './../Error/Error';
    
    const propTypes = {
      pageBackground: PropTypes.string,
      location: PropTypes.object,
      editMode: PropTypes.bool,
      changeLocation: PropTypes.func,
      switchEditMode: PropTypes.func,
      weatherIcon: PropTypes.string,
      temperature: PropTypes.number,
      weatherDescription: PropTypes.string
    };
    
    export default function Main (props) {
      return (
        <div className={styles.container}>
          {props.loading && <Loader />}
          {props.loadingError && <Error>Sorry, an error occurred. Try to reload page</Error>}
          <PageBackground background={props.pageBackground} />
          <Location city={props.location.city}
            editMode={props.editMode}
            onLocationSelect={props.changeLocation}
            onChangeLocationClick={() => props.switchEditMode(true)}
            onInputBlur={() => props.switchEditMode(false)} />
          <div className={styles.split}>
           <WeatherIcon code={props.weatherIcon} class={styles.icon}/>
          	<span className={styles.date}>Today</span>
          </div>
          <Temperature value={props.temperature}/>
          <div className={styles.description}>props.weatherDescription}</div>
        </div>
      )
    }
    
    Main.propTypes = propTypes;


    4) в идеале все компоненты кромеMainContainer должны (в этом проекте) быть functional components.

    5) можно было бы написать больше редьюсеров(отдельный для получения location, для weather, для pagebackground) это позволило бы тебе "выдавить" логику из дочерних компонентов(PageBackground, Temperature, WeatherIcon) в MainContainer, чтобы представить в функуиональном виде

    6) нужно ограничить кол-во запросов за определённый промужуток времени(например, 15минут), ибо погода не меняется так часто (express-rate-limit).
    Ответ написан
  • Как увязать React router и Redux-редьюсеры?

    devellopah
    @devellopah
    просто мысли вслух

    // types
    const types = {
    	UPDATE_FIELD: 'UPDATE_FIELD',
    };
    //types
    
    // operations
    const updateField = (value, error) => (
    	{ type: types.UPDATE_FIELD, value, error }
    );
    
    // reducer
    const initialState = { 
    	values: {email: '', password: ''}, 
    	errors: { email: '', password: '' } 
    };
    
    function someFormReducer(state = initialState, action) {
    	switch(action.type) {
    		case types.UPDATE_FIELD: {
    			return { 
    				values: { ...state.values, ...action.value }, 
    				errors: { ...state.errors, ...action.error };
    		}
    	}
    	
    	return state;
    }
    // reducer
    
    // так примерно выглядит global store
    {
    	...
    	...
    	form: {
    		login: {
    			values: {
    				email: '',
    				password: '',
    			},
    			errors: {
    				email: '',
    				password: ''
    			}
    		},
    	}
    }
    // так примерно выглядит global store
    
    
    import React from 'react';
    import { validateEmail, validatePassword } from 'someValidateLibrary';
    import { connect } from 'react-redux';
    import { browserHistory } from 'react-router';
    	
    import { updateField } from 'path to operations file (action creators)' 
    
    class SomeForm extends React.Component {
    	
    	handleValueChange = (e) => {
    		const field = e.target.name;
    		let error = '';
    		
    		if(field === 'email' && !validateEmail(field)) {
    			error = "You email is so bad, seriously!";
    		} 
    		else if (!validationPassword(field) ) {
    			error = "Your password is sucks!"
    		}
    		
    		this.props.dispatch(
    			updateField(
    				{ field: e.target.value.trim() }, 
    				{ field: error.trim() }
    			)
    		);
    	} 
    
    	proccessForm = () => {
    		const { errors } = this.props;
    		// если ошибок больше нет, то переход на другой роут
    		if(!errors.email && !errors.password) {
    			browserHistory.push('/home');
    		} else {
    			// your code here
    		}
    		
    	}
    	
    	render() {
    		const { values, errros } = this.props;
    		
    		return (
    			// some ui
    			<form onSubmit={this.proccessForm}>
    				<p class="form-field">
    					<input 
    						type="email" 
    						name="email" 
    						onChange={this.handleValueChange} 
    						value={values.email} 
    					/>
    					<span>{errors.email}</span>
    				</p>
    				<p class="form-field">
    					<input 
    						type="text" 
    						name="password" 
    						onChange={this.handleValueChange} 
    						value={values.password} 
    					/>
    					<span>{errors.password}</span>
    				</p>
    				<button 
    					type="submit" 
    					disabled={!values.email && !values.password}>
    						submit
    				</button>
    			</form>
    		);
    	}
    }
    
    const mapStateToProps = ({ form: { login } }) => (
      { values: login.values, errors: login.errors }
    );
    	
    
    connect(mapDispatchToProps)(SomeForm);
    Ответ написан
  • Выбор между flux и redux?

    devellopah
    @devellopah
    о каких проблемах идёт речь? ты просто перечислил "недостатки", не объяснив в чём дело, с какой реальной проблемой столкнулся?

    это всё равно, что я скажу, что претензии надуманные, не объясняя почему я так думаю.
    Ответ написан
  • Как вывести изображение в React + Redux (webpack)?

    devellopah
    @devellopah
    ты берёшь картинки из папки assets и потом после сборки туда же их и отправляешь. как-то это странно. возможно лучше создать папку/src/images, а в конфиге

    {
          test: /\.(jpg|jpeg|gif|png)$/,
           include: path.resolve(__dirname, "src/images"),
           loader:'url-loader?limit=1024&name=images/[name].[ext]'
       }


    и в стилях

    body {
        background: url(../../assets/back.jpg);
        background-size: cover;
        font-family: 'Open Sans', sans-serif;
    }
    Ответ написан
  • Как выполнить action до рендеринга с сервера?

    devellopah
    @devellopah
    // Register server-side rendering middleware
    app.get('*', (req, res) => {
      if (__DEV__) {
        webpackIsomorphicTools.refresh();
      }
    
       const store = configureStore();
    
      // If __DISABLE_SSR__ = true, disable server side rendering
      if (__DISABLE_SSR__) {
        res.send(renderHtmlPage(store));
        return;
      }
    
      const memoryHistory = createMemoryHistory(req.url);
      const history = syncHistoryWithStore(memoryHistory, store, {
        selectLocationState: state => state.get('routing').toJS(),
      });
    
      // eslint-disable-next-line max-len
      match({history: memoryHistory, routes, location: req.url }, (error, redirectLocation, renderProps) => {
        if (error) {
          res.status(500).send(error.message);
        } else if (redirectLocation) {
          res.redirect(302, redirectLocation.pathname + redirectLocation.search);
        } else if (!renderProps) {
          res.sendStatus(404);
        } else {
          // Dispatch the initial action of each container first
          const promises = renderProps.components
            .filter(component => component.fetchData)
            .map(component => component.fetchData(store.dispatch, renderProps.params));
         
          // Then render the routes
          Promise.all(promises)
            .then(() => {
              // Using the enhanced history of react-redux-router to instead of the 'memoryHistory'
              const props = Object.assign({}, renderProps, { history });
              const content = renderToString(
                <Provider store={store}>
                   <RouterContext {...props} />
                </Provider>
              );
    
              res.status(200).send(renderHtmlPage(store, content));
            });
        }
      });
    });


    твой компонент который загружается при переходе на роут должен иметь статический метод fetchData, он может выглядеть примерно так

    class UsersPage extends React.Component {
    	
    	static async fetchData(url) {
    		try {
    			const res = await axios.get(url);
    			const users = res.data 
    		} catch(err) {
    			console.error(err.message);
    		}
    	}
    	
    	componentDidMount() {
    		this.fetchData(url);
    	}
    	
    	render() {
    		return (
    		 // whatever
    		);
    	}
    }
    Ответ написан
  • Зачем нужна jwt стратегия?

    devellopah
    @devellopah
    Справка(упрощённо).
    Аутентификация - вход в систему.
    Авторизация - получение доступа к инфе из базы данных.

    Если ты создаёшь сессию для пользователей своего приложения, то когда юзер залогинился на сервере инициируется сессия и сервер в куках отправляет на клиент session id.
    В последующих запросах к базе данных сервер читает этот session id из кук, обращается к некой внутренней базе ( обычно имеет форму ключ-значение, где ключ - это session id, значение - mail пользователя, к примеру ) затем обращается к базе данных, достаёт данных для пользователя ( мы определили его на предыдущем шаге ) и возвращается в response.
    Такая система называется stateful ( поскольку мы вынуждены хранить инфу об аутентифицированных пользователях на сервере ). Это не вяжется с концепцией RESTful API, подразумевающей, что сервер должен быть stateless ( не хранить инфу аутентифицированных пользователях ).
    Добиться этого помогает json web token, поскольку он хранит всю необходимую для авторизации пользователя инфу. Это позволяет тебе написать RESTful API(stateless)

    Что почитать: session-based authentication, token-based authentication
    Ответ написан
  • Для чего передавать параметр в функцию next из redux middleware?

    devellopah
    @devellopah
    функция next - это следующая в очереди миддвара, если таковой нет, то это - store.dispatch
    твоя миддвара принимает action, который эксплуатирует, а затем передаёт следующей миддваре.
    Чтобы передать дальше, твоя текущая миддвара возвращает вызов next c action в качестве аргумента, в противном случае action не дойдет до reducer(я так понимаю, возможно кто-то поправит).
    миддвары - это серия функций, которые по очереди работают с экшеном(обычно в миддварах делают сайд-эффекты, типа вывод в консоли экшена или запрос на получение каких-то данных).
    экшен задиспатчен -> серия миддлвар(работают с экшеном) -> экшен дошёл до редьюсера.

    примерно такая же схема как у миддлвар в express,

    приходит запрос(request) -> серия миддлвар(работают с request, формируют response) -> возвращается ответ(response)
    Если нет вызвать next() в миддваре express, приложение подвиснет(если не ошибаюсь).
    Ответ написан
  • Почему не работает redux-logger в изоморфном приложении?

    devellopah
    @devellopah
    лучше как-то так, чтобы не повторяться

    import { compose, createStore, combineReducers, applyMiddleware } from 'redux';
    import createLogger from 'redux-logger';
    import * as reducers from './reducers';
    import promises from './middlewares/promises';
    
    
    const reducer = combineReducers(reducers);
    const middlewares = [promises];
    
    if(process.env.NODE_ENV === 'development') {
      
      if(process.browser) {
        middlewares.push(createLogger());
      }
      
      // другие миддлвары, которые использовать только в этой среде
    }
    
    const store = compose(applyMiddleware(...middlewares))(createStore)(reducer);
    
    export default store;
    Ответ написан
  • Webpack не показывает страницу с динамическим ID?

    devellopah
    @devellopah
    ты же делаешь запрос на сервер, которого у тебя нет, поэтому и ошибка.
    нужно набросать простенький express сервер с wildcard роутом в entry point файле, примерно так

    // ./index.js
    const express = require('express')
    const path = require('path')
    const port = process.env.PORT || 3000
    const app = express()
    
    // serve static assets normally
    app.use(express.static(__dirname + '/public'))
    
    // Handles all routes so you do not get a not found error
    app.get('*', function (request, response){
        response.sendFile(path.resolve(__dirname, 'public', 'index.html'))
    })
    
    app.listen(port)
    console.log("server started on port " + port)


    где бы ты не делал перезагрузку страницы тебя будет выкидывать на главную.

    p.s. почитай про server-side rendering with react
    Ответ написан
  • Нужна лучшая практика что бы убрать писанину в action?

    devellopah
    @devellopah
    то что у тебя(разве что reducer не экспортировал по дефолту)
    https://github.com/erikras/ducks-modular-redux

    если кажется, что слишком много кода в одном файле, то может пойти ещё дальше
    https://github.com/alexnm/re-ducks
    Ответ написан
  • Зачем Redux Saga если есть Redux Thunk?

    devellopah
    @devellopah
    Если сомневаешься нужна ли тебе эта зависимость в проекте, значит она не нужна.
    Ответ написан
  • Почему в React созданные функции(методы) вызываются без аргументов?

    devellopah
    @devellopah
    Я примерно так вижу, что там под капотом происходит. К сожаленью, не заглядывал в исходники, поэтому скорее всего пример не свосем корректный, но на вопрос твой отвечает.

    function connect(...mappers){
    	// не знаю как connect получает доступ к store, поэтому тупо захардкодил, но это не важно 
           // чтобы ответить на вопрос
    	const store = {
    		getState() {
    			return {
    				players: ['Rooney', 'Mata', 'Messi', 'Ronaldo', 'Zlatan']
    			}
    		},
    		dispatch(actionCreator) {
    			return function (...args) {
    				return Promise
    					.resolve( actionCreator(...args) )
    					.then(() => console.log(`I have just called ${actionCreator.name} actionCreator`));
    				};
    			}
    	};
    	
    	let fromStore = {};
    	
    	if(typeof mappers[0] === "function") {
    		const state = store.getState();
    		const stateToProps = mappers[0](state);
    		
    		fromStore = {...stateToProps};
    	}
    
    	if(typeof mappers[1] === "function") {
    		fromStore = { ...fromStore, ...mappers[1](store.dispatch) };
    	}
    	
    	
    	return function(MyComponent) {
    		const HOC = Component => props =>
    			<Component dispatch={store.dispatch} {...fromStore}  {...props}  />
    		
    		return HOC(MyComponent);
    	};
    }
    
    
    const App = (props = { a: 1, b: 2 }) => (
    	console.log('App props', props)
    );
    
    const fetchData = (api) => console.log(`I am gonna fetch some data from this api ${api}`)
    
    const mapStateToProps = state => ({ players: state.players });
    const mapDispatchToProps = dispatch => ({ fetchData:  dispatch(fetchData) });
    	
    const ConnectedApp = connect(mapStateToProps, mapDispatchToProps)(App);
    
    console.log('connectedApp props', ConnectedApp({ hello: 'hello kitty' }).props);


    должен признать что вот так не работает
    console.log('connectedApp props', <ConnectedApp hello={'hello kitty'} />);

    в примере, а должно, но я не хочу париться, поскольку этого примера достаточно
    вот можно поиграться codepen.io/dagman/pen/Ppajeg
    Ответ написан