Async data loading in Redux-form - javascript

I am working with React and Redux, and I'm using Redux-form for my forms.
I am trying to load some initial data from database. In the docs it is recommended to use
{
load: loadAccount
}
in connect component, but I'm struggling to set it properly.
I can load initial data in a different way: adding a dispatch action to connect component and call it from componentDidMount, but I would like to understand how to set { load: loadAccount }.
This is my actions file —I show only the action that matters—:
const actions = {
requestProject: () => {
return {
type: C.LOAD_PROJECT_STARTED,
};
},
receiveProject: (id, data) => {
return {
type: C.LOAD_PROJECT_SUCCESS,
id,
data,
};
},
loadProject: (id = '') => {
const url = '/api/projects/';
const encodedURI = isBrowser
? encodeURI(window.location.origin + url + id)
: encodeURI('http://localhost:' + config.SERVER + url + id);
return isBrowser
? function(dispatch) {
dispatch(actions.requestProject());
return fetch(encodedURI)
.then(
(response) => {
return response.json();
},
(error) => {
return console.log('An error occurred.', error);
}
)
.then((data) => {
return dispatch(actions.receiveProject(id, data));
});
}
: fetch(encodedURI)
.then((response) => {
return response.json();
})
.then((data) => {
return data;
})
.catch((error) => {
return Promise.reject(Error(error.message));
});
},
};
export default actions;
Then my reducers —again, only one—:
export const Project = (state = {}, action) => {
switch (action.type) {
case C.LOAD_PROJECT_STARTED:
console.log('LOAD_PROJECT_STARTED');
return Object.assign({}, state, {
isFetching: true,
});
case C.LOAD_PROJECT_SUCCESS:
return Object.assign({}, state, {
...action.data.Project,
isFetching: false,
});
case C.SUBMIT_PROJECT_FORM_STARTED:
return Object.assign({}, state, {
isFetching: true,
});
case C.SUBMIT_PROJECT_FORM_SUCCESS:
return Object.assign({}, state, {
...action.data.Project,
isFetching: false,
});
default:
return state;
}
};
export const form = formReducer;
This is the connect ProjectForm component for the form:
import { connect } from 'react-redux';
import ProjectFormUi from './ProjectFormUi';
import actions from '../../redux/actions';
import { load as loadAccount } from './account'
export const ProjectForm = connect(
(state) => {
return {
initialValues: state.Project,
};
},
(dispatch) => {
return {
loadProject(id) {
dispatch(actions.loadProject(id));
},
onSubmit(data) {
dispatch(actions.sendingProject(data));
},
};
},
{
load: loadAccount,
}
)(ProjectFormUi);
export default ProjectForm;
And finally, the actual form component, ProjectFormUi.
import React from 'react';
import { Field, reduxForm } from 'redux-form';
class ProjectFormUi extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
componentDidMount() {
this.props.loadProject(1);
}
static getDerivedStateFromProps(newProps, prevState) {
return newProps != prevState ? newProps : null;
}
componentDidUpdate(prevProps) {
if (this.props.Project !== prevProps.Project) {
this.setState({
isFetching: this.props.Project.isFetching,
});
}
}
render() {
const { handleSubmit, load, pristine, reset, submitting } = this.props;
return (
<form onSubmit={handleSubmit}>
<div>
<label>First Name</label>
<div>
<Field name="title" component="input" type="text" placeholder="First Name" />
</div>
</div>
<div>
<button type="submit" disabled={submitting}>
Submit
</button>
<button type="button" disabled={pristine || submitting} onClick={reset}>
Undo Changes
</button>
</div>
</form>
);
}
}
export default reduxForm({
form: 'ProjectForm',
enableReinitialize: true,
})(ProjectFormUi);
As I said, currently I'm loading data in connect ProjectForm component with:
loadProject(id) {
dispatch(actions.loadProject(id));
},
which is called from componentDidMount in ProjectFormUi component.
But I would like to understand how to load data as in the docs, setting
{
load: loadAccount
}
in connect ProjectForm component.

Related

Why changes from local state don't go into global state?

To-Do-List
When I try to edit my created task, I see some modifications, but only in local State. When I look at the data of the global state, nothing change, the data remains the same as after creating the tasks object.
It is also interesting to note that when case EDIT_TASK has worked , action.id = values from Input, and action.task = undefined
P.S: Put all the component code below, maybe there was a mistake somewhere.
P.S: Sorry for ENG
Component's code
import React from 'react'
import s from "./../../App.module.css";
class Item extends React.Component {
state = {
statusChange: false,
task: this.props.task
}
activeStatusChange = () => {
this.setState( {
statusChange: true
}
);
}
deActivateStatusChange = () => {
this.setState( {
statusChange: false
}
);
this.props.editTask(this.props.task)
}
onStatusChange = (e) => {
this.setState({
task: e.target.value
})
}
render(){
return (
<div className={s.item}>
<span onClick={this.props.editStatus} className={s.statusTask}>
{this.props.status ? <img src="https://img.icons8.com/doodle/48/000000/checkmark.png"/>
: <img src="https://img.icons8.com/emoji/48/000000/red-circle-emoji.png"/>}
</span>
{ this.state.statusChange
? <input onChange={this.onStatusChange} autoFocus={true} onBlur={this.deActivateStatusChange} value={this.state.task} />
: <span className={this.props.status === true ? s.task : s.taskFalse} onClick={this.activeStatusChange}> {this.state.task} </span>}
<span onClick={this.props.deleteTask} className={s.close}><img src="https://img.icons8.com/color/48/000000/close-window.png"/></span>
</div>
)
}
}
export default Item;
Reducer's code
import React from 'react'
import shortid from 'shortid';
const ADD_TASK = 'ADD_TASK'
const EDIT_TASK = 'EDIT_TASK'
const initialState = {
tasks: []
};
const mainReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_TASK: {
return {
...state,
tasks: [{
id: shortid.generate(),
task: action.task,
status: false
}, ...state.tasks]
}
}
case EDIT_TASK: {
return {
...state,
tasks: state.tasks.filter((t) => t.id === action.id ? {...t, task: action.newTask} : t)
}
}
default:
return state
}
}
//window.store.getState().mainReducer.tasks
export const addTask = task => ({type: 'ADD_TASK', task});
export const editTask = (id,newTask) => ({type: 'EDIT_TASK', id, newTask})
export default mainReducer;
Parent's component:
import React from "react";
import s from "./../../App.module.css";
import CurrentTasks from "../current-tasks";
import FilterButtonTasks from "../filter-button-tasks";
import ListTasks from "../tasks-list";
class SetForm extends React.Component {
constructor(props) {
super(props);
this.state = {
text: ''
}
}
onInputChange = event => {
this.setState({
[event.target.name]: event.target.value
})
}
handleSubmit = event => {
event.preventDefault();
if(this.state.text === '') {
return undefined
}
this.props.addTask(this.state.text)
this.setState({
text: ''
})
}
filterTasks = (tasks, activeFilter) => {
switch (activeFilter) {
case 'done': {
return tasks.filter(task => task.status);
}
case 'active': {
return tasks.filter(task => !task.status)
}
default:
return tasks;
}
}
render() {
const currentTasks = this.filterTasks(this.props.tasks, this.props.filter);
return (
<div>
<form onSubmit={this.handleSubmit}>
<div>
<input name={"text"} onChange={this.onInputChange} value={this.state.text}placeholder={"Set your task"} className={s.setTask}/>
<button onClick={this.handleSubmit} className={s.add}>ADD</button>
<button onClick={this.props.removeAllTasks} className={s.clearAll}>Clear</button>
</div>
</form>
<CurrentTasks tasks={this.props.tasks}/>
<ListTasks currentTasks={currentTasks} editStatus={this.props.editStatus} deleteTask={this.props.deleteTask} editTask={this.props.editTask}/>
<FilterButtonTasks currentTasks={currentTasks} changeFilter={this.props.changeFilter} removeAllDone={this.props.removeAllDone}/>
</div>
)
}
}
export default SetForm;
one more:
import React from 'react'
import Item from './SetItem/item'
const ListTasks = ({currentTasks,editStatus,deleteTask,editTask}) => {
return (
currentTasks.map(t => (<Item editStatus={() => editStatus(t.id)}
deleteTask={() => deleteTask(t.id)}
key={t.id} task={t.task} status={t.status} editTask={editTask}/>))
)
}
export default ListTasks;
Since, you are only updating the local state onStatusChange the state does not get updated in global state. So on deActivateStatusChange you need to call this.props.editTask with updated state, that is this.state.task
deActivateStatusChange = () => {
this.setState({
statusChange: false
});
this.props.editTask(this.state.task); // change is here
};
The problem is in your EDIT_TASK reducer:
Change
state.tasks.filter((t) => t.id === action.id ? {...t, task: action.newTask} : t)
To
state.tasks.map((t) => t.id === action.id ? {...t, task: action.newTask} : t)
map will update the object, not filter
Code should be:
case EDIT_TASK: {
return {
...state,
tasks: state.tasks.map((t) => t.id === action.id ? {...t, task: action.newTask} : t)
}
}
Also it seems like you are not passing id and newTask to editTask action:
const ListTasks = ({ currentTasks, editStatus, deleteTask, editTask }) => {
return currentTasks.map(t => (
<Item
editStatus={() => editStatus(t.id)}
deleteTask={() => deleteTask(t.id)}
key={t.id}
task={t.task}
status={t.status}
editTask={(newTask) => editTask(t.id, newTask)} // change current code to this
/>
));
};

Why is one of my child components able to get handleClick() but the others are not?

PROBLEM
I am swapping components based on state in Dashboard(parent) component and passing props to all of them. When I log in using wholesaler the app is running without problems but when i log in with retailer account the app return
TypeError: this.props.handleClick is not a function
when i click on a button-handleClick() -> switch components through handleClick which changes state
My components are almost identical and i have no idea where this is coming from.
Thank you in advance! :)
Files:
Dashboard.js
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
import { Retailers } from './_components/Retailers'
import { Locations } from './_components/Locations'
import { Products } from './_components/Products'
class Dashboard extends Component {
constructor(props) {
super(props)
this.state = {
chosenRetailerId: null,
chosenLocationId: null,
mountComponent: ''
}
this.handleClick = this.handleClick.bind(this)
}
componentDidMount() {
switch (this.props.user.type) {
case 'wholesaler':
this.setState({ mountComponent: "retailers" })
break;
case 'retailer':
this.setState({ mountComponent: "locations" })
break;
case 'location':
this.setState({ mountComponent: "products" })
break;
default:
break;
}
}
handleClick(id, shouldMountComponent) {
switch (shouldMountComponent) {
case 'locations':
this.setState({
mountComponent: shouldMountComponent,
chosenRetailerId: id
})
break;
case 'products':
this.setState({
mountComponent: shouldMountComponent,
chosenLocationId: id
})
break;
default:
break;
}
}
render() {
const { user } = this.props
const { chosenLocationId, chosenRetailerId, mountComponent } = this.state
return (
<div className="dashboard">
{user.type === 'wholesaler' &&
<div className="wholesaler">
<h1>Wholesaler</h1>
<h3>{user._id}</h3>
{this.state.mountComponent === 'retailers' &&
<Retailers mountComponent={mountComponent} handleClick={this.handleClick} />
}
{this.state.mountComponent === 'locations' &&
<Locations retailerId={chosenRetailerId} locationId={chosenLocationId} mountComponent={mountComponent} handleClick={this.handleClick} />
}
{this.state.mountedComponent === 'products' &&
<Products locationId={chosenLocationId} mountComponent={mountComponent}/>
}
</div>
}
{user.type === 'retailer' &&
<div className="retailers">
<h1>Retailer {user._id}</h1>
<Locations locationId={chosenLocationId}/>
</div>
}
{user.type === 'location' &&
<div className="locations">
<h1>Location {user._id}</h1>
<Products />
</div>
}
<p>You're logged in with React & JWT!!</p>
<p>
<Link to="/login">Logout</Link>
</p>
</div>
)
}
}
function mapStateToProps(state) {
const { authentication } = state
const { user } = authentication
return {
user
}
}
const connectedDashboard = connect(mapStateToProps)(Dashboard)
export { connectedDashboard as Dashboard }
Retailers.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
class Retailers extends Component {
state = {
retailers: []
}
componentDidMount() {
const { user } = this.props
const requestOptions = {
method: 'GET',
headers: { 'Authorization': 'Bearer ' + user.token }
}
fetch(`http://localhost:4000/retailers/by-wholesaler/${user._id}`, requestOptions)
.then(result => result.json())
.then(result => {
this.setState({
retailers: result
})
})
}
render() {
const { retailers } = this.state
return (
<div className="retailers">
{retailers.map((retailer, index) =>
<button key={index} onClick={() => this.props.handleClick(retailer._id, 'locations')}>{retailer.name}</button>
)}
</div>
)
}
}
function mapStateToProps(state) {
const { authentication } = state
const { user } = authentication
return { user }
}
const connectedRetailers = connect(mapStateToProps)(Retailers)
export { connectedRetailers as Retailers }
Locations.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
class Locations extends Component {
state = {
locations: []
}
componentDidMount() {
const { user } = this.props
const requestOptions = {
method: 'GET',
headers: { 'Authorization': 'Bearer ' + user.token }
}
const retailerId = (user.type === 'retailer') ? user._id : this.props.retailerId
console.log(retailerId)
fetch(`http://localhost:4000/locations/by-retailer/${retailerId}`, requestOptions)
.then(result => result.json())
.then(result => {
this.setState({
locations: result
})
})
}
render() {
const { locations } = this.state
return (
<div className="locations">
{locations.map((location, index) =>
<button key={index} onClick={() => this.props.handleClick(location._id, 'products')}>{location.name}</button>
)}
</div>
)
}
}
function mapStateToProps(state) {
const { authentication } = state
const { user } = authentication
return { user }
}
const connectedLocations = connect(mapStateToProps)(Locations)
export { connectedLocations as Locations }
You have to pass handleCLick to location as a prop. You do that in you wholesaler case (passing it to the retailer component), but not when using the Locations component
You didn't pass handleClick as a prop :)
<Retailers handleClick={this.handleClick} />
<Locations handleClick={this.handleClick} />
So prop is undefined and you can't call it as a function.
Check the locations.map function in your Locations.js.
Your are passing a so called thisArg to the map function, so it is no longer using the right context.
This should work:
<div className="locations">
{locations.map((location, index) =>
<button key={index} onClick={() =>
this.props.handleClick(location._id, 'products')}>
{location.name}
</button>
)}
</div>
Also, think about using the uuid package for your iteration keys. Now you are using an index and that will not be unique if you do so in another iteration too (not the case yet so).

Fetching data from server through Redux (Action & Reducer) fails to store the data in the State

I'm trying to fetch data through Redux (with actions & reducers) and store it in a ReactTable
Here is the Table :
// MisleadLeadsTable
import React from "react";
import "react-table-v6/react-table.css";
import ReactTable from "react-table-v6";
import { connect } from "react-redux";
import {
getLeadsNotValid,
updateSpecificNotValidLead
} from "../../actions/leads";
import Spinner from "../layout/Spinner";
class MisleadLeadsTable extends React.Component {
constructor(props) {
super();
const { getLeadsNotValid } = props;
// Going to get data from the Server
// Call the Action and use the Reducer
getLeadsNotValid();
// Later put the data in the state
this.state = {
data: []
};
this.renderEditable = this.renderEditable.bind(this);
}
componentDidMount() {
// TODO
const { leadsNotValid } = this.props;
this.setState({
data: leadsNotValid
});
}
// Edit the cells
renderEditable(cellInfo) {
return (
<div
style={{ backgroundColor: "#fafafa" }}
contentEditable
suppressContentEditableWarning
onBlur={e => {
const data = [...this.state.data];
data[cellInfo.index][cellInfo.column.id] = e.target.innerHTML;
this.setState({ data });
}}
dangerouslySetInnerHTML={{
__html: this.state.data[cellInfo.index][cellInfo.column.id]
}}
/>
);
}
render() {
// loading data or not
const { loadingData } = this.props;
// This "data" should hold the fetched data from the server
const { data } = this.state;
return (
<div>
{loadingData ? (
<Spinner />
) : (
<div>
<ReactTable
data={data}
columns={[
{
Header: "Business Name",
accessor: "BusinessName"
// Cell: this.renderEditable
}
]}
defaultPageSize={10}
className="-striped -highlight"
/>
<br />
</div>
)}
</div>
);
}
}
const mapStateToProps = state => ({
loadingData: state.leadReducer.loadingData,
leadsNotValid: state.leadReducer.leadsNotValid
});
const mapDispatchToProps = { getLeadsNotValid, updateSpecificNotValidLead };
export default connect(mapStateToProps, mapDispatchToProps)(MisleadLeadsTable);
However when I try to store the data in the State (in componentDidMount) it always comes back empty , and when the table is being rendered it gets an empty array.
It is crucial to store the data in the State because I'm trying to implement an editable table.
The data is stored in leadsNotValid , and if I do :
<ReactTable
data={leadsNotValid} // Notice !! Changed this
columns={[
{
Header: "Business Name",
accessor: "BusinessName"
// Cell: this.renderEditable
}
]}
defaultPageSize={10}
className="-striped -highlight"
/>
Then the data is presented successfully to the user , however it's not in the State of the component.
How can I put the leadsNotValid in the State using setState ?
Here are the Action & Reducer if it's needed (THEY WORK GREAT !) :
Action :
import axios from "axios";
import {
REQUEST_LEADS_NOT_VALID,
REQUEST_LEADS_NOT_VALID_SUCCESS,
UPDATED_SUCCESSFULLY_A_NOT_VALID_LEAD_THAT_NOW_IS_VALID,
UPDATE_A_SINGLE_NOT_VALID_LEAD
} from "./types";
export const updateSpecificNotValidLead = updatedLead => async dispatch => {
dispatch({
type: UPDATE_A_SINGLE_NOT_VALID_LEAD
});
const config = {
headers: {
"Content-Type": "application/json"
}
};
const body = JSON.stringify({ updatedLead });
const res = await axios.post(
".......API/Something1/....",
body,
config
);
if (res !== null && res.data !== null) {
dispatch({
type: UPDATED_SUCCESSFULLY_A_NOT_VALID_LEAD_THAT_NOW_IS_VALID
});
}
};
export const getLeadsNotValid = () => async dispatch => {
dispatch({
type: REQUEST_LEADS_NOT_VALID
});
const res = await axios.get(".......API/Something2/....");
if (res !== null && res.data !== null) {
dispatch({
type: REQUEST_LEADS_NOT_VALID_SUCCESS,
payload: res.data
});
}
};
Reducer :
import {
GET_MAIN_LEADS_SUCCESS,
REQUEST_MAIN_LEADS,
RELOAD_DATA_MAIN_LEADS_TABLE,
REQUEST_LEADS_NOT_VALID,
REQUEST_LEADS_NOT_VALID_SUCCESS,
UPDATE_A_SINGLE_NOT_VALID_LEAD,
UPDATED_SUCCESSFULLY_A_NOT_VALID_LEAD_THAT_NOW_IS_VALID
} from "../actions/types";
const initialState = {
mainLeadsClients: [],
loadingData: null, // general loader
reloadMainLeadTable: 0,
reloadMisleadTable: 0,
leadsNotValid: []
};
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case REQUEST_LEADS_NOT_VALID:
return {
...state,
loadingData: true
};
case REQUEST_LEADS_NOT_VALID_SUCCESS:
return {
...state,
loadingData: false,
leadsNotValid: payload
};
case UPDATE_A_SINGLE_NOT_VALID_LEAD:
return {
...state,
loadingData: true
};
case UPDATED_SUCCESSFULLY_A_NOT_VALID_LEAD_THAT_NOW_IS_VALID:
return {
...state,
reloadMisleadTable: state.reloadMisleadTable + 1,
loadingData: false
};
// ... more
default:
return state;
}
}
You may have to write super(props) instead of super() in order to access props in the constructor.

How to show props from reducers in component

I have a problem when want to show
const mapStateToProps = state => {
return {
loading: state.auth.loading,
error: state.auth.error,
userId: state.auth.userId,
tokenId: state.auth.token
}
}
this in my function
register = (event) => {
event.preventDefault()
this.props.onAuth( this.state.email, this.state.password, this.state.isSignup );
localStorage.setItem('token', this.props.tokenId);
localStorage.setItem('userId', this.props.userId);
}
I see token and userId after the second click. But I can't see after the first click. What I need more to show immediately?
This is my auth.js reducers
import * as actionTypes from '../actions/actionsTypes';
import { updateObject } from '../utility';
const initialState = {
token: null,
userId: null,
error: null,
loading: false
};
const authStart = ( state, action ) => {
return updateObject( state, { error: null, loading: true } );
};
const authSuccess = (state, action) => {
return updateObject( state, {
token: action.idToken,
userId: action.userId,
error: null,
loading: false
} );
};
const authFail = (state, action) => {
return updateObject( state, {
error: action.error,
loading: false
});
}
const reducer = ( state = initialState, action ) => {
switch ( action.type ) {
case actionTypes.AUTH_START: return authStart(state, action);
case actionTypes.AUTH_SUCCESS: return authSuccess(state, action);
case actionTypes.AUTH_FAIL: return authFail(state, action);
default:
return state;
}
};
export default reducer;
But, after the first click, I got token in my render function.
{this.props.tokenId}
Could you please help me? I think I need to use async/await. But I am not sure.
Here you go Header.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import * as actions from '../../store/actions/index'
import PropTypes from 'prop-types'
import './header.css'
class Header extends Component {
constructor(props) {
super(props)
this.state = {
email: '',
password: '',
isSignup: true,
token: false
}
this.handleChange = this.handleChange.bind(this);
}
handleChange (evt) {
this.setState({ [evt.target.name]: evt.target.value });
}
switchAuthModeHandler = (event) => {
event.preventDefault()
this.setState(prevState => {
return {
isSignup: !prevState.isSignup
}
})
}
register = (event) => {
event.preventDefault()
this.props.onAuth( this.state.email, this.state.password, this.state.isSignup );
localStorage.setItem('token', this.props.tokenId);
localStorage.setItem('userId', this.props.userId);
}
render() {
let regBtn = ''
if (this.state.isSignup) {
regBtn = 'Register'
}
else {
regBtn = 'Login'
}
let login = null
if(!this.props.tokenId) {
login = (
<div className="login">
<form onSubmit={this.register}>
<input type="email" placeholder="email" name="email" onChange={this.handleChange} />
<input type="password" placeholder="password" name="password" onChange={this.handleChange} />
<button>{regBtn}</button>
</form>
<div onClick={this.switchAuthModeHandler} className="switch">Switch to {this.state.isSignup ? 'Login' : 'Register'}</div>
</div>
)
}
else {
login = (
<div>
<p>Hello: {this.props.userId}</p>
<button>Logout</button>
</div>
)
}
if(this.props.loading) {
login = <div>Loading...</div>
}
return (
<div>
<div className="header-inner">
{this.props.tokenId}
{login}
<img src="http://civcic.com/assets/images/header-bg.jpg" alt="img" />
<div className="header-content">
<h2>React.JS DEVELOPER</h2>
<a className="knowmore-btn" href="https://www.upwork.com/freelancers/~01f507600be26cc2a3" rel="noopener noreferrer" target="_blank">Upwork profile</a><br />
<a className="knowmore-btn" href="https://www.linkedin.com/in/boris-civcic-37244378/" rel="noopener noreferrer" target="_blank">Linkedin</a><br />
<a className="knowmore-btn" href="https://github.com/fixman93" rel="noopener noreferrer" target="_blank">GitHub</a>
</div>
</div>
</div>
)
}
}
Header.defaultProps = {
tokenId: ''
}
Header.propTypes = {
tokenId: PropTypes.string
}
const mapStateToProps = state => {
return {
loading: state.auth.loading,
error: state.auth.error,
userId: state.auth.userId,
tokenId: state.auth.token
}
}
const mapDispatchToProps = dispatch => {
return {
onAuth: ( email, password, isSignup) => dispatch( actions.auth(email, password, isSignup))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Header)
import axios from 'axios';
import * as actionTypes from './actionsTypes';
export const authStart = () => {
return {
type: actionTypes.AUTH_START
}
}
export const authSuccess = (token, userId) => {
return {
type: actionTypes.AUTH_SUCCESS,
idToken: token,
userId: userId
}
}
export const authFail = (error) => {
return {
type: actionTypes.AUTH_FAIL,
error: error
};
};
export const auth = (email, password, isSignup) => {
return dispatch => {
dispatch(authStart());
const authData = {
email: email,
password: password,
fullName: 'Boris Civcic',
role: 'admin',
returnSecureToken: true
};
let url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=AIzaSyC5nW8-XOJADEvU7Mi7sgmhUNhHfRxXNQI';
if (!isSignup) {
url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=AIzaSyC5nW8-XOJADEvU7Mi7sgmhUNhHfRxXNQI';
}
axios.post(url, authData)
.then(response => {
console.log(response);
dispatch(authSuccess(response.data.idToken, response.data.localId));
// dispatch(checkAuthTime(response.data.expiresIn));
})
.catch(err => {
dispatch(authFail(err.response.data.error));
})
};
};
this is auth.js action
and this is utility
export const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
};
};
In your register handler, onAuth is an asynchronous action but you've populated localStorage immediately. You should wait onAuth returns and then set your localStorage items.
first return a promise from your thunk ( simply by adding return before axios ):
...
return axios.post(url, authData)
.then(response => {
console.log(response);
dispatch(authSuccess(response.data.idToken, response.data.localId));
// dispatch(checkAuthTime(response.data.expiresIn));
})
.catch(err => {
dispatch(authFail(err.response.data.error));
})
...
Then set your localStorage items like this:
register = (event) => {
event.preventDefault();
this.props.onAuth( this.state.email, this.state.password, this.state.isSignup )
.then(() => {
localStorage.setItem('token', this.props.tokenId);
localStorage.setItem('userId', this.props.userId);
});
}

Dispatching an in-progress action in Redux

I have a signup form, which posts data asynchronously to an API and then does some stuff based on the response. I am dispatching a "signup_in_progress" action as soon as the function is called, with a payload of "true", and update my state based on that, then dispatching this action with a payload of "false" when the promise is resolved.
I can see that the dispatch happens as intended by putting a console.log() statement in the reducer.
What I would like to happen is for a spinner to appear instead of the signup form when the signup_in_progress piece of state is "true." But that's not happening. Any idea what I'm missing?
My action:
export function signUpUser(props) {
return function(dispatch) {
dispatch({ type: SIGNUP_IN_PROGRESS, payload: true });
axios
.post(`${ROOT_URL}/signup`, props)
.then(() => {
dispatch({ type: SIGNUP_IN_PROGRESS, payload: false });
dispatch({ type: SIGNUP_SUCCESS });
browserHistory.push(`/signup/verify-email?email=${props.email}`);
})
.catch(response => {
dispatch({ type: SIGNUP_IN_PROGRESS, payload: false });
dispatch(authError(SIGNUP_FAILURE, response.response.data.error));
});
};
}
The relevant part of my reducer:
import {
...
SIGNUP_IN_PROGRESS,
SIGNUP_SUCCESS,
SIGNUP_FAILURE...
} from '../actions/types';
export default function(state = {}, action) {
switch (action.type) {
...
case SIGNUP_IN_PROGRESS:
return { ...state, signing_up: action.payload };
...
My connected component:
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import * as actions from '../../actions';
import SignupFirstPage from './signupComponents/signupFirstPage';
import SignupSecondPage from './signupComponents/signupSecondPage';
import SignupThirdPage from './signupComponents/SignupThirdPage';
import Spinner from 'react-spinkit';
class SignUp extends Component {
constructor(props) {
super(props);
this.nextPage = this.nextPage.bind(this);
this.previousPage = this.previousPage.bind(this);
this.state = {
page: 1
};
this.handleFormSubmit = this.handleFormSubmit.bind(this);
}
nextPage() {
this.setState({ page: this.state.page + 1 });
}
previousPage() {
this.setState({ page: this.state.page - 1 });
}
handleFormSubmit(props) {
this.props.signUpUser(props);
}
render() {
const { handleSubmit } = this.props;
const { page } = this.state;
if (this.props.signingUp) {
return (
<div className="dashboard loading">
<Spinner name="chasing-dots" />
</div>
);
}
return (
<div>
{page === 1 && <SignupFirstPage onSubmit={this.nextPage} />}
{page === 2 && (
<SignupSecondPage
previousPage={this.previousPage}
onSubmit={this.nextPage}
/>
)}
{page === 3 && (
<SignupThirdPage
previousPage={this.previousPage}
onSubmit={values => this.props.signUpUser(values)}
/>
)}
<div>
{this.props.errorMessage &&
this.props.errorMessage.signup && (
<div className="error-container">
Oops! {this.props.errorMessage.signup}
</div>
)}
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
singingUp: state.auth.signing_up,
errorMessage: state.auth.error
};
}
SignUp = reduxForm({ form: 'wizard' })(SignUp);
export default connect(mapStateToProps, actions)(SignUp);
You just have a typo in your code:
function mapStateToProps(state) {
return {
singingUp: state.auth.signing_up, // TYPO!!
errorMessage: state.auth.error
};
}
should be changed to
function mapStateToProps(state) {
return {
signingUp: state.auth.signing_up,
errorMessage: state.auth.error
};
}
I recommend using redux-pack which allows you to handle three phases of the request in the reducer: start, success and error.
Then you can use the start handler to set the boolean to true, and on success set it to false.

Categories

Resources