How to use react/tanstack query useMutation in my component - javascript

I'm currently converting the logic in my mern (with typescript) project to use React/Tanstack query to learn this tool better.
I want to use useMutation to handle the post request logic from the details inputted in the form, in this login component but can't figure out how to do this. Any tips would be appreciated thanks. Below is the code from my login component
const Login = () => {
const navigate = useNavigate();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [errorMsg, setErrorMsg] = useState("");
const [state, setState] = useContext(UserContext);
const handleSubmit = async (e: { preventDefault: () => void }) => {
e.preventDefault();
let response;
const { data: loginData } = await axios.post("http://localhost:5001/auth/login", {
email,
password,
});
response = loginData;
if (response.errors.length) {
return setErrorMsg(response.errors[0].msg);
}
setState({
data: {
id: response.data.user.id,
email: response.data.user.email,
stripeCustomerId: response.data.user.stripeCustomerId,
},
loading: false,
error: null,
});
localStorage.setItem("token", response.data.token);
axios.defaults.headers.common["authorization"] = `Bearer ${response.data.token}`;
navigate("/dashboard");
};
return (
<div className="login-card">
<div>
<h3>Login</h3>
</div>
<form onSubmit={handleSubmit}>
<div className="login-card-mb">
<label>Email</label>
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
</div>
<div className="login-card-mb">
<label>Password</label>
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
</div>
{errorMsg && <p>{errorMsg}</p>}
<button type="submit">Submit</button>
</form>
</div>
);
};

After setting up your project to use React Query ( Check the docs if you have not). You want to extract your api call to a separate function that takes an object. This object will hold the values you would like to post.
const Login = (dataToPost) => {
let res = await axios.post('url', dataToPost)
return res.data
}
Now that you have that, you can import useMutation from React Query. Once imported you can now use the hook. UseQuery, useMutation both contain a data variable so no need to create state for the data returned from your endpoint. In this example, I'm deconstructing the data and loading state. But most importantly the mutate function. Which allows you to fire off your api call. We add our api call to the hook. I'm renaming the mutate function to doLogin. It's a habit
const {data,isLoading,mutate:doLogin} = useMutation(Login)
Finally we can just call mutate(objectWithValues) wherever you want in your code. The data will initially be null and isLoading will be true once called. To tie it all together. Your handleSubmit could look as follows
const handleSubmit = () => {
e.preventDefault();
doLogin({email,password})
}
You also have the option of running functions on a success or error of the mutation
const {data,isLoading,mutate: doLogin} =
useMutation(Login, {
onError: (err) => console.log("The error",err),
onSuccess:(someStuff)=>console.log("The data being returned",someStuff)
})

Related

Cannot properly update frontend on rerender reactjs using useEffect

So I was making a blog application where only logged in users can add new blogs. To start, when a user logs in, they will see all the blogs they have previously created on the frontend along with a form to add new ones. However, when the logged in user tries to add a new blog, it updates on the frontend but returns back to the original list they had before when the page is refreshed. I can see the updated blog list when I log out and log back in again. I actually used local storage to make sure that logged in users remain logged in after a new render. I just need help in making sure the new blogs added after login remain on the frontend after a render.
I think I have an idea why it is happening but I am not sure.
So whenever my loginService function is called within the handleLogin function, the server sends back the user info which includes all the blogs they have created. The problem with refreshing is due to the same list of blogs that were there at the time of login unless you log out and log in again.
Any help would be greatly appreciated.
ReactJS code
import { useState, useEffect } from 'react'
import Blog from './components/Blog'
import blogService from './services/blogs'
import loginService from './services/login'
import userService from './services/user'
const App = () => {
const [blogs, setBlogs] = useState([])
const [newBlogs, setNewBlogs] = useState([])
const [user, setUser] = useState(null)
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [errorMsg, setErrorMsg] = useState('')
const [blogTitle, setBlogTitle] = useState('')
const [blogAuthor, setBlogAuthor] = useState('')
const [blogUrl, setBlogUrl] = useState('')
useEffect( () => {
if(user != null){
setBlogs(user.blog.concat(newBlog))
}
console.log("blogs is", blogs)
}, [user])
//Seeing if a user is logged in on rerender
useEffect(() => {
const loggedInUser = window.localStorage.getItem('loggedBlogUser')
if(loggedInUser){
const user = JSON.parse(loggedInUser)
setUser(user)
}
},[])
// Logging in users
const handleLogin = async (event) => {
event.preventDefault()
console.log("Logging in,", username, password)
try {
const user = await loginService({username, password})
blogService.setToken(user.token)
window.localStorage.setItem('loggedBlogUser', JSON.stringify(user))
setUser(user)
setUsername('')
setPassword('')
}
catch(error){
setErrorMsg('Wrong credentials')
setTimeout(() => {
setErrorMsg(null)
},[])
}
}
//Logging out users
const handleLogout = () => {
window.localStorage.removeItem('loggedBlogUser')
setUser(null)
setBlogs([])
}
//Adding new blogs
const addNewBlog = async (e) => {
e.preventDefault()
console.log("User here is", user)
try {
const newBlog = {
title: blogTitle,
author: blogAuthor,
url: blogUrl
}
await blogService.createBlog(newBlog)
setBlogs(blogs.concat(newBlog))
setNewBlogs(newBlogs.concat(newBlog))
setBlogTitle('')
setBlogAuthor('')
setBlogUrl('')
}
catch(error){
console.log("error adding new blog", error)
}
console.log("blogs is", blogs)
}
return (
<div>
<h2>blogs</h2>
{user == null && <div className="login-form">
<form onSubmit={handleLogin}>
<div className="username-container">
username
<input type='text' value={username} onChange={(e) => setUsername(e.target.value)} name='username'/>
</div>
<div className="password-container">
password
<input type='password' value={password} onChange={(e) => setPassword(e.target.value)} name='password'/>
</div>
<button type='submit'>Login</button>
</form>
</div>}
{user != null && <div className="notes">
<p>{user.name} logged in <button onClick={handleLogout}>logout</button></p>
</div>}
{user != null && <div className="addBlog-container">
<b>create new</b>
<form onSubmit={addNewBlog}>
<label>Title:</label><input type="text" value={blogTitle} onChange={(e) => setBlogTitle(e.target.value)} name="blog-title"/>
<label>Author:</label><input type="text" value={blogAuthor} onChange={(e) => setBlogAuthor(e.target.value)} name="blog-author"/>
<label>Url:</label><input type="text" value={blogUrl} onChange={(e) => setBlogUrl(e.target.value)} name="blog-url"/>
<button type='submit'>create blog</button>
</form>
</div>}
{blogs != null && blogs.map(blog =>
<Blog key={blog.id} blog={blog} />
)}
</div>
)
}
export default App
At first you are updating blogs from user.blog but in update blog you are only updating blogs variable, that's why new blog disappears as soon as you refresh. Try after updating user.blog with new blog.

TypeError: Cannot read properties of undefined (reading 'data') console.log(data) isnt returning user information

So basically I'm making a login function in React and I've made users using api I've stored the users in my MongoDB database and I'm getting no coding errors in my terminal I now have tried to login to one of the accounts and check the console on my browser and I keep getting back the error Cannot read properties of undefined (reading 'data').
Its saying that my console.log(data) isn't reading any properties and I'd appreciate some help on how i can fix this I'll paste down the code below to show what I've done
I need the console.log(data) to show the user which I log into information once I've logged in that should appear in the console but the error which I've trying to resolve isn't allowing it
import axios from 'axios';
import React, { useState } from 'react';
import { Col, Container, Row, Form, Button } from "react-bootstrap";
import './Login.css'
export const Login = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const submitHandler = async (e) => {
e.preventDefault();
try {
const config = {
headers: {
"Content-type": "application/json"
},
};
setLoading(true)
const { data } = await axios.post(
"/api/users/login",
{
email,
password,
},
config
);
//Here is the console.log which isnt returning the users info in my console
console.log(data);
localStorage.setItem('userInfo', JSON.stringify(data));
setLoading(false);
} catch (error) {
setError(error.response.data);
}
};
return (
<Form onSubmit={submitHandler}>
<Form.Group controlId="formBasicEmail">
<Form.Label>Email address</Form.Label>
<Form.Control
type="email"
value={email}
placeholder="Enter email"
onChange={(e) => setEmail(e.target.value)}
/>
</Form.Group>
<Form.Group controlId="formBasicPassword">
<Form.Label>Password</Form.Label>
<Form.Control
type="password"
value={password}
placeholder="Password"
onChange={(e) => setPassword(e.target.value)}
/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
);
};
export default Login;
Try the following, without async/ await.
axios.post("api/users/login", { email, password, },config)
.then(res=>res.data)
.then(data=> {
console.log(data);
localStorage.setItem('userInfo', JSON.stringify(data));
setLoading(false);
})
.catch(error => {
setError(error)
})
I had the same error,
in my project I was using axios in an async function with await command as below
(bmUserApi is an api library which I coded myself over axios )
onLogin = async(formValues) => {
this.setState({loading:true, error: ""});
try {
var ls_response = await bmUserApi.login(formValues);
this.setState({loading: false});
const lv_token = ls_response.headers['x-auth-token'];
Redux_Set_User(lv_token, this.props.redux_dispatch, this.props.history);
when I was checking the error position on Google Chrome in "Call Stack" part, I've seen that my api library was trying add to authentication token to the api call header, by reading from localStorage of the browser.
I was doing it by using axios interceptors as below :
axiosClient.interceptors.request.use(
async function(config) {
config.headers = {
'Content-Type': 'application/json'
}
// token :
var ls_user = JSON.parse(localStorage.getItem('user'));
const authToken = ls_user.token;
if (authToken)
config.headers['x-auth-token'] = authToken;
return config;
},
error => { Promise.reject(error) }
)
But this is login call .. so off course there is no stored data and token on the browser yet. So the "ls_user" variable in the above code was null .. this was causing the error. I just added control before that.
I hope this can be useful to your case.

React useEffect onClick Refetch Data - Change Params

How do I add a click handler to refetch data from my API based on my input ON CLICK?
In my console I'm getting back data if I input "Jon Snow" for instance because the onChange set to e.target.value but not sure how to fetch this on button click.
Code Sandbox: https://codesandbox.io/s/pedantic-lichterman-4ev6f?file=/src/game.jsx
import React, { useEffect, useState } from "react";
import axios from "axios";
export default function Game() {
const [error, setError] = useState(null);
const [name, setName] = useState("");
const handleSubmit = e => {
e.preventDefault();
console.log( name );
}
const handleClick = e => {
// ??
}
useEffect(() => {
fetch(`https://anapioficeandfire.com/api/characters?name=${name}`)
.then((res) => res.json())
.then((data) => {
console.log(data[0].name); // the data I want back
})
.catch((error) => {
console.log("Error", error);
setError(error);
});
}, [name]);
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
/>
<input type="submit" value="Submit" onClick={handleClick}/>
</form>
);
}
When the Submit button is clicked it will trigger onSubmit event, no need for you to handle the onClick event separately.
import React, { useEffect, useState } from "react";
import axios from "axios";
export default function Game() {
const [error, setError] = useState(null);
const [name, setName] = useState("");
const handleSubmit = e => {
e.preventDefault();
console.log( name );
fetchData(name);
}
const fetchData = (name) => {
fetch(`https://anapioficeandfire.com/api/characters?name=${name}`)
.then((res) => res.json())
.then((data) => {
console.log(data[0].name); // the data I want back
})
.catch((error) => {
console.log("Error", error);
setError(error);
});
}
useEffect(() => {
fetchData(name);
}, []);
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
/>
<input type="submit" value="Submit" onClick={handleClick}/>
</form>
);
}
Add another stateful variable. You need not only a value and setter for the input value but also a value and setter for the API results you want to be able to use elsewhere. Maybe something like
const [searchText, setSearchText] = useState('');
const [result, setResult] = useState('');
// inside fetch callback:
setResult(data[0]?.name ?? ''); // use optional chaining to not throw an error
// if there is no result
<input
type="text"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
placeholder="Name"
/>
And then you can use the result where you need.
Live demo:
const App = () => {
const [error, setError] = React.useState(null);
const [searchText, setSearchText] = React.useState('');
const [result, setResult] = React.useState('');
const handleSubmit = e => {
e.preventDefault();
console.log( name );
}
React.useEffect(() => {
fetch(`https://anapioficeandfire.com/api/characters?name=${searchText}`)
.then((res) => res.json())
.then((data) => {
setResult(data[0] ? data[0].name : '');
})
.catch((error) => {
console.log("Error", error);
setError(error);
});
}, [searchText]);
console.log(result);
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
placeholder="Name"
/>
<input type="submit" value="Submit" onClick={e => e.preventDefault()}/>
</form>
);
}
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>
You can use direct state variable [name] in handleClick function.
The other answers are all correct that you should trigger the fetch in your handleSubmit. I just wanted to chime in with some sample code for rendering results since you asked for help with that.
The API returns an array of characters. We want to map through that result and show each character. We also want to tell the user if there were no results (especially since this API seems to only work with an exact name and will return a result for "Arya Stark" but not for "Stark"). We don't want to show that "No Characters Found" message before they have submitted.
I am using a setState hook to store the array of character matches from the API. I am initializing the state to undefined instead of [] so that we only show the no results message if it gets set to [].
My code allows the user to submit multiple times. We keep displaying the previous results until they submit a new search. Once we have an array in our characters state, we display those results.
// an example component to render a result
const RenderCharacter = ({ name, aliases }) => {
return (
<div>
<h2>{name}</h2>
{aliases.length && (
<div>
<h3>Aliases</h3>
<ul>
{aliases.map((a) => (
<li key={a}>{a}</li>
))}
</ul>
</div>
)}
</div>
);
};
export default function Game() {
// current form input
const [name, setName] = useState("");
// save characters returned from the API
// start with undefined instead of empty array
// so we know when to show "no characters found" message
const [characters, setCharacters] = useState();
// store API errors
const [error, setError] = useState(null);
const fetchData = () => {
fetch(`https://anapioficeandfire.com/api/characters?name=${name}`)
.then((res) => res.json())
.then(setCharacters) // store data to state
.then(() => setError(null)) // clear previous errors
.catch((error) => {
console.log("Error", error);
setError(error);
setCharacters(undefined); // clear previous character matches
});
};
const handleSubmit = (e) => {
e.preventDefault();
fetchData();
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
/>
<input type="submit" value="Submit" />
</form>
{characters !== undefined &&
(characters.length === 0 ? (
<div>No Characters Found</div>
) : (
<div>
{characters.map((character) => (
<RenderCharacter key={character.name} {...character} />
))}
</div>
))}
{error !== null && <div>Error: {error.message}</div>}
</div>
);
}
Code Sandbox Demo (with typescript annotations)

React Axios Input Undefined

I must post {input} data to http://localhost:4000/prediction with Axios. But {input} turns undefined.
I am using const instead of class Main extends component. onChange, it sets form data.
const Main = ({ value, suggestions, auth: { user } }) => {
const [formData, setFormData] = useState("");
const [messages, setMessages] = useState([]);
const { input } = formData;
const onChange = e => setFormData(e.target.value);
const onSubmit = event => {
event.preventDefault();
setMessages(prevMsgs => [...prevMsgs, formData]);
console.log({ input });
Axios post.
axios
.post(
`http://localhost:4000/prediction`,
{ input },
{ crossdomain: true }
)
.then(res => {
console.log(res.data);
//setMessages(prevMsgs => [...prevMsgs, formData]);
})
.catch(error => {
console.log(error.message);
});
};
Return (form) with onSubmit, onChange.
return (
<div className="true">
<br />
<form noValidate onSubmit={e => onSubmit(e)}>
<div className="input-group mb-3">
<input
name="input"
type="text"
className="form-control"
placeholder="Type text"
onChange={e => onChange(e)}
/>
)}
<div className="input-group-append">
<button className="btn btn-outline-secondary">Send</button>
</div>
</div>
</form>
</div>
);
};
As I have mentioned in the comment section formData is a string as I see which does not have a property called input what you try to destructure and that's why it is undefined always.
If you really need that format for axios then you can try change the structure of formData with useState as the following first:
const [formData, setFormData] = useState({input: null});
Then maybe you can try updating as:
const onChange = e => setFormData({input: e.target.value});
I hope that helps!

React hooks callback receives outdated state

Trying out react hooks on a simple search component. The idea is simple: user types symbols, every typed symbol initiates api query.
To achieve that I have useState and useCallback hooks like in the code below:
const Search = () => {
const [query, setQuery] = useState("");
const sendRequest = useCallback(() => {
console.log('sendRequest ', query);
}, [query]);
return (
<div>
<input
type="text"
value={query}
placeholder="Search"
onChange={e => {
console.log('onChange ', e.target.value);
setQuery(e.target.value);
sendRequest();
}}
/>
</div>
}
The result is that sendRequest method always gets a previous version of query.
onChange q
sendRequest
onChange qu
sendRequest q
onChange que
sendRequest qu
Why is that? I assume that this is not how the hooks are supposed to be used, but I can't figure that out from the documentation.
setState is asynchronous!
At the time you send sendRequest, the local state is not updated, because it is asynchronous and it needs some time to get set.
You should either give the string as a parameter into the function or useEffect and listen to changes of query.
Exchanging useCallback with useEffect and removing the call in onChange should work.
const Search = () => {
const [query, setQuery] = useState("");
useEffect(() => {
console.log('sendRequest ', query);
}, [query]);
return (
<div>
<input
type="text"
value={query}
placeholder="Search"
onChange={e => {
setQuery(e.target.value);
}}
/>
</div>
}
Use useEffect instead useCallback. useEffect fires your callback function when query changes.
useEffect(() => { console.log(query) }, [query])
hey bro you can try this implementation its works as you expect
const [query, setQuery] = useState("");
const sendRequest = e => {
setQuery(e);
console.log('sendRequest ', e);
};
return (
<div>
<input
type="text"
value={query}
placeholder="Search"
onChange={e => {
console.log('onChange ', e.target.value);
sendRequest(e.target.value);
}}
/>
</div>)

Categories