I'm trying to make re-directing function from the QnaNew.js.
onSubmit = (formProps) => {
this.props.createPost(formProps, () => {
this.props.history.push('/qna');
});
};
I want to make it When I send post req then redirect to '/qna'.
Strangely, the redirect is working in Login.js, Join.js page.
history.js
import { createBrowserHistory } from 'history';
export default createBrowserHistory();
src - index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Router } from 'react-router-dom';
import './index.css';
import App from './App';
import history from './history';
// Redux
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose } from 'redux';
import reducers from './reducers';
import reduxThunk from 'redux-thunk';
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducers,
{
auth: { authenticated: localStorage.getItem('token') },
},
composeEnhancer(applyMiddleware(reduxThunk))
);
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<App />
</Router>
</Provider>,
document.getElementById('root')
);
QnANew.js
import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
import { CKEditor } from '#ckeditor/ckeditor5-react';
import ClassicEditor from '#ckeditor/ckeditor5-build-classic';
import { compose } from 'redux';
import { connect } from 'react-redux';
import * as actions from '../../actions';
//import { createPost } from '../../actions'
class QnANew extends Component {
renderInput({ input, label, meta }) {
return (
<div className="qnaTitle">
<label>{label}</label>
<input {...input} />
<div>{meta.error}</div>
</div>
);
}
renderEditor({ input, label }) {
return (
<div className="qnaContent">
<label>{label}</label>
<CKEditor
data={input.value}
editor={ClassicEditor}
onChange={(event, editor) => {
return input.onChange(editor.getData());
}}
/>
</div>
);
}
onSubmit = (formProps) => {
this.props.createPost(formProps, () => {
this.props.history.push('/qna');
});
};
render() {
const { handleSubmit } = this.props;
return (
<div className="QnaNew">
<h2>Using CKEditor 5 build in React</h2>
<h2>Using CKEditor 5 build in React</h2>
<h2>Using CKEditor 5 build in React</h2>
<h2>Using CKEditor 5 build in React</h2>
<h2>Using CKEditor 5 build in React</h2>
<form onSubmit={handleSubmit(this.onSubmit)} className="ui form">
<fieldset>
<Field name="title" type="text" component={this.renderInput} label="Enter Title" autoComplete="none" />
</fieldset>
<fieldset>
<Field name="content" type="text" component={this.renderEditor} label="Enter Description" autoComplete="none" />
</fieldset>
<button type="submit">Submit</button>
</form>
</div>
);
}
}
const validate = (formValues) => {
const errors = {};
if (!formValues.title) {
errors.title = 'You must enter a title';
}
return errors;
}
const formWrapped = reduxForm({ form: 'QnANew', validate })(QnANew);
export default compose(connect(null, actions))(formWrapped);
actions - index.js
import axios from 'axios';
import history from '../history';
import qna from '../apis/qna';
import { AUTH_USER, AUTH_ERROR, CREATE_QNA, FETCH_QNA, FETCH_QNAS, EDIT_QNA, DELETE_QNA } from './types';
export const join = (formProps, callback) => async dispatch => {
try {
const response = await axios.post('http://localhost:3001/join', formProps);
dispatch({ type: AUTH_USER, payload: response.data.token });
localStorage.setItem('token', response.data.token);
callback();
} catch (e) {
dispatch({ type: AUTH_ERROR, payload: 'Email in use'});
}
}
export const login = (formProps) => async (dispatch) => {
try {
const response = await axios.post('http://localhost:3001/login', formProps);
dispatch({ type: AUTH_USER, payload: response.data.token });
localStorage.setItem('token', response.data.token);
history.push('/');
} catch (e) {
dispatch({ type: AUTH_ERROR, payload: 'Invalid login credentials' });
}
};
export const logout = () => {
localStorage.removeItem('token');
return {
type: AUTH_USER,
payload: ''
};
}
export const createPost = (formProps) => async (dispatch) => {
const response = await qna.post('/qnanew', { ...formProps });
dispatch({ type: CREATE_QNA, payload: response.data });
history.push('/qna');
};
export const fetchPost = (id) => async dispatch => {
const response = await qna.get(`/qna/${id}`);
dispatch({ type: FETCH_QNA, payload: response.data });
};
// Post List
export const fetchPosts = () => async dispatch => {
const response = await qna.get('/qna');
dispatch({ type: FETCH_QNAS, payload: response.data });
};
export const editPost = (id, formValues) => async dispatch => {
const response = await qna.put(`/qna/${id}`, formValues);
dispatch({ type: EDIT_QNA, payload: response.data });
};
export const deletePost = (id) => async dispatch => {
await qna.delete(`/qna/${id}`);
dispatch({ type: DELETE_QNA, payload: id });
}
Login.js
import React, { Component } from 'react';
import { reduxForm, Field } from 'redux-form';
import { compose } from 'redux';
import { connect } from 'react-redux';
import * as actions from '../actions';
import './Login.scss';
class Login extends Component {
onSubmit = (formProps) => {
this.props.login(formProps, () => {
this.props.history.push('/');
});
};
render() {
const { handleSubmit } = this.props;
return (
<div className="Join">
<h1>Login</h1>
<p>Login page</p>
<form onSubmit={handleSubmit(this.onSubmit)} className="JoinForm">
<fieldset>
<label>Email</label>
<Field name="email" type="text" component="input" autoComplete="none" />
</fieldset>
<fieldset>
<label>Password</label>
<Field name="password" type="password" component="input" autoComplete="none" />
</fieldset>
<div>{this.props.errorMessage}</div>
<button type="submit">Login</button>
</form>
</div>
);
}
}
function mapStateToProps(state) {
return { errorMessage: state.auth.errorMessage };
}
export default compose(connect(mapStateToProps, actions), reduxForm({ form: 'login' }))(Login);
you can use like this:
this.props.history.push('/');
this.props.history.go('/');
or:
window.location.href = "/";
Related
I am trying to connect Primereact to my django backend project use a restframework and corsheader to connect both but i keep getting this error when trying to get or pass data.
Warning: Failed prop type: The prop addLead is marked as required in LeadsForm, but its value is undefined.Uncaught TypeError: _this.props.addLead is not a function LeadsForm.js:24
My action file
import axios from 'axios';
import { GET_LEADS, ADD_LEAD } from './types'
export const getLeads = () => dispatch => {
axios`enter code here`
.get("http://127.0.0.1:8000/api/leads/")
.then(res => {
dispatch({
type: GET_LEADS,
payload: res.data
});
}).catch(err => console.log(err));
};
// ADD LEADS
export const addLead = lead => dispatch => {
axios
.post("http://127.0.0.1:8000/api/leads/", lead)
.then(res => {
dispatch({
type: ADD_LEAD,
payload: res.data
});
}).catch(err => console.log(err));
}
LeadsForm file
import React, { Component } from 'react'
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { addLead } from '../actions/leads';
import {InputTextarea} from 'primereact/inputtextarea';
import {InputText} from 'primereact/inputtext';
import { Button } from 'primereact/button';
export class LeadsForm extends Component {
state = {
name: '',
email: '',
message: ''
};
static propTypes = {
addLead: PropTypes.func.isRequired
};
onChange = e => this.setState({ [e.target.name]: e.target.value});
onSubmit = e => {
e.preventDefault();
const { name, email, message } = this.state;
const lead = { name, email, message };
this.props.addLead(lead);
this.setState({
name: "",
email: "",
message: ""
});
};
// onSubmit = e => {
// this.setState({
// name: "",
// email: "",
// message: ""
// });
// };
render() {
const { name, email, message } = this.state;
return (
<div className="card card-w-title">
<div className="p-grid ">
<h2>Add Lead</h2>
<form onSubmit={this.onSubmit}>
<div className="p-col-12">
<span className="p-float-label">
<InputText id="in"
name="name"
value={name}
onChange={this.onChange} size={50} />
<label htmlFor="in">Name</label>
</span>
</div>
<div className="p-col-12">
<span className="p-float-label">
<InputText id="in"
name="email"
value={email}
onChange={this.onChange} size={50}/>
<label htmlFor="in">Email</label>
</span>
</div>
<div className="p-col-12">
<span className="p-float-label">
<InputTextarea id="in" size={50} rows={5} cols={30}
name="message"
value={message}
onChange={this.onChange} />
<label htmlFor="in">Message</label>
</span>
</div>
<Button type = "submit" value="Submit" label="Save" style={{marginBottom:'10px'}} className="p-button-raised" />
{/* <button type="submit" className="btn btn-primary">
Submit
</button> */}
</form>
</div>
</div>
)
}
}
// LeadsForm.propTypes= {
// addLead: PropTypes.func
// }
export default connect(null, { addLead })(LeadsForm);
reducer/leads.js
import { GET_LEADS, ADD_LEAD } from '../actions/types';
const initialState = {
leads: []
}
export default function (state = initialState, action){
switch(action.type){
case GET_LEADS:
return {
...state,
leads: action.payload
};
case ADD_LEAD:
return {
...state,
leads: [...state.leads, action.payload]
}
default:
return state;
}
}
'
reducer/index.js
' import { combineReducers } from 'redux';
import leads from './leads';
export default combineReducers({
leads
});
store.js
import { createStore, applyMiddleware} from 'redux';
import { composeWithDevTools} from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
You are doing both a named export (export class ...) and a default export (export default ...) of your LeadsForm component. The default export will be wrapped by connect with your action added to props, the named export will not be wrapped by connect making your action undefined.
If the component will not work without being put through connect you should probably remove the named export to reduce confusion.
Verify that you are importing LeadsForm as a default import. This is most likely your problem.
I was moving actions from the sign in component to the context (I configured context API) everything was going fine until i had to install graphql and moved the mutations files from the sign in component to the context file. Right now is giving me the following error:
Argument of undefined passed to parser was not a valid GraphQL DocumentNode. You may need to use 'graphql-tag' or another method to convert your operation into a document
this error has came out when i used when i call the mutation at the bottom of my AppProvider component. i'm thinking that is not that way with context to use graphql mutations.
AppProvider:
// React
import React, { Fragment } from 'react';
// React apollo
import { graphql } from 'react-apollo';
import * as compose from 'lodash.flowright';
// React router
import { withRouter, Redirect } from 'react-router-dom';
// Mutation
import mutations from './mutations';
// Context
export const Context = React.createContext();
class AppProvider extends React.Component {
constructor(props) {
super(props);
this.state = {
// Login & logout states
login_credentials: {},
isLogged: false,
get_data: this.get_data,
submit: this.submit,
}
}
// Actions
// Login & Logout
get_data = async(e) => {
e.preventDefault();
const { name, value } = e.target;
const data = { [name]: value };
const newData = { ...this.state.login_credentials, ...data };
this.setState({
login_credentials: newData
});
}
submit = async(e) => {
e.preventDefault();
const { signinUser } = this.props;
const { login_credentials, isLogged } = this.state;
try {
let variables = login_credentials;
const response = await signinUser({variables});
const get_token = response.data.signinUser.token;
// setting localStorage
localStorage.setItem('token', get_token);
this.setState({
isLogged: true
});
this.props.history.push({pathname: '/home', state: {isLogged: this.state.isLogged}});
} catch(error) {
console.log(error);
}
}
// END LOGIN & LOGOUT ACTIONS
render() {
return(
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
);
}
}
export default compose(
withRouter,
graphql(mutations.signinUser, { name: 'signinUser' }),
)(AppProvider)
Sign in component where i used to use the functions and mutations:
// React
import React, { Fragment } from 'react';
// React apollo
import { graphql } from 'react-apollo';
import * as compose from 'lodash.flowright';
// React router
import { withRouter, Redirect } from 'react-router-dom';
// import mutations
import mutations from './mutations';
// React bootstrap
import { Container, Row, Form, Button } from 'react-bootstrap';
// Import Style
import './style.css';
// Component
import NavbarLayout from '../Navbar';
// Context
import Context from '../../context/context';
class LoginForm extends React.Component {
/* state = {
login_credentials: {},
isLogged: false
}
get_data = async(e) => {
e.preventDefault();
const { name, value } = e.target;
const data = { [name]: value };
const newData = { ...this.state.login_credentials, ...data };
this.setState({
login_credentials: newData
});
}
submit = async(e) => {
e.preventDefault();
const { signinUser } = this.props;
const { login_credentials, isLogged } = this.state;
try {
let variables = login_credentials;
const response = await signinUser({variables});
const get_token = response.data.signinUser.token;
// setting localStorage
localStorage.setItem('token', get_token);
this.setState({
isLogged: true
});
this.props.history.push({pathname: '/home', state: {isLogged: this.state.isLogged}});
} catch(error) {
console.log(error);
}
}
*/
render() {
return(
<Fragment>
<Context.Consumer>
{c => {
return(
<Container>
<Form className="form-container">
<h2 className="text-center pb-4">Ingreso</h2>
<Form.Group controlId="formBasicEmail">
<Form.Control name='email' onChange={e => c.get_data(e)} type="email" placeholder="Email" />
</Form.Group>
<Form.Group controlId="formBasicPassword">
<Form.Control name='password' onChange={e => c.get_data(e)} type="password" placeholder="ContraseƱa" />
</Form.Group>
<div className="text-center">
<Button className="button-login" variant="primary" onClick={e => c.submit(e)} type="submit">
Ingresa
</Button>
</div>
</Form>
</Container>
);
}}
</Context.Consumer>
</Fragment>
);
}
}
export default compose(
/* withRouter,
graphql(mutations.signinUser, { name: 'signinUser' }), */
)(LoginForm);
I made my own Django rest Framework API and I included token authentication. Everything works fine, I can get tokens and stuff, BUT, I have some trouble redirecting my user, using :
this.props.push('/');
Here is the Logic behind the authentication for the UI :
import React, {Component} from 'react';
import { connect } from 'react-redux';
import {authLogin} from '../../actions/authentication.js';
class Login extends Component {
constructor(props){
super(props);
this.state = {
email: "",
password: ""
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e){
this.setState({[e.target.name]: e.target.value});
}
handleSubmit(e){
e.preventDefault();
this.props.authLogin(this.state.email, this.state.password);
console.log(this.props.isAuthenticated);
console.log(this.props.loading);
if (this.props.isAuthenticated){
this.props.history.push('/');
}
}
render(){
return(
<div className="Login">
<h1> This is a form </h1>
<form>
<input type="text" onChange={this.handleChange} name="email"/>
<input type="password" onChange={this.handleChange} name="password"/>
<input type="submit" onClick={this.handleSubmit}/>
{ this.props.isAuthenticated &&
<p>Hello </p>
}
</form>
</div>
)
}
}
const mapStateToProps = state => {
console.log(state);
return {
token: state.authentication.token
}
}
export default connect(mapStateToProps, {authLogin})(Login);
This Login Component is inside a Container Component and this is where I want my Redux to tell " hey, here is the new token " :
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import {connect } from 'react-redux';
import AppHeader from './header/AppHeader';
import HomePageStream from './HomePageStream/HomePageStream.js';
import HeaderCategories from './header/HeaderCategories.js';
import ArticleDetails from './ArticleDetails/ArticleDetails.js';
import { Route, Switch } from 'react-router-dom';
import { transitions, positions, Provider as AlertProvider } from 'react-alert'
import AlertTemplate from 'react-alert-template-basic'
import Alerts from './Alerts/Alerts.js';
import {authStateCheck} from '../actions/authentication.js';
import { Provider } from 'react-redux'
import { store } from '../store.js';
import { BrowserRouter } from "react-router-dom";
import Login from './Authentication/Login.js';
const options = {
timeout: 2000,
position: "top center"
}
class Container extends Component {
componentDidMount(){
this.props.authStateCheck();
}
render(){
return(
<div className="Container">
<Provider store={store}>
<AlertProvider template={AlertTemplate}
{...options}
>
<AppHeader />
<Alerts />
<HeaderCategories />
<Switch>
<Route exact
path="/:category"
render={routerProps => <HomePageStream {...routerProps}/>}
/>
<Route exact
path="/article/:slug"
render={routerProps => <ArticleDetails {...routerProps}/>}
/>
<Route exact
path="/authentication/login"
render={ routerProps => <Login {...routerProps}{...this.props}/>}
/>
</Switch>
</AlertProvider>
</Provider >
</div>
)
}
}
const mapStateToProps = state => {
console.log(state.authentication);
return {
isAuthenticated: state.authentication.token !== null,
loading: state.authentication.loading
}
}
export default connect(mapStateToProps, {authStateCheck} )(Container);
Here is the Problem :
When I click on submit, isAuthenticated is false, because I only get the token AFTER handleSubmit() has been called !
Here is the code for my action :
import axios from 'axios';
import {AUTH_START, AUTH_FAIL, AUTH_SUCESS, AUTH_LOGOUT} from './type';
export const authStart = () => {
return {
type: AUTH_START,
loading: true
};
}
export const authSucess = token => {
console.log(token);
return {
type: AUTH_SUCESS,
token: token,
error: null,
loading: false
};
}
export const authFail = error => {
console.log(error)
return {
token: null,
type: AUTH_FAIL,
error: error,
loading: false
};
}
export const logout = () => {
window.localStorage.removeItem('token');
window.localStorage.removeItem('expiration_time');
return {
type: AUTH_LOGOUT
}
}
export const checkAuthTimeOut = expiration_time => dispatch => {
return setTimeout(()=> {
dispatch(logout());
}, expiration_time * 1000);
}
export const authLogin = (email, password) => dispatch => {
dispatch(authStart());
console.log("I'm authlogin ! ");
axios.post('http://127.0.0.1:8000/rest-auth/login/',{
"email": email,
"password": password
})
.then( res => {
console.log("RESPONSE !")
const token = res.data.key
const expiration_time = new Date(new Date().getTime() + 3600 * 1000);
window.localStorage.setItem('token', token);
window.localStorage.setItem('expiration_time', expiration_time);
dispatch(authSucess(token));
console.log(token);
dispatch(checkAuthTimeOut(3600));
})
.catch( err => {
console.log(err);
dispatch(authFail());
})
}
export const authStateCheck = () => dispatch => {
const expiration_time = window.localStorage.getItem('expiration_time');
const token = window.localStorage.getItem('token');
if (!token){
return dispatch(logout());
} else if ( expiration_time <= new Date()){
console.log("EXPIRATION");
console.log( expiration_time <= new Date() )
return dispatch(logout());
} else {
console.log("NEW TIMER !!!! ");
return checkAuthTimeOut((expiration_time - new Date().getTime()) / 1000)
}
}
Here is my reducer :
import {AUTH_START, AUTH_FAIL, AUTH_SUCESS, AUTH_LOGOUT} from '../../actions/type';
const initialState = {
error: null,
token: null,
loading: false
}
export function authenticationReducer(state = initialState, action){
switch (action.type) {
case AUTH_START:
return {
...state,
loading: true
}
case AUTH_SUCESS:
console.log(action.token);
console.log(action.loading);
return {
...state,
error: null,
token: action.token,
loading: false
}
case AUTH_FAIL:
return {
...state,
error: action.error
}
default:
return state
}
}
But if i hit submit one more time, it works. But i really want my user to be immediately redirected. How can I fix this ??
B
Thank you so much ;(
I would suggest removing the history.push code and let react-router handle the redirection:
import { Route, Redirect } from 'react-router'
...
<Switch>
<Route exact
path="/:category"
render={routerProps => <HomePageStream {...routerProps}/>}
/>
<Route exact
path="/article/:slug"
render={routerProps => <ArticleDetails {...routerProps}/>}
/>
<Route exact
path="/authentication/login"
render={ routerProps => (this.props.isAuthenticated ? (<Redirect to="/"/>) :(<Login {...routerProps}{...this.props}/>))}
/>
</Switch>
I'm trying to get some information about a user and store it in redux when the user logs in. I dispatch an action to the the uid which works fine. Directly after (I've commented out where in the SignIn.js code) I try to dispatch a redux thunk action that gets a list of product images from firebase. For some reason this action isn't dispatching. Not sure why? I export the function then import the function and use connect so I have access to the dispatch method. What am I missing? Thanks!
SignIn.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { fbAuth, db } from '~/config/firebase/firebaseConfig';
import { getId, getRetailerData } from '~/redux/modules/retailer';
class SignIn extends Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
push: PropTypes.func.isRequired
}
constructor(props) {
super(props);
this.state = {
email: '',
password: ''
};
this.handleEmailChange = this.handleEmailChange.bind(this);
this.handlePasswordChange = this.handlePasswordChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleEmailChange (event) {
this.setState({email: event.target.value});
}
handlePasswordChange (event) {
this.setState({password: event.target.value});
}
handleSubmit (event) {
event.preventDefault();
const email = this.state.email;
const password = this.state.password;
// Register user
fbAuth.signInWithEmailAndPassword(email, password)
.catch((error) => {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
console.log(errorMessage);
// ...
})
.then(() => {
const user = fbAuth.currentUser;
console.log(user);
this.props.dispatch(getId(user.uid));
// This action below is not being dispatched
this.props.dispatch(getRetailerData());
this.props.push(`/dashboard`);
})
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label>Email address</label>
<input
type="email"
className="form-control"
placeholder="Enter email"
onChange={this.handleEmailChange}
/>
</div>
<div className="form-group">
<label>Password</label>
<input
type="password"
className="form-control"
placeholder="Password"
onChange={this.handlePasswordChange}
/>
</div>
<button className="btn btn-default" type="submit">Continue</button>
</form>
);
}
}
export default connect()(SignIn);
My reducer
const GET_ID = 'GET_ID';
const GET_PRODUCT_IMAGES = 'GET_PRODUCT_IMAGES';
import { db, fbAuth } from '~/config/firebase/firebaseConfig';
const initialState = {
uid: '',
name: '',
productImages: [],
inventory: {}
}
export function getId (id) {
return {
type: GET_ID,
id
}
}
function getProductImages (images) {
return {
type: GET_PRODUCT_IMAGES,
images
}
}
export function getRetailerData () {
return (dispatch, getState) => {
const user = fbAuth.currentUser;
const productImages = db.ref('users/' + user.uid + '/productImages/');
productImages.on('child_added', (data) => {
console.log(data.val());
dispatch(getProductImages(data.val()))
});
}
}
export default function retailer (state = initialState, action) {
switch (action.type) {
case GET_ID :
return {
...state,
uid: action.id
}
case GET_PRODUCT_IMAGES :
return {
...state,
productImages: action.images
}
default :
return state;
}
}
Lastly I was thinking it could be something with my Index.js?
import React from 'react';
import ReactDOM from 'react-dom';
import { App } from '~/containers';
import { createStore, applyMiddleware, combineReducers, compose } from
'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import './index.css';
import * as reducers from './redux';
const store = createStore(
combineReducers(reducers),
compose(
applyMiddleware(thunk),
window.devToolsExtension ? window.devToolsExtension() : f => f
)
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
The way I usually use the connect method with actions is like this.
const mapState = state => ({
propsIWantBoundToReduxStore: store.field,
});
const mapDispatch = {
actionMethod,
//in your case: getRetailerData
};
export default connect(mapState, mapDispatch)(MyComponent);
Then you could call this.props.getRetailerData() to fire off the action.
my getRetailerData method would look something like this:
export function getRetailerData() {
return {
type: 'GET_RETAILER_DATA'
};
}
I'm a beginner with React & Redux and I'm trying to set up a very simple login form & redirection.
I'll add react-router or react-router-redux later.
I don't really understand where i have to put my 'logic code' (an ajax call and a redirection).
Here is what I've write.
index.js (entry point) :
import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import App from './containers/App'
import rootReducer from './reducers/reducers'
let store = createStore(rootReducer);
let rootElement = document.getElementById('root');
render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
containers/App.js :
import { Component } from 'react'
import { connect } from 'react-redux'
import { login } from '../actions/actions'
import LoginForm from '../components/LoginForm'
class App extends Component {
render () {
const { dispatch } = this.props;
return (
<div>
<LoginForm onSubmit={(id, pass) =>
dispatch(login(id, pass))
} />
</div>
)
}
}
const mapStateToProps = (state) => {
return {
}
};
const mapDispatchToProps = (dispatch) => {
return {
}
};
export default connect(mapStateToProps)(App);
components/LoginForm.js :
import { Component, PropTypes } from 'react'
class LoginForm extends Component {
render () {
return (
<div>
<form action="#" onSubmit={(e) => this.handleSubmit(e)}>
<input type="text" ref={node => { this.login = node }} />
<input type="password" ref={node => { this.password = node }} />
<input type="submit" value="Login" />
</form>
</div>
)
}
handleSubmit(e) {
e.preventDefault();
this.props.onSubmit(this.login.value, this.password.value);
}
}
LoginForm.propTypes = {
onSubmit: PropTypes.func.isRequired
};
export default LoginForm;
reducers/root.js :
import { combineReducers } from 'redux'
import user from './user'
const rootReducer = combineReducers({
user
});
export default rootReducer;
reducers/user.js :
import { LOGIN, BAD_LOGIN, LOGOUT } from '../actions/actions'
const initialState = {
cid: null,
username: '',
logo: ''
};
const user = (state = initialState, action) => {
switch (action.type) {
case LOGIN:
const api = new loginApi; //simple version
api.login(action.login, action.password)
.done(res => {
//Right here ?
})
.fail(err => console.error(err));
return state;
case LOGOUT:
//...
return state;
default:
return state;
}
};
export default user;
actions/actions.js :
export const LOGIN = 'LOGIN';
export const LOGOUT = 'LOGOUT';
export function login(login, password) {
return {
type: LOGIN,
login,
password
}
}
following this link : http://redux.js.org/docs/advanced/AsyncActions.html
I hesitate between write my login stuff inside the reducer (but I think reducer's purpose is just to reduce the state object) or to create multiple actions with one 'main' action which call REQUEST_LOGIN and LOGIN_SUCCES / LOGIN_FAILURE for example.
Thanks.
You are correct, reducers are only for mapping data to the state. Create your async logic in the action creator. The key is to use a store enhancer to make async actions possible.
redux-thunk
redux-promise
A tutorial on async redux can be found in the redux documentation.