// 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)
case 'SWITCH_EDIT_MODE':
return Object.assign({}, state, {
editMode: action.value
})
case 'SWITCH_EDIT_MODE':
return { ...state, editMode: action.value}
weatherApiId
спрятать в .env
, а .env
кинуть в .gitignore
(dotenv-webpack to the rescue)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;
MainContainer
должны (в этом проекте) быть functional components. MainContainer
, чтобы представить в функуиональном виде// 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);
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;
}
// 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));
});
}
});
});
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
);
}
}
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;
// ./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)
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'} />);