Not able to update state while using redux toolkit - javascript

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

How to mock a custom hook using react-testing-library and jest

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');
});

Why is state set to null immediately after setter function defined by useState is called?

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.

How to add Search function with reactjs in with api

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.

Using React Router to Redirect to next page after successful login

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

Clicking like icons increases the like count of other components

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
)

Categories

Resources