Manual Cache Update | Dispatch function is not firing - reactjs

I have the following API slice:
const mainApi = createApi({
reducerPath: 'mainApi',
baseQuery: fetchBaseQuery({
baseUrl: 'http://localhost:3001/api/v1',
}),
tagTypes: ['$USER'],
endpoints: (builder) => ({
login: builder.mutation({
query: ({ email, password }) => ({
url: '/auth/login',
method: 'POST',
body: {
email,
password,
},
credentials: 'include',
}),
transformResponse: (res) => res.data,
// invalidatesTags: ['$USER'],
async onQueryStarted(payload, { dispatch, queryFulfilled }) {
console.log('LOGIN QUERY STARTED');
try {
const { data: $USER } = await queryFulfilled;
dispatch(
mainApi.util.updateQueryData('getLoggedInUser', null, (draft) => {
console.log('METHOD DISPATCH RAN!!');
Object.assign(draft, $USER);
})
);
} catch (err) {}
},
}),
getLoggedInUser: builder.query({
query: () => ({
url: '/auth/logged-in-user',
credentials: 'include',
}),
transformResponse: (res) => res.data,
providesTags: ['$USER'],
}),
}),
});
and I have the following component which uses this API slice:
login.js
export default function Login() {
const navigate = useNavigate()
const [login, { isError, isSuccess, isLoading, error, data }] = useLoginMutation()
useEffect(() => {
switch (true) {
case isError:
console.log("ERROR!")
break
case isSuccess:
console.log('SUCCESSFULY LOGGED IN!, userdata:', data)
navigate('/', { replace: true })
}
}, [isError, isSuccess])
const formSubmit = (e) => {
e.preventDefault()
const formData = new FormData(e.target)
const email = formData.get('email')
const password = formData.get('password')
login({ email, password })
}
return (
<form onSubmit={formSubmit}>
<TextField name="email" type="email" required />
<TextField name="password" type="password" required />
<button type="subbmit">LOGIN</button>
</form>
)
}
when I input the email and the password on the front-end and click LOGIN I can successfully log in.
but it prints the following in the console:
LOGIN QUERY STARTED
SUCCESSFULY LOGGED IN!
but it does not print METHOD DISPATCH RAN!! which is causing other things in my app to fail, can you help me?

Related

Upload files React.js

Save images in the MongoDB database after uploading from the React.js.
I want to save the file's path(only) along with other data like template's name, package_ids etc...
Following is the templateCreate action:
export const createTemplate = (templateData) => async (dispatch, getState) => {
try {
dispatch({ type: TEMPLATE_CREATE_REQUEST });
const {
adminLogin: { adminInfo },
} = getState();
const config = {
url: "http://localhost:8000/v1/template/create",
method: "POST",
data: templateData,
headers: {
"Content-Type": "application/json",
"x-access-token": adminInfo.data.JWToken,
},
};
const { data } = await axios(config).catch(console.error);
dispatch({ type: TEMPLATE_CREATE_SUCCESS, payload: data });
} catch (error) {
dispatch({
type: TEMPLATE_CREATE_FAILURE,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
The createTempleteReducer function:
export const templateCreateReducer = (state = {}, action) => {
switch (action.type) {
case TEMPLATE_CREATE_REQUEST:
return { loading: true };
case TEMPLATE_CREATE_SUCCESS:
return { loading: false, success: true, template: action.payload };
case TEMPLATE_CREATE_FAILURE:
return { loading: false, error: action.payload };
default:
return state;
}
};
The templateScreen:
return (
<>
<FormContainer>
<h2 className="template-h2">Create New Template</h2>
<Form onSubmit={submitHandler}>
<Form.Group controlId="preview_image">
<Form.Label>Select Preview Image</Form.Label>
<Form.Control
type="file"
accept=".png, .jpg, .jpeg .pdf"
name="preview_image"
/>
</Form.Group>
...other fields
</Form>
</FormContainer>
</>
);
The submitHandler function:
const submitHandler = (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("name", name);
formData.append("package_ids", JSON.stringify(package_ids));
formData.append("preview_image", preview_image);
formData.append("html_template", html_template);
formData.append("required_variables", required_variables);
console.log(preview_image);
const config = {
headers: {
"Content-Type": "application/json",
"x-access-token": adminInfo.data.JWToken,
},
};
dispatch(createTemplate(formData));
};
It saves the data to the database but preview_image is saved as 'C:\\fakepath\\2021_Facebook_icon.jpg' I'm not sure from where the fakepath came. and it is also not uploading the image in the uploads folder
The useEffect hook which currently just lists the package data from the database:
useEffect(() => {
dispatch(listPackages());
}, [dispatch]);
Now what to do next to save all the data to the database using useEffect and createTemplate action function. I am using multer in the backend(Node.js)

How to save current user data using react-redux?

I create simple MERN app. I use passport in authentication. I have home, login, register secret and submit page have. only loginned users can see secret , home and submit pages and only not loginned users can see login and register page. I send data using axios backend. User login,register was succesfully but after loginned users get user data many time after. I dont know how to fix this. My backend doesnt have error. It work well.
-------------- Secret page --------------
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Redirect } from "react-router";
import { user_ } from "../actions/register_action";
const Secret = ()=>{
const dispatch = useDispatch();
const user = useSelector( state => state.user );
useEffect( ()=>{
dispatch( user_ );
},[] );
console.log(user);
if(user === null) return <Redirect to = "/login" />
else
return (
<div className="jumbotron text-center">
<div className="container">
<i className="fas fa-key fa-6x"></i>
<h1 className="display-3">You've Discovered My Secret!</h1>
<p className="secret-text">Jack Bauer is my hero.</p>
<hr />
<a className="btn btn-light btn-lg" href="/logout" role="button">Log Out</a>
<a className="btn btn-dark btn-lg" href="/submit" role="button">Submit a Secret</a>
</div>
</div>
);
}
export default Secret;
-------------- actions --------------
import { fail, user, USER_LOGIN_FAIL, USER_LOGIN_REQUEST, USER_LOGIN_SUCCESS,
USER_REGISTER_FAIL, USER_REGISTER_REQUEST, USER_REGISTER_SUCCESS
} from "../constants/all";
import axios from "axios";
const user_Register = (email, password) => async (dispatch) =>{
dispatch({ type: USER_REGISTER_REQUEST });
try {
const {data} = await axios( {
method: "POST",
data: {
"username": email,
"password": password
},
withCredentials: true,
url: "http://localhost:3001/register"
});
dispatch({ type: USER_REGISTER_SUCCESS, payload: data });
} catch (error) {
dispatch({type: USER_REGISTER_FAIL, error: error.message});
}
}
const user_Login = (email, password) => async (dispatch) =>{
dispatch({ type: USER_LOGIN_REQUEST });
try {
const {data} = await axios( {
method: "POST",
data: {
username: email,
password: password
},
withCredentials: true,
url: "http://localhost:3001/login"
});
dispatch({ type: USER_LOGIN_SUCCESS, payload: data });
} catch (error) {
dispatch({type: USER_LOGIN_FAIL, error: error.message});
}
}
const user_ = async (dispatch) => {
try{
const {data} = await axios({
method: "GET",
withCredentials: true,
url: 'http://localhost:3001'
});
if(data.isAuth){
dispatch({type:user, payload:data});
}
else{
dispatch({type: fail});
}
}
catch (error){
}
}
export {user_Register, user_Login, user_};
Who have better idea? Thanks a lot!
Can you try re-writing your user_ action creator to be a function that returns a function:
const user_ = () => async (dispatch) => {
try {
const { data } = await axios({
method: "GET",
withCredentials: true,
url: "http://localhost:3001",
});
if (data.isAuth) {
dispatch({ type: user, payload: data });
} else {
dispatch({ type: fail });
}
} catch (error) {}
};
And then in your Secret component's useEffect, call the user_ action creator to create the action:
useEffect(() => {
dispatch(user_());
}, []);
I'm not positive this is the issue, but it does seem like something that should be done either way.

called async function from state is not waiting (react)

I call a state function in my component, the function should change the state(and it does but late), i want to log the change but the log triggers before the state is changed
this is the function in the state:
const login = async (user, password) => {
const body = {
username: user,
password: password,
};
await axios
.post('/sessions', body, {
headers: { 'Content-Type': 'application/json' },
})
.then((resp) => {
dispatch({ type: LOGIN, payload: resp.data.data });
})
.catch((err) => {
console.log(err.response.data);
});
};
and this is the call in the component
const onSubmit = (e) => {
e.preventDefault();
login(user, password);
console.log(credes);
};
"credes" is the state for that response, but it keeps printing the initial state witch is an empty object
the function triggers on the form submission but logs first and updates the state later.
As pointed out by bubulledu93, ronakvp and coreyward, I was butchering the syntax. I was trying to perform two actions in one function, so I moved the log into a useEffect to watch for changes in the "credes" hope is the right way but is working as I needed it.
const login = (user, password) => {
const body = {
username: user,
password: password,
};
axios
.post('/sessions', body, {
headers: { 'Content-Type': 'application/json' },
})
.then((resp) => {
dispatch({ type: LOGIN, payload: resp.data });
})
.catch((err) => {
console.log(err.response.data);
});
};
and the call in the component + the useEffect
const onSubmit = (e) => {
e.preventDefault();
login(user, password);
};
useEffect(() => {
if (credes.success) {
console.log(credes.data);
}
}, [credes]);
There isn't any benefit to awaiting as the last call in a function. Instead of using async and await, simply return the Promise chain started by axios.post() to onSubmit and then chain on it (or use await there):
const login = (user, password) => {
const body = {
username: user,
password: password,
};
return axios
.post('/sessions', body, {
headers: { 'Content-Type': 'application/json' },
})
.then((resp) => {
dispatch({ type: LOGIN, payload: resp.data.data });
})
.catch((err) => {
console.log(err.response.data);
});
};
// Option 1:
const onSubmit = (e) => {
e.preventDefault();
login(user, password)
.then(() => {
console.log(credes);
});
};
// Option 2:
const onSubmit = async (e) => {
e.preventDefault();
await login(user, password);
console.log(credes)
}

useSelector doesn't load the state in time

I'm working on a project of mine using React and Redux.
I'm trying to retrieve the token field from my auth state from my store (which contains an authentication key that I intend to send over to my server side) using useSelectorbut the thing is, that it doesn't load in time. How do I fix this?
I'll add below parts of my code that are associated with this issue:
LikeButton.js: (here is the part where I'm trying to retrieve token)
...
const LikeButton = ({ postId }) => {
const classes = useStyles();
const [isLiked, setIsLiked] = useState(false);
const isMount = useIsMount();
const dispatch = useDispatch();
const { token } = useSelector((state) => state.auth);
const { likedPostsIds } = useSelector((state) => state.likes);
useEffect(() => {
if (token) dispatch(fetchLikedPosts(token));
}, [token]);
...
likeActions.js:
...
export const fetchLikedPosts = ({ token }) => (dispatch) => {
fetch("http://localhost:8080/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: fetchLikedPostsQuery(),
})
.then((res) => res.json())
...
authActions.js
...
export const signIn = ({ password, email }) => (dispatch) => {
dispatch({
type: IS_AUTH_LOADING,
});
fetch("http://localhost:8080/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: signInQuery(password, email),
})
.then((res) => res.json())
.then((resData) => {
if (resData.errors) {
dispatch(showNotification("User authentication has failed!", ERROR));
dispatch({
type: AUTH_ERROR,
});
} else {
setLocalStorageAuth(resData.data.signIn.token, expiryDate);
dispatch({
type: SIGN_IN,
payload: resData.data.signIn,
});
}
})
.catch((err) => {
dispatch(
showNotification(
"User authentication has failed. Please try again later.",
ERROR
)
);
dispatch({
type: AUTH_ERROR,
});
emptyLocalStorage();
});
};
...
auth.js:
...
const initialState = {
token: localStorage.getItem("token"),
isAuth: localStorage.getItem("isAuth"),
expiryDate: localStorage.getItem("expiryDate"),
isLoading: false,
};
export default function authReducer(state = initialState, action) {
switch (action.type) {
case REGISTER_LOADING:
case IS_AUTH_LOADING:
return {
...state,
isLoading: true,
};
case SIGN_IN:
return {
token: action.payload.token,
isAuth: true,
isLoading: false,
};
case AUTH_ERROR:
case LOGIN_FAIL:
case REGISTER_FAIL:
case LOGOUT_SUCCESS:
return {
token: null,
isAuth: false,
isLoading: false,
};
case CREATE_USER:
return {
...state,
isLoading: false,
};
default:
return state;
}
}
...
The fetchLikedPosts function expects an object with a token key as an argument. the effect currently passes just the token as an argument. Try
useEffect(() => {
// note the change from token to {token}
if (token) dispatch(fetchLikedPosts({token}));
}, [token]);

showing success and error messages in react/redux app

I'm trying to add toast notifications to my app, one plugin I've been trying to use is react-toastify.
The issue I'm having is probably more a general react/redux issue more than with a plugin such as react-toastify.
I'm using a reducer to set the redux state for errors and success messages, from what I understand with the current code, each error or success message is persistent in the store until another action is called to clear them.
The issue I can't figure out is how do I trigger a toast only once. Eg. I enter the wrong credentials, it creates an error toast, but whenever the state changes and reloads (typing anything into the email or password fields) it creates another toast.
How do I get it to only show once?
userActions.js
function handleErrors(res) {
if (res.ok) {
return res.json();
} else {
return res.json().then(err => {throw err;});
}
}
export const login = (user) => dispatch => {
fetch(`${url}/login`,
{
credentials: 'include',
method: 'post',
body: user,
headers: new Headers({
'Content-Type': 'application/json',
'Accept': 'application/json'
})
})
.then(handleErrors)
.then(res =>
dispatch({
type: LOGIN,
payload: res
})
)
.catch(error =>
dispatch({
type: ERROR,
payload: error
})
)
}
userReducer.js
const initialState = {
errors: '',
success: ''
};
export default function(state = initialState, action) {
switch (action.type) {
case LOGIN:
return {
...state,
errors: '',
success: action.payload.message
};
case ERROR:
return {
...state,
success: '',
errors: action.payload.message
}
default:
return state;
}
}
app.js
app.post('/login', function(req, res) {
... return res.status(500).send({ message: 'Wrong credentials' });
... return res.status(200).send({ message: 'good!' });
});
login.js
class Login extends React.Component {
constructor() {
super();
this.state = {
email: "",
password: ""
}
}
handleChange = event => {
this.setState({
[event.target.id]: event.target.value
});
}
render() {
const { errors, login, success } = this.props;
if (success !== '') toast.success(success, {
position: toast.POSITION.TOP_CENTER
});
if (errors !== '') toast.error(errors, {
position: toast.POSITION.TOP_CENTER
});
return (
<div>
<input type="text" id="email" placeholder="Email Address" onChange={this.handleChange} />
<input type="password" id="password" placeholder="Password" onChange={this.handleChange} />
<button onClick={() => login(JSON.stringify({email: this.state.email, password: this.state.password}))}>Log In</button>
<ToastContainer />
</div>
)
}
}
const mapStateToProps = state => ({
errors: state.store.errors,
success: state.store.success
});
export default connect(mapStateToProps, {login})(Login);
You're calling toast.success or toast.error inside render which makes a new toast pop up every time you re-render the component.
The solution is simple. Move your toast calls outside render, where they will only be called once.
One way to achieve this is to return a value from your userAction.
export const login = (user) => dispatch => {
return new Promise((resolve, reject) => {
fetch(`${url}/login`,
{
credentials: 'include',
method: 'post',
body: user,
headers: new Headers({
'Content-Type': 'application/json',
'Accept': 'application/json'
})
})
.then(handleErrors)
.then(res => {
dispatch({
type: LOGIN,
payload: res
})
resolve(res)
}
)
.catch(error => {
dispatch({
type: ERROR,
payload: error
})
reject(error)
}
)
}
}
Then use that value to toast in login.js.
class Login ... {
...
loginUser = () => {
this.props.login(JSON.stringify({email: this.state.email, password: this.state.password}))
.then(res => {
toast.success(res.message, { position: toast.POSITION.TOP_CENTER })
}
).catch(error => {
toast.error(error.message, { position: toast.POSITION.TOP_CENTER })
}
)
}
...
render() {
return (
...
<button onClick={this.loginUser}>Log In</button>
...
)
}
}
There are other ways to achieve the same functionality and depending on the structure of your project, you may want to toast in a more generalized way.

Resources