I asked similar question earlier, but didn't get much back. I have two modals for user auth: join and login. Each modal has a link to the other one. Displayed login errors persist when you click on the "sign up" and switch to the join modal and vise versa. I tried to set the state.errors to empty array, but the errors still persist. I changed handleSwitch to callback. The errors array still has length. I tried using switched as part of the state, resetting it to true in handleSwitch and ternary, no result either. Can anybody suggest an alternative solution.
import React from 'react';
class Login extends React.Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
errors: [],
switched: false
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleSwitch = this.handleSwitch.bind(this);
this.mapErrors = this.mapErrors.bind(this);
this.handleErrors = this.handleErrors.bind(this);
}
componentDidMount() {
this.setState({ errors: this.props.errors})
}
componentDidUpdate(prev) {
if (prev.errors.length !== this.props.errors.length) {
this.setState( {errors: this.props.errors} )
}
}
handleInput(type) {
return (err) => {
this.setState({ [type]: err.currentTarget.value })
};
}
handleSubmit(event) {
event.preventDefault();
const user = Object.assign({}, this.state);
this.props.processForm(user)
// .then(() => this.props.history.push('/users')); //change to /videos later
}
handleSwitch() {
// debugger
this.setState({ errors: [] }, function () {
this.props.openModal('signup')
});
// debugger
}
mapErrors() {
if (this.state.errors.length) {
return this.state.errors.map((error, i) => {
return <p key={i}>{error}</p>
})
}
}
handleErrors() {
debugger
if (!this.state.switched) {
return <div className="errors">{this.mapErrors}</div>
} else {
return null;
}
};
render() {
console.log(this.state.errors)
return (
<div className="login-form">
<div>
<h2 className="login-header">Log in to Foxeo</h2>
</div>
<form>
<input className="login-email"
type="text"
value={this.state.email}
placeholder="Email address"
onChange={this.handleInput('email')}
/>
<input className="login-password"
type="password"
value={this.state.password}
placeholder="Password"
onChange={this.handleInput('password')}
/>
<div className="errors">{this.mapErrors()}</div>
{/* { this.state.switched ?
<div className="errors">{this.handleErrors()}</div> :
<div className="errors">{this.mapErrors()}</div>
} */}
<button className="login-button" onClick={this.handleSubmit}>Log in with email</button>
<div className="login-footer">Don't have an account?
{/* <button className="login-form-btn" onClick={() => this.props.openModal('signup')}>Join</button> */}
<button className="login-form-btn" onClick={ this.handleSwitch}> Join</button>
</div>
</form>
</div>
);
}
};
export default Login;
I suggest getting the new errors from the props instead of from state:
mapErrors() {
if (this.props.errors.length) {
return this.props.errors.map((error, i) => {
return <p key={i}>{error}</p>
})
Dispatching resetErrors action solved the issue. The handleSwitch method is quite simple:
handleSwitch() {
this.props.resetErrors()
this.props.openModal('signup')
}
session actions:
import * as apiUtil from '../util/session_api_util';
export const RECEIVE_CURRENT_USER = 'RECEIVE_CURRENT_USER';
export const LOGOUT_CURRENT_USER = 'LOGOUT_CURRENT_USER';
export const RECEIVE_ERRORS = 'RECEIVE_ERRORS';
export const CLEAR_ERRORS = 'CLEAR_ERRORS';
const receiveErrors = (errors) => ({
type: RECEIVE_ERRORS,
errors
})
const clearErrors = () => ({
type: CLEAR_ERRORS,
errors: []
})
const receiveCurrentUser = (user) => ({
type: RECEIVE_CURRENT_USER,
user
});
const logoutCurrentUser = () => ({
type: LOGOUT_CURRENT_USER
});
export const signup = user => dispatch => (
apiUtil.signup(user).then(user => (
dispatch(receiveCurrentUser(user))
), err => (
dispatch(receiveErrors(err.responseJSON))
))
);
export const login = user => dispatch => {
return apiUtil.login(user).then(user => {
dispatch(receiveCurrentUser(user))
}, err => (
dispatch(receiveErrors(err.responseJSON))
))
};
export const logout = () => dispatch => apiUtil.logout()
.then(() => dispatch(logoutCurrentUser()));
export const resetErrors = () => dispatch(clearErrors());
session errors reducer:
import { RECEIVE_ERRORS, RECEIVE_CURRENT_USER, CLEAR_ERRORS } from '../actions/session_actions';
const sessionErrorsReducer = (state = [], action) => {
Object.freeze(state);
switch (action.type) {
case RECEIVE_ERRORS:
return action.errors;
case CLEAR_ERRORS:
return [];
case RECEIVE_CURRENT_USER:
return [];
default:
return state;
}
};
export default sessionErrorsReducer;
Related
I have a react component that fetches from API with createAsyncThunk and cannot understand a behaviour that happens on Mount in this part:
if(isLoading===true) return <div>Loading...</div>
if(failedToLoad===true) return <div>Error loading feed</div>
if(feedResponse) return(
<div className={styles.feed}>
{feedResponse.map(({id}) => {
return(
<Link to={`/thread=${id}`} key={id}>
<Thread key={id} id={id}/>
</Link>
)
})}
</div>
)
If I remove if(feedResponse) next to the return(...) the component will crash because it will try to render before feedResponse status has data. Why isn't that covered by the first two IFs?
if(isLoading===true) return <div>Loading...</div>
if(failedToLoad===true) return <div>Error loading feed</div>
It is my understanding that there is no scenario where we have an inLoading = false & feedResponse = null
Here is the complete code if needed:
Feed.js
export const Feed = () => {
const dispatch = useDispatch()
const feedResponse = useSelector(selectFeedResponse)
const isLoading = useSelector(isLoadingFeed)
const failedToLoad = useSelector(failedToLoadFeed)
const location = useLocation()
useUpdateEffect(() => {
dispatch(SearchThunk(searchTerm))
}, searchTerm)
useUpdateEffect(() => {
dispatch(homeThunk(location.pathname+'.json'))
}, location)
if(isLoading===true) return <div>Loading...</div>
if(failedToLoad===true) return <div>Error loading feed</div>
if(feedResponse) return(
<div className={styles.feed}>
{feedResponse.map(({id}) => {
return(
<Link to={`/thread=${id}`} key={id}>
<Thread key={id} id={id}/>
</Link>
)
})}
</div>
)
}
feedSlice.js
export const homeThunk = createAsyncThunk(
'feed/homeThunk',
async (homePath) => {
const response = await fetch(`https://www.reddit.com${homePath}`)
const json = await response.json()
const threads = json.data.children.map(thread => {
return {
id: thread.data.id,
subreddit: thread.data.subreddit,
title: thread.data.title,
author: thread.data.author,
thumbnail: thread.data.thumbnail,
created: thread.data.created,
score: thread.data.score,
num_comments: thread.data.num_comments
}
})
return threads
}
)
export const feedSlice = createSlice({
name: 'feed',
initialState: {
feedResponse: '',
isLoadingFeed: false,
failedToLoadFeed: false
},
extraReducers: (builder) => {
builder
.addCase(homeThunk.pending, (state) => {
state.isLoadingFeed = true
state.failedToLoadFeed = false
})
.addCase(homeThunk.fulfilled, (state, action) => {
state.isLoadingFeed = false
state.failedToLoadFeed = false
state.feedResponse = action.payload
})
.addCase(homeThunk.rejected, (state) => {
state.isLoadingFeed = false
state.failedToLoadFeed = true
})
}
})
export const selectFeedResponse = state => state.feed.feedResponse
export const isLoadingFeed = state => state.feed.isLoadingFeed
export const failedToLoadFeed = state => state.feed.failedToLoadFeed
export default feedSlice.reducer
feedUtilities.js
import { useEffect, useRef } from "react";
export const useUpdateEffect = (effect, deps = []) => {
const isFirstMount = useRef(true);
useEffect(() => {
if(!isFirstMount.current) effect()
else isFirstMount.current = false
}, [deps]);
}
i cannot delete a product, although it seems that everything looks fine and i see in console that action of type: REMOVE_SELECTED_PRODUCT occurs, the product is still in array and seems like the state is not updated??
So lets start with productActions.js
export const removeSelectedProduct = (id) => {
return {
type: REMOVE_SELECTED_PRODUCT,
payload: id,
};
};
export const removeProduct = (id) => {
return async (dispatch) => {
dispatch(removeSelectedProduct(id));
try {
console.log(id); // this is for test, and i see in console that proper id is printed
await axios.delete(`https://fakestoreapi.com/products/${id}`);
} catch (err) {
console.log(err);
}
};
};
now the productsReducer.js
const intialState = {
products: [],
};
export const productsReducer = (state = intialState, { type, payload }) => {
switch (type) {
case SET_PRODUCTS:
return { ...state, products: payload };
case ADD_PRODUCT:
return { ...state, products: [...state.products, payload] };
case REMOVE_SELECTED_PRODUCT:
return {
...state,
products: state.products.filter((el) => el.id !== payload),
};
default:
return state;
}
};
And i use it in ProductDetails.js as an button so there is a whole code of this component:
const ProductDetails = ({ removeProduct, product }) => {
const { productId } = useParams();
const { image, title, price, category, description } = product;
const dispatch = useDispatch();
const fetchProductDetail = async (id) => {
const response = await axios
.get(`https://fakestoreapi.com/products/${id}`)
.catch((err) => {
console.log("Err: ", err);
});
dispatch(selectedProduct(response.data));
};
useEffect(() => {
if (productId && productId !== "") fetchProductDetail(productId);
}, [productId]);
return (
<div>
{Object.keys(product).length === 0 ? (
<div>...Loading</div>
) : (
<div>
<img alt={productId} src={image} />
<div>
<h1>{title}</h1>
<h2>
<p>${price}</p>
</h2>
<h3>{category}</h3>
<p>{description}</p>
<button>Add to Cart</button>
<button onClick={() => removeProduct(productId)}>UsuĊ</button>
</div>
</div>
)}
</div>
);
};
const mapStateToProps = (state) => {
return {
products: state.allProducts.products,
product: state.product,
};
};
const mapDispatchToProps = {
removeProduct,
selectedProduct,
};
export default connect(mapStateToProps, mapDispatchToProps)(ProductDetails);
If anyone cant find the problem, i would be glad.
As an i said the acction and the id is printed to the console:
console output on clicking the button
Maybe there is some problem with state, and it dont update the state when i delete? Idk, please help me :(
As product has number type id and you're providing string i.e
2 !== "2"
Do this:
dispatch(removeSelectedProduct(+id));//change string to number
...
...
await axios.delete(`https://fakestoreapi.com/products/${+id}`);//similarly here
I have a post details component where on clicking the like button the redux state changes the redux state is like
posts
->postDetails
I'am changing the liked property and number of likes of postDetais object, On clicking the like button the liked property is set to true from false and vice versa and the number of likes is incremented.
However the state is changing but the componentDidUpdate method is not firing
PostDetails.js
import React, { Component } from "react";
import { connect } from "react-redux";
import {
getPostData,
likePost,
unlikePost
} from "../../store/actions/postsActions";
import { Icon, Tooltip } from "antd";
import { Link } from "react-router-dom";
export class PostDetails extends Component {
state = {
postData: this.props.postDetails
};
componentDidMount() {
this.props.getPostData(this.props.match.params.post_id);
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log(this.props.postDetails);
if (prevProps.postDetails !== this.props.postDetails) {
this.setState({
postData: this.props.postDetails
});
}
}
render() {
const { postData } = this.state;
const liked = postData.liked;
return (
<div className="postDetails">
{postData && (
<div className="postDetailsContainer">
<div className="postImage">
<img src={postData.imageUrl} alt={postData.caption} />
</div>
<div className="postContent">
<div className="postContent__header">
<Link
to={`/user/${postData.username}`}
className="postContent__headerContent"
>
<img
src={postData.profileUrl}
alt={postData.username}
className="postContent__profileImage"
/>
<p className="postContent__username">{postData.username}</p>
</Link>
</div>
<div className="postComments" />
<div className="postInfo">
<div className="postActions">
{liked ? (
<Tooltip title="Unlike post">
<Icon
type="heart"
className="likePost"
theme="filled"
style={{ color: "#d41c00" }}
onClick={() => this.props.unlikePost(postData.id)}
/>
</Tooltip>
) : (
<Tooltip title="Like post">
<Icon
type="heart"
className="likePost"
onClick={() => this.props.likePost(postData.id)}
/>
</Tooltip>
)}
<Tooltip title="Comment">
<Icon type="message" className="commentButton" />
</Tooltip>
</div>
<Tooltip title="Refresh comments">
<Icon type="reload" className="reloadComments" />
</Tooltip>
</div>
<div />
</div>
</div>
)}
</div>
);
}
}
const mapStateToProps = state => {
return {
postDetails: state.posts.postDetails
};
};
const mapDispatchToProps = dispatch => {
return {
getPostData: postId => dispatch(getPostData(postId)),
likePost: postId => dispatch(likePost(postId)),
unlikePost: postId => dispatch(unlikePost(postId))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(PostDetails);
postsReducer.js
const initialState = {
creatingPost: false,
feed: [],
createdPost: false,
feedUpdated: false,
postDetails: {}
};
const postsReducer = (state = initialState, action) => {
switch (action.type) {
case "CREATING_POST":
return {
...state,
creatingPost: true,
createdPost: false
};
case "ADD_POST":
return {
...state,
feed: state.feed.concat(action.payload)
};
case "FETCH_FEED":
return {
...state,
feed: action.payload
};
case "CREATED_POST":
return {
...state,
creatingPost: false,
createdPost: true
};
case "UPDATE_FEED":
return {
...state,
feed: action.payload,
feedUpdated: true
};
case "GET_POST_DATA":
return {
...state,
postDetails: action.payload
};
case "RESET_FEED_UPDATED":
return {
...state,
feedUpdated: false
};
case "RESET_CREATED_POST":
return {
...state,
createdPost: false
};
case "LIKED_POST":
const { postDetails } = state;
postDetails.liked = true;
postDetails.likes += 1;
return {
...state,
postDetails: postDetails
};
case "UNLIKED_POST":
const postDetails1 = state.postDetails;
postDetails1.liked = false;
postDetails1.likes -= 1;
return {
...state,
postDetails: postDetails1
};
case "CLEAR_POST_DATA":
return initialState;
default:
return state;
}
};
export default postsReducer;
postsActions.js
import Axios from "axios";
import moment from "moment";
import store from "../store";
export const createPost = postData => {
return (dispatch, getState) => {
dispatch({ type: "CREATING_POST" });
Axios.post("/api/post/new", {
imageUrl: postData.imageUrl,
caption: postData.caption
})
.then(res => {
dispatch({ type: "CREATED_POST" });
dispatch({ type: "ADD_POST", payload: res.data.post });
})
.catch(err => {
console.log(err);
});
};
};
export const fetchFeed = () => {
return (dispatch, getState) => {
Axios.get("/api/user/feed")
.then(res => {
var feed = res.data.feed;
const state = store.getState();
const likedPosts = state.user.userData.likedPosts;
for (var i = 0; i < feed.length; i++) {
for (var j = 0; j < feed.length - i - 1; j++) {
if (moment(feed[j + 1].createdAt).isAfter(feed[j].createdAt)) {
var temp = feed[j];
feed[j] = feed[j + 1];
feed[j + 1] = temp;
}
}
}
for (var i = 0; i < feed.length; i++) {
if (likedPosts.indexOf(feed[i]._id) > -1) {
feed[i]["liked"] = true;
} else {
feed[i]["liked"] = false;
}
}
console.log(feed);
dispatch({ type: "FETCH_FEED", payload: feed });
})
.catch(err => {
console.log(err);
});
};
};
export const likePost = postId => {
return (dispatch, getState) => {
Axios.put("/api/post/like", { postId: postId })
.then(res => {
const feed = store.getState().posts.feed;
feed.forEach(post => {
if (post._id === postId) {
post.liked = true;
}
});
dispatch({ type: "UPDATE_FEED", payload: feed });
dispatch({ type: "LIKED_POST", payload: res.data.postId });
})
.catch(err => {
console.log(err);
});
};
};
export const unlikePost = postId => {
return (dispatch, getState) => {
Axios.put("/api/post/unlike", { postId: postId })
.then(res => {
const feed = store.getState().posts.feed;
feed.forEach(post => {
if (post._id === postId) {
post.liked = false;
}
});
dispatch({ type: "UPDATE_FEED", payload: feed });
dispatch({ type: "UNLIKED_POST", payload: res.data.postId });
})
.catch(err => {
console.log(err);
});
};
};
export const getPostData = postId => {
return (dispatch, getState) => {
Axios.get(`/api/post/${postId}`)
.then(res => {
const likedPosts = store.getState().user.userData.likedPosts;
if (likedPosts.indexOf(postId) > -1) {
res.data.post["liked"] = true;
} else {
res.data.post["liked"] = false;
}
dispatch({ type: "GET_POST_DATA", payload: res.data.post });
})
.catch(err => {
console.log(err);
});
};
};
export const resetFeedUpdated = () => {
return (dispatch, getState) => {
dispatch({ type: "RESET_FEED_UPDATED" });
};
};
export const resetCreatedPost = () => {
return (dispatch, getState) => {
dispatch({ type: "RESET_CREATED_POST" });
};
};
Your LIKED_POST and UNLIKED_POST reducer cases are not pure - they are are mutating the existing postDetails object in the state and putting it back into state so connect is optimizing and not re-rendering when it does a shallow equals comparison on postDetails from the previous and next props in componentShouldUpdate. Make sure you're creating a completely new value for postDetails like:
case "LIKED_POST":
const { postDetails } = state;
const newPostDetails = {
...postDetails,
liked: true,
likes: postDetails.likes + 1,
};
return {
...state,
postDetails: newPostDetails
};
You should check, if the comparison if (prevProps.postDetails !== this.props.postDetails) ever hits. Because with the like function you only change properties of the same object, the comparison will fail, because it's still the same object reference for postDetails. Try to return a new object in your reducer:
case "LIKED_POST":
const { postDetails } = state;
postDetails.liked = true;
postDetails.likes += 1;
return {
...state,
postDetails: {
...postDetails
},
}
Also if you're not changing anything of the object inside the component but in Redux store why not use the component property directly? You can remove the state object and the componentDidUpdate. Also you could refactor it to a function component.
render() {
const { postDetails: postData } = this.props;
...
}
When working with Redux, never forget the three principles
Single Source of truth
State is ready only
Reducers must be pure functions: Reducers take previous state and some action and modifies it and returns new state. We should never mutate state. We should create new objects and return them.
You have mutated existing state in your reducer functions. This doesnt trigger componentdidupdate because, connect method ( it checks mapStateToProps) treats that there is nothing that changed (It checks reference and since reference didnt change Component is not invoked).
You can use Object.assign or use spread operator which helps to make your reducers return a new object.
Change your Liked and unlinked posts reducer functions to return a new object instead of mutating existing object.
#azundo added how your code should be to achieve what you need.
I'm trying to dynamically add inputs when the user clicks the button to add a question.
Usually doing a controlled form is easy as your know what the field names are. But in this situation they are dynamic.
I've got a working solution but it mutates the state.
Is there a better way to do this?
Thanks
JSX
import React, { Component } from 'react';
import axios from 'axios';
import { saveAs } from 'file-saver';
class Form extends Component {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
this.handleForm = this.handleForm.bind(this);
this.addQuestion = this.addQuestion.bind(this);
this.removeQuestion = this.removeQuestion.bind(this);
this.state = {
questions: []
}
}
onChange(e, i) {
this.state.questions[i] = e.target.value;
this.setState({
questions: this.state.questions
})
}
handleForm(e) {
e.preventDefault();
const body = {
questions: this.state.questions
};
axios.post('/api/pdfs/create', body)
.then(() => axios.get('/api/pdfs/fetch', { responseType: 'blob' }))
.then((res) => {
const pdfBlob = new Blob([res.data], { type: 'application/pdf' });
return saveAs(pdfBlob, 'questions.pdf');
})
.catch(error => {
console.log(error.response)
});
}
addQuestion() {
this.setState({
questions: [...this.state.questions, '']
});
}
removeQuestion(index) {
this.setState({
questions: this.state.questions.filter((question, i) => i !== index)
});
}
render() {
return (
<div>
<button onClick={this.addQuestion}>Add Question</button>
<form onSubmit={this.handleForm}>
{this.state.questions.map((question, index) => (
<div key={index}>
<input type="text" name={`question-${question}`} onChange={(e) => this.onChange(e, index)} />
<button type="button" onClick={() => this.removeQuestion(index)}>x</button>
</div>
))}
<button type="submit">Submit</button>
</form>
</div>
);
}
}
export default Form;
You are mutating the state only in your onChange call, and that can be fixed easily:
onChange(e, i) {
this.setState({
questions: this.state.questions.map((v, i2) => i === i2 ? e.target.value : v),
});
}
(This won't change the functionality though, its just a "best practice improvement")
I was driven crazy by my first react-redux app. My Redux State is never updated.
I tried to find every solution on the website but they are not helping. One very similar question here used promise but I am not.
Redux-dev-tools catch the actions about the login_success but the global state is never updated, please tell me what should I do to debug if you could, thank you so much.
First Index.js
import ... from ...
const compostEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const rootReducer = combineReducers({
auth: authReducer,
})
const store = createStore(rootReducer, compostEnhancer(
applyMiddleware(thunk)
));
const app = (<BrowserRouter><App /></BrowserRouter>)
ReactDOM.render(<Provider store={store}>{app}</Provider>, document.getElementById('root'));
registerServiceWorker();
authReducer.js :
import * as actionTypes from '../actions/actionTypes';
const initialState = {
token: null,
userId: null,
error: null,
loading: false
}
const authReducer = (state = initialState, action) => {
switch (action.types) {
case actionTypes.LOGIN_START:
return {
...state,
};
case actionTypes.LOGIN_SUCCESS:
return {
...state,
token: action.idToken,
userId: action.userId,
loading: false,
error:null
}
default:
return state;
}
}
export default authReducer;
authAction.js:
import * as actionTypes from './actionTypes';
import axios from 'axios';
export const loginStart= () =>{
return {
type: actionTypes.LOGIN_START
};
}
export const loginSuccess = (token,userId) =>{
return {
type: actionTypes.LOGIN_SUCCESS,
userId: userId,
idtoken: token,
}
}
export const loginFail = (error) =>{
return {
type:actionTypes.LOGIN_FAIL,
error:error
}
}
export const auth = (email,password,isSignup ) =>{
return dispatch =>{
dispatch(loginStart());
const authData = {
email: email,
password: password,
returnSecureToken:true
}
let url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=...'
if(!isSignup ){
url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=...'
}
axios.post(url, authData)
.then( response =>{
console.log(response);
dispatch(loginSuccess(response.data.idToken, response.data.localId))
})
.catch(err=>{
console.log(err);
dispatch(loginFail(err));
})
}
}
Login.js (Component):
import ... from ...;
class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: "",
isSignup: false,
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit = e => {
e.preventDefault();
// This will trigger actions
this.props.onAuth(this.state.email, this.state.password, this.state.isSignup);
console.log(this.props.token) //here I can Get Token
}
handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
}
render() {
let form =
<div className={classes.ContactData} >
<h4>Sign IN</h4>
<form onSubmit={this.handleSubmit} >
<Input
elementType='input'
name="email"
required
label="Email"
placeholder="Email"
value={this.state.email}
margin="normal"
onChange={this.handleChange}
/>
<br />
<Input
elementType='input'
required
name="password"
value={this.state.password}
label="Password"
onChange={this.handleChange}
/>
<br />
<Button
color="primary"
type="submit"
>Submit</Button>
<Button color="primary" href="/"> CANCLE</Button>
</form>
</div>
if (this.props.loading) {
form = <Spinner />
}
let errorMessage = null;
if (this.props.error) {
errorMessage =(
<p>{this.props.error.message} </p>
)
}
let token = null;
if(this.props.token){
token = this.props.token.toString()
}
return (
<div>
{errorMessage}
{ form }
</div>
)
}
}
const mapStateToProps = state => {
return {
loading: state.auth.loading,
error: state.auth.error,
token:state.auth.idToken,
}
}
const mapDispatchToProps = dispatch => {
return {
onAuth: (email, password, isSignup) => dispatch(actions.auth(email, password, isSignup)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Login);
And also, the problem leads to my Spinner and show ErrorMessage not working.
I suppose its a typo.
instead of
switch (action.types)
try this
switch (action.type)
In reducer, we get an object returned from the actions, on the argument action.