I am trying to create a user authentication flow using react router dom v6 and and redux toolkit. But sometimes it seems the state is not updating in redux.
Here is how I have created my authentication slice.
AuthenticationSlice
const initialState = {
isAuthenticating: false,
isAuthenticated: false,
isAuthenticationError: false,
error: null,
};
const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
loggingInUser: (state) => {
state.isAuthenticating = true;
state.isAuthenticated = false;
},
loggedInUser: (state, payload) => {
state.isAuthenticated = true;
state.isAuthenticating = false;
},
loggingUserFail: (state, payload) => {
state.isAuthenticated = false;
state.isAuthenticating = false;
state.error = payload.error;
},
logoutUser: (state, payload) => {
state.isAuthenticated = false;
state.isAuthenticating = false;
state.isAuthenticationError = false;
},
},
});
export const authSelector = (state) => state.auth;
export const {
loggedInUser,
loggingInUser,
loggingUserFail,
logoutUser,
} = authSlice.actions;
export default authSlice.reducer;
export function loginUserThunk(body) {
return async (dispatch) => {
dispatch(loggingInUser());
try {
const response = await axios({
method: "POST",
url: `${BASE_URL}/auth/login`,
data: body,
});
if (response.data.data) {
window.localStorage.setItem(
"access_token",
response?.data?.data?.access_token
);
setAuthorizationHeader(response.data.data);
dispatch(loggedInUser(response.data.data));
}
} catch (error) {
dispatch(loggingUserFail(error));
}
};
}
Login.js
import { loginUserThunk } from "../../redux/slices/auth";
function Login() {
const dispath = useDispatch();
const navigate = useNavigate();
const { handleBlur, handleChange, handleSubmit, errors, touched, values } =
useFormik({
initialValues: {
username: "",
password: "",
},
onSubmit: (values) => {
dispath(loginUserThunk(values));
navigate("/");
},
});
return (
<Container>
<Grid
container
justifyContent={"center"}
display={"flex"}
alignItems={"center"}
height={"100vh"}
width={"100%"}
>
<Grid item>
<Card sx={{ minWidth: 500 }}>
<Box p={2}>
<Typography variant="h5">Login to explore</Typography>
</Box>
<CardContent>
<form onSubmit={handleSubmit}>
<Stack>
<TextField
name="username"
onChange={handleChange}
value={values.username}
sx={{ marginTop: 2 }}
placeholder="UserName"
variant="outlined"
/>
<TextField
name="password"
onChange={handleChange}
value={values.password}
sx={{ marginY: 2 }}
placeholder="Password"
type={"password"}
variant="outlined"
/>
<Button type="submit" variant="contained">
Login
</Button>
<Box marginTop={2}>
<Typography>
New User ? <Link to={"/register"}>Register</Link>
</Typography>
</Box>
</Stack>
</form>
</CardContent>
</Card>
</Grid>
</Grid>
</Container>
);
}
export default Login;
Here even after dispatching dispatch(loggedInUser(response.data.data)); loggedInUser isAuthenticated state is not updating.
Related
I am trying to write tests for an authentication form I created. I have the need to mock the return value of a custom hook which returns the login function and the state showing if the login function is running and waiting for a response. I have to mock the useAuth() hook in the top of the component. I've never done testing before so I feel a bit lost!
Here is my Form:
export const LoginForm = () => {
const { login, isLoggingIn } = useAuth(); // here's the function I want to mock
const { values, onChange }: any = useForm({});
const [error, setError] = useState(null);
const handleLogin = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
try {
await login(values);
} catch (err: any) {
setError(err.message);
}
};
return (
<Box
sx={{
py: (t) => t.spacing(8),
px: (t) => t.spacing(8),
background: '#FFF',
borderRadius: 2,
marginTop: 0,
position: 'relative',
zIndex: 1,
}}
>
<form data-testid='login-form' onSubmit={handleLogin}>
<FormTextField
placeholder='your email'
name='email'
data-testid='login-email'
onChangeFn={onChange}
loading={isLoggingIn}
fullWidth={true}
id='email'
label='email'
type='text'
autoFocus={true}
/>
<FormTextField
placeholder='password'
name='password'
data-testid='login-password'
onChangeFn={onChange}
loading={isLoggingIn}
fullWidth={true}
id='password'
label='password'
type='password'
autoFocus={false}
sx={{
marginBottom: 2,
}}
/>
<Typography
variant='subtitle1'
sx={{
marginY: (t) => t.spacing(3.7),
textAlign: 'center',
}}
>
Forget password? <BlueLink to='#/hello' text='Reset here' />
</Typography>
<FormButton
text='Submit'
data-testid='submit-login'
variant='contained'
disabled={false}
fullWidth
type='submit'
sx={{
color: 'white',
}}
/>
<div data-testid='error-login'>
{error && (
<>
<hr />
{error}
</>
)}
</div>
</form>
<Hidden mdDown>
<img
style={{
position: 'absolute',
right: -50,
bottom: -50,
}}
src={SquigglesOne}
alt=''
/>
</Hidden>
</Box>
);
};
export default LoginForm;
Here's how I'm trying to mock it:
jest.mock('../../../helpers/auth', () => ({
...jest.requireActual('../../../helpers/auth'),
useAuth: jest.fn(() => ({
login: () => true,
})),
}));
When I run my tests I get an error:
Error: Uncaught [TypeError: Cannot destructure property 'login' of '(0 , _auth.useAuth)(...)' as it is undefined.] But when I remove the mock my test runs successfully so I hope I'm close. Here's my full test:
import {
fireEvent,
render /* fireEvent */,
within,
} from '#testing-library/react';
import { AuthProvider } from 'helpers/auth';
import { ReactQueryProvider } from 'helpers/react-query';
import { MemoryRouter } from 'react-router';
import LoginForm from './index';
jest.mock('../../../helpers/auth', () => ({
...jest.requireActual('../../../helpers/auth'),
useAuth: jest.fn(() => ({
login: () => true,
})),
}));
const setup = () => {
const loginRender = render(
<MemoryRouter>
<ReactQueryProvider>
<AuthProvider>
<LoginForm />
</AuthProvider>
</ReactQueryProvider>
</MemoryRouter>
);
const emailbox = loginRender.getByTestId('login-email');
const passwordBox = loginRender.getByTestId('login-password');
const emailField = within(emailbox).getByPlaceholderText('your email');
const passwordField = within(passwordBox).getByPlaceholderText('password');
const form = loginRender.getByTestId('login-form');
return {
emailField,
passwordField,
form,
...loginRender,
};
};
test('Input should be in document', async () => {
const { emailField, passwordField } = setup();
expect(emailField).toBeInTheDocument();
expect(passwordField).toBeInTheDocument();
});
test('Inputs should accept email and password strings', async () => {
const { emailField, passwordField } = setup();
emailField.focus();
await fireEvent.change(emailField, {
target: { value: 'atlanteavila#gmail.com' },
});
expect(emailField.value).toEqual('atlanteavila#gmail.com');
passwordField.focus();
await fireEvent.change(passwordField, {
target: { value: 'supersecret' },
});
expect(passwordField.value).toEqual('supersecret');
await fireEvent.keyDown(form, { key: 'Enter' });
});
test('Form should be submitted', async () => {
const { emailField, passwordField, form } = setup();
emailField.focus();
await fireEvent.change(emailField, {
target: { value: 'atlanteavila#gmail.com' },
});
expect(emailField.value).toEqual('atlanteavila#gmail.com');
passwordField.focus();
await fireEvent.change(passwordField, {
target: { value: 'supersecret' },
});
await fireEvent.keyDown(form, { key: 'Enter' });
expect(passwordField.value).toEqual('supersecret');
});
I refactored my App.js into a functional component. When I try to login, my currentUser is set to a user, but then immediately set to null. I am confused why currentUser is set to null after it is given value. Is something calling setCurrentUser again to set it to null?
App.js:
const App = (props) => {
const [ currentUser, setCurrentUser ] = useState(null)
const [ shoppingCart, setShoppingCart ] = useState([])
const handleLogin = (email, password, history) => {
axios({
method: 'post',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
data: { email, password},
url: 'http://localhost:3000/api/v1/login'
})
.then( res => setCurrentUser(res.data.user))
}
console.log('this is currentUser', currentUser)
return (
<Switch>
<div>
<SideDrawer currentUser={currentUser} setCurrentUser={setCurrentUser}/>
<div className='App'>
{/** OTHER ROUTES HERE **/}
<Route path='/login' render={
() => {
return (
<Login handleLogin={handleLogin} history={props.history} currentUser={currentUser}/>
)
}
} />
{/* Route to Signup page */}
<Route path='/signup' render={
() => {
return(
<SignupForm setCurrentUser={setCurrentUser} history={props.history}/>
)
}
} />
</div>
</div>
</Switch>
Login.js
const Login = ({history, handleLogin}) => {
const classes = useStyles()
const [ values, setValues ] = useState({
email: '',
password: '',
showPassword: false
})
const handleChange = e => {
const { name, value } = e.target
setValues({ ...values, [name]: value})
}
const handleShowPassword = () => {
setValues({...values, showPassword: !values.showPassword})
}
const handleMouseDownPassword = (e) => {
e.preventDefault()
}
return (
<Box className={classes.container}>
<h1>Login</h1>
<FormGroup>
<FormControl variant="outlined">
<TextField
className={classes.text}
variant='outlined'
multiline
name='email'
label="Email"
onChange={handleChange}
/>
</FormControl>
<FormControl variant='outlined'>
<TextField
label='Password'
className={classes.text}
type={values.showPassword ? 'text' : 'password'}
name='password'
margin="normal"
variant='outlined'
onChange={handleChange}
InputProps={{
endAdornment: (
<InputAdornment>
<IconButton
onClick={handleShowPassword}
onMouseDown={handleMouseDownPassword}
>
{values.showPassword ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
)
}}
/>
</FormControl>
<Button
variant='contained'
className={classes.button}
onClick={ () => handleLogin(values.email, values.password, history)}
>
Login
</Button>
</FormGroup>
</Box>
)
}
Something is setting it to null.
Either:
.then( res => setCurrentUser(res.data.user))
or SideDrawer or SignupForm are calling setCurrentUser(null).
If you remove both SideDrawer and SignupForm you can check the functionality of the .then( res => setCurrentUser(res.data.user)) line.
Then add back SideDrawer and see if that is causing it to be set to null. Lastly, add back SignupForm and see if that's the culprit.
I am working on react app which uses redux for state management. In this app i have to implement search page. In which when user types on search box the app display the product related to that text input.
I have api endpoint like this api.XXXXXXX.com/buyer/product/filter?q=${inputValue}. I tried implement this but i am not able filter this. I haven't worked on this before and couldn't find good tutorial for this.
Here is my code
searchAction.js
export const searchProduct =(value) => dispatch => {
dispatch({
type: searchActionTypes.SEARCH_PRODUCT_LOAD
});
new _rest().get(`/buyer/product/filter?q=${value}`)
.then(res => {
console.log(res);
dispatch({
type: searchActionTypes.SEARCH_PRODUCT_SUCCESS,
payload: res
})
}).catch(err => {
console.log(err)
dispatch({
type: searchActionTypes.SEARCH_PRODUCT_ERROR,
error: err
})
});
}
searchActionreducer.js
const initialState = {
isFetching: false,
searchData: {},
isFetched: false,
isError: null
}
export default (state=initialState, action) =>{
switch (action.type) {
case searchActionTypes.SEARCH_PRODUCT_LOAD:
return {
...state,
isFetching: true
};
case searchActionTypes.SEARCH_PRODUCT_SUCCESS:
return {
...state,
isFetching: false,
searchData: action.payload.data._embedded,
isFetched: true
};
case searchActionTypes.SEARCH_PRODUCT_ERROR:
return {
...state,
isFetched: false,
};
default:
return {
...state
}
}
}
searchPage.js
class SearchPage extends React.Component {
state = {
inputValue: '',
}
componentDidMount() {
this.props.searchProduct('');
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('component update', prevState)
if(this.state.inputValue !== prevState.inputValue) {
this.props.searchProduct(this.state.inputValue);
}
}
render() {
console.log('SearchPage Result', this.props.product);
const {product} = this.props;
const {inputValue} =this.state;
const updateInputValue = (e) => {
e.preventDefault();
this.setState({
inputValue: e.target.value
})
}
return(
<Grid container>
<div style={{width: '100%', display:'flex', justifyContent: 'center' }}>
<form noValidate autoComplete="off">
<TextField value={inputValue} onChange={updateInputValue} id="standard-basic" label="Search Product" />
</form>
</div>
<Grid container spacing={8} style={{padding: '30px'}}>
{product && product.productResourceList &&
product.productResourceList.map((data, index) => {
return(
<Grid item xs={6}>
<MainCards key={data.productId} data={data} />
</Grid>
)
})}
</Grid>
</Grid>
)
}
}
const mapStateToProps =(state) => {
return {
product: state.search.searchData
}
}
const mapDispatchToProps = {
searchProduct,
}
export default connect(mapStateToProps, mapDispatchToProps)(SearchPage);
Here In componentDidMount Initially when component mounts i made display all the product. After that in componentDidUpdate i trying get filtered data from api buy passing this.state.inputValue as value of q in api endpoint api.XXXXXXX.com/buyer/product/filter?q=${inputValue}. But component is not updating at all.
I have created a basic MERN log in page and I am a bit confused on how to redirect the user after a successful log in. I have created a Log in context and when a user successfully logs in I set logged in state to true. I am unsure how I can then redirect to /items route. Any help would be appreciated. Here is my App code:
function App() {
return (
<LoggedInProvider>
<ThemeProvider>
<Switch>
<Route
exact
path="/"
render={() => <SignIn />}
/>
<Route
exact
path="/items"
render={() => <Index />}
/>
</Switch>
</ThemeProvider>
</LoggedInProvider>
);
}
export default App;
Here is my SignIn component:
function Form(props) {
const { isDarkMode } = useContext(ThemeContext);
const { loggedIn, changeLogIn } = useContext(LoggedInContext);
const [isSignUp, setSignUp] = useState(false);
const { classes } = props;
const [usernameValue, setUsernameValue] = useState('');
const [passwordValue, setPasswordValue] = useState('');
const handleUsernameChange = (e) => {
setUsernameValue(e.target.value);
};
const handlePasswordChange = (e) => {
setPasswordValue(e.target.value);
};
// const [value, setValue] = useState(initialVal);
// const handleChange = (e) => {
// setValue(e.target.value);
// };
const handleClick = () => {
setSignUp(!isSignUp);
};
const reset = () => {
setUsernameValue('');
setPasswordValue('');
};
const authSubmitHandler = async (event) => {
event.preventDefault();
if (!isSignUp) {
try {
const response = await fetch('http://localhost:8181/auth', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: usernameValue,
password: passwordValue,
}),
});
const responseData = await response.json();
if (responseData.code === 200) {
console.log('Success Response');
changeLogIn(true);
reset();
}
console.log('This is a response');
console.log(responseData);
} catch (err) {
console.log(err);
}
} else {
try {
const response2 = await fetch('http://localhost:8181/auth', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: usernameValue,
password: passwordValue,
}),
});
const responseData2 = await response2.json();
console.log(responseData2);
} catch (err) {
console.log(err);
}
}
};
return (
<main className={classes.main}>
{!isSignUp ? (
<Paper
className={classes.paper}
style={{ background: isDarkMode ? '#2E3B55' : 'white' }}
>
<Avatar className={classes.avatar}>
<LockOutlinedIcon />
</Avatar>
<Typography variant="h5">Sign In</Typography>
<form
className={classes.form}
onSubmit={authSubmitHandler}
>
<FormControl
margin="normal"
required
fullWidth
>
<InputLabel htmlFor="username">Username</InputLabel>
<Input
id="username1"
name="username1"
value={usernameValue}
onChange={handleUsernameChange}
autoFocus
/>
</FormControl>
<FormControl
margin="normal"
required
fullWidth
>
<InputLabel htmlFor="password">Password</InputLabel>
<Input
id="password"
name="password"
type="password"
value={passwordValue}
onChange={handlePasswordChange}
autoFocus
/>
</FormControl>
<Button
variant="contained"
type="submit"
fullWidth
color="primary"
className={classes.submit}
>
Sign In
</Button>
</form>
<Button
variant="contained"
type="submit"
fullWidth
color="secondary"
className={classes.submit}
onClick={handleClick}
>
Switch to Sign up
</Button>
</Paper>
) : (
<Paper
className={classes.paper}
style={{ background: isDarkMode ? '#2E3B55' : 'white' }}
>
<Avatar className={classes.avatar}>
<LockOutlinedIcon color="primary" />
</Avatar>
<Typography variant="h5">Sign Up</Typography>
<form
className={classes.form}
onSubmit={authSubmitHandler}
>
<FormControl
margin="normal"
required
fullWidth
>
<InputLabel htmlFor="username">Username</InputLabel>
<Input
id="username2"
name="username"
value={usernameValue}
onChange={handleUsernameChange}
autoFocus
/>
</FormControl>
<FormControl
margin="normal"
required
fullWidth
>
<InputLabel htmlFor="password">Password</InputLabel>
<Input
id="password"
name="password"
type="password"
value={passwordValue}
onChange={handlePasswordChange}
autoFocus
/>
</FormControl>
<Button
variant="contained"
type="submit"
fullWidth
color="primary"
className={classes.submit}
>
Sign Up
</Button>
</form>
<Button
variant="contained"
type="submit"
fullWidth
color="secondary"
className={classes.submit}
onClick={handleClick}
>
Switch to Sign In
</Button>
</Paper>
)}
</main>
);
}
// class Form extends Component {
// static contextType = LanguageContext;
// render() {
// return (
// );
// }
// }
export default withStyles(styles)(Form);
And here is my log in context:
import React, { createContext, useState } from 'react';
export const LoggedInContext = createContext();
export function LoggedInProvider(props) {
const [loggedIn, setLoggedIn] = useState(false);
const changeLogIn = (val) => {
setLoggedIn(val);
};
return (
<LoggedInContext.Provider value={{ loggedIn, changeLogIn }}>
{props.children}
</LoggedInContext.Provider>
);
}
In your LoggedInProvider component you can do something like this:
import { useHistory } from "react-router-dom";
import React, { createContext, useState } from "react";
export const LoggedInContext = createContext();
export function LoggedInProvider(props) {
const history = useHistory();
const [loggedIn, setLoggedIn] = useState(false);
const changeLogIn = (val) => {
setLoggedIn(val);
if(val) NavigateAway('/items');
};
const NavigateAway = path => {
history.push(path);
}
return (
<LoggedInContext.Provider value={{ loggedIn, changeLogIn }}>
{props.children}
</LoggedInContext.Provider>
);
}
Hope this work's for you.
Just use props.history.push(). Every route's component will have access to the history object attached to it. As a reference:
const Login = ({ history }) => {
// username, password state fields here
function login(e) {
e.preventDefault();
login({ username, password })
.then(() => {
// set your authentication credentials to some persistent store
history.push('/items');
});
}
return (
// login form markup in here
);
};
well, the easiest way to do is to use the history object available in your props.
Try to see if it exist, you can do the following:
this.props.history.push('/item'): this should redirect you to the page.
Link from react-router-dom: it contains a property called redirect
Upon the click of a single like, it is increasing the number of likes for both separate components. What is causing both like numbers to increase, and how can I code it to where only one like number increases upon clicking a like?
I have also include the console in the picture below where I have console logged the logic in my reducer. You can find the code for the reducer further below the picture.
Reducer code
import { GET_GOALS, GOAL_ERROR, UPDATE_LIKES } from "../actions/types";
const initialState = {
goals: [],
goal: null,
loading: true,
error: {}
};
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case GET_GOALS:
return {
...state,
goals: payload,
loading: false
};
case GOAL_ERROR:
return {
...state,
error: payload,
loading: false
};
case UPDATE_LIKES:
return {
...state,
goals: state.goals.map(goal =>
console.log("goal id", goal._id) === console.log("payload id", payload.goalId) ? { ...goal, likes: payload.likes } : goal
),
loading: false
};
default:
return state;
}
}
Action code
import axios from "axios";
import { GET_GOALS, GOAL_ERROR, UPDATE_LIKES } from "./types";
// Get goals
export const getGoals = () => async dispatch => {
try {
const res = await axios.get("/api/goal/goalfeed");
dispatch({
type: GET_GOALS,
payload: res.data
});
} catch (error) {
dispatch({
type: GOAL_ERROR,
payload: { msg: error.response }
});
}
};
// Add like
export const addLike = goalId => async dispatch => {
try {
const res = await axios.put(`/api/goal/like/${goalId}`);
dispatch({
type: UPDATE_LIKES,
payload: { goalId, likes: res.data }
});
} catch (error) {
dispatch({
type: GOAL_ERROR,
payload: { msg: error.response }
});
}
};
// Remove like
export const removeLike = goalId => async dispatch => {
try {
const res = await axios.put(`/api/goal/unlike/${goalId}`);
dispatch({
type: UPDATE_LIKES,
payload: { goalId, likes: res.data }
});
} catch (error) {
dispatch({
type: GOAL_ERROR,
payload: { msg: error.response }
});
}
};
Goals component code
import React, { useEffect } from "react";
import Moment from "react-moment";
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
import { connect } from "react-redux";
import { addLike, removeLike } from "../../actions/goal";
import { getGoals } from "../../actions/goal";
import Spinner from "../layout/Spinner";
import Navbar from "../dashboard/Navbar";
import ThumbUpAltIcon from "#material-ui/icons/ThumbUpAlt";
import ThumbDownAltIcon from "#material-ui/icons/ThumbDownAlt";
import ChatIcon from "#material-ui/icons/Chat";
import DeleteIcon from "#material-ui/icons/Delete";
import DoneIcon from "#material-ui/icons/Done";
import {
Typography,
Container,
CssBaseline,
makeStyles,
Grid,
Avatar,
Paper,
Button
} from "#material-ui/core";
const useStyles = makeStyles(theme => ({
paper: {
height: "auto",
marginBottom: theme.spacing(3)
},
actionButtons: {
marginTop: "3vh"
},
profileHeader: {
textAlign: "center",
marginBottom: 20
},
avatar: {
width: theme.spacing(7),
height: theme.spacing(7)
}
}));
const Goals = ({
getGoals,
auth,
addLike,
removeLike,
goal: { goals, user, loading }
}) => {
useEffect(() => {
getGoals();
}, [getGoals]);
const classes = useStyles();
return loading ? (
<>
<Navbar />
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Spinner />
</div>
</Container>
</>
) : (
<>
<CssBaseline />
<Navbar />
<main>
<Container>
<Typography variant="h2" className={classes.profileHeader}>
Goals
</Typography>
{/* parent grid */}
<Grid container spacing={4}>
{goals.map(singleGoal => (
<Grid
className={classes.paper}
key={singleGoal._id}
spacing={1}
container
item
direction="row"
alignItems="center"
component={Paper}
>
<Grid
item
container
direction="column"
justify="center"
alignItems="center"
xs={3}
>
<Avatar className={classes.avatar} src={singleGoal.avatar} />
<Typography variant="caption">
{singleGoal.first_name} {singleGoal.last_name}
</Typography>
<Typography variant="caption" className={classes.postedOn}>
Posted on{" "}
<Moment format="MM/DD/YYYY">{singleGoal.date}</Moment>
</Typography>
</Grid>
<Grid container item direction="column" xs={9}>
<Typography variant="body1">{singleGoal.text}</Typography>
<Grid item className={classes.actionButtons}>
<Button size="small" onClick={e => addLike(singleGoal._id)}>
<ThumbUpAltIcon />
</Button>
<Typography variant="caption">
{singleGoal.likes.length}
</Typography>
<Button
size="small"
onClick={e => removeLike(singleGoal._id)}
>
<ThumbDownAltIcon />
</Button>
<Button href={`/goal/${singleGoal._id}`} size="small">
<ChatIcon />
</Button>
{!auth.loading && singleGoal.user === auth.user._id && (
<Button size="small">
<DoneIcon />
</Button>
)}
{!auth.loading && singleGoal.user === auth.user._id && (
<Button size="small">
<DeleteIcon />
</Button>
)}
</Grid>
</Grid>
</Grid>
))}
</Grid>
</Container>
</main>
</>
);
};
Goals.propTypes = {
getGoals: PropTypes.func.isRequired,
goal: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
goal: state.goal,
auth: state.auth
});
export default connect(mapStateToProps, { getGoals, addLike, removeLike })(
Goals
);
There exists a flaw in your conditional test.
state.goals.map(goal =>
console.log("goal id", goal._id) === console.log("payload id", payload.goalId) // What is this? it will always evaluate to true
? { ...goal, likes: payload.likes }
: goal
)
console.log('EQUAL?', console.log() === console.log()); // true
console.log('EQUAL?', console.log(3) === console.log(3)); // true
console.log('EQUAL?', console.log(3) === console.log('three')); // true
console.log('EQUAL?', console.log('apple') === console.log({})); // true
console.log('EQUAL?', console.log(42) === console.log(-42)); // true
The function console.log is a void return, i.e. undefined, so you are comparing undefined === undefined, which is always true.
console.log(undefined === undefined); // true
You are spreading in the new 'likes' value to every goal object.
Try instead:
state.goals.map(
goal => goal._id === payload.goalId
? { ...goal, likes: payload.likes }
: goal
)