React search option from API needs running twice? - javascript

When connecting to the Spotify API through a React app I enter a term to the search box and it loads fine but refreshes the page immediately. Upon searching a second time it works perfectly fine? Is this an obvious error I am missing?
I have included the code below and appreciate any help
Spotify Fetch
const clientId = '//my api key//';
const redirectUri = 'http://localhost:3000/';
let accessToken;
getAccessToken() {
if(accessToken) {
return accessToken;
}
const hasAccessToken = window.location.href.match(/access_token=([^&]*)/);
const hasExpiresIn = window.location.href.match(/expires_in=([^&]*)/);
if (hasAccessToken && hasExpiresIn) {
accessToken = hasAccessToken[1];
const expiresIn = Number(hasExpiresIn[1]);
window.setTimeout(() => accessToken = '', expiresIn * 1000);
window.history.pushState('Access Token', null, '/');
return accessToken;
} else {
const accessUrl = `https://accounts.spotify.com/authorize?client_id=${clientId}&response_type=token&scope=playlist-modify-public&redirect_uri=${redirectUri}`;
window.location = accessUrl;
}
},
Spotify Search
search(term) {
const accessToken = Spotify.getAccessToken();
return fetch(`https://api.spotify.com/v1/search?type=track&q=${term}`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
}).then(
response => {
if (response.ok) {
return response.json();
} else {
console.log('API request failed');
}
}).then(
jsonResponse => {
if(!jsonResponse.tracks) {
return [];
}
return jsonResponse.tracks.items.map(track => ({
id: track.id,
name: track.name,
artist: track.artists[0].name,
album: track.album.name,
uri: track.uri,
preview: track.preview_url,
art: track.album.images[2].url
}));
});
},
Search Component
import React from 'react'
import './searchbar.css';
class Searchbar extends React.Component {
constructor(props) {
super(props)
this.state = { term: ''}
this.search = this.search.bind(this)
this.handleTermChange = this.handleTermChange.bind(this)
}
search() {
this.props.onSearch(this.state.term)
}
handleTermChange(e) {
this.setState( {term: e.target.value} )
}
render() {
return <div className="SearchBar">
<input placeholder="Enter A Song, Album, or Artist" onChange = {this.handleTermChange} />
<button className="SearchButton" onClick = {this.search}>SEARCH</button>
</div>
}
}
export default Searchbar;

Related

How to pass State in Context React and use this an another components

I am trying this code with useContext. i want to take this response.data.message in Editdata.js component. how did i do? how i will send this state message using context.
auth-context.js
const AuthContext = React.createContext({
updatePlayer: () => {},
});
export const AuthContextProvider = (props) => {
const [msg, setmsg] = useState();
const playerupdate = async (updatedPlayer) => {
const { id, player_fname, player_lname, player_nickname } = updatedPlayer;
await axios
.post(
"https://scorepad.ominfowave.com/api/adminPlayerUpdate",
JSON.stringify({ id, player_fname, player_lname, player_nickname }),
{
headers: { "Content-Type": "application/json" },
}
)
.then((response) => {
fetchPlayerList()
//navigate('/view-players');
setmsg(response.data.message)
})
.catch((error) => {
alert(error);
});
};
return (
<AuthContext.Provider
value={{
updatePlayer: playerupdate
}}
>
{props.children}
</AuthContext.Provider>
);
};
type here
Editdata.js
function Editdata() {
const authcon = useContext(AuthContext);
const submitupdateForm = (e) => {
e.preventDefault();
const playerdetail = {
player_fname: firstname,
player_lname: lastname,
player_nickname: nickname,
id: empid
}
authcon.updatePlayer(playerdetail)
}
return (
<form onSubmit={submitupdateForm}>
</form>
);
}
How is the correct way to pass state between components with useContext?

I got a rejection error from the redux toolkit while trying to update the item

I am working on a MERN app and I have a problem when updating items. I am getting rejections when sending a patch request and there is not much info for debugging to solve the problem. I will appreciate it if someone can point out some logic that is not correct in my code. Thank you in advance.
Here below is the logic I have implemented.
postService.js:
import axios from 'axios';
const API_URL = '/api/posts/';
const updatePost = async (postId, postData, token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
const response = await axios.patch(`${API_URL}/${postId}/`, postData, config);
if (response.data) {
return {
...response.data,
id: postId,
};
}
};
postSlice.js:
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
import postService from './postService';
const initialState = {
posts: [],
isError: false,
isSuccess: false,
isLoading: false,
message: '',
};
export const updatePost = createAsyncThunk(
'posts/updatePost',
async (id, postData, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await postService.updatePost(id, postData, token);
} catch (error) {
const message =
(error.response.data.message) ||
error.toString();
return thunkAPI.rejectWithValue(message);
}
}
);
export const postSlice = createSlice({
name: 'post',
initialState,
reducers: {
reset: (state) => initialState,
},
extraReducers: (builder) => {
builder
.addCase(updatePost.pending, (state) => {
state.isLoading = true;
})
.addCase(updatePost.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.posts = state.posts.map((post) =>
post.id === action.payload.id ? action.payload : post
);
})
.addCase(updatePost.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
})
});
},
});
export const selectAllPosts = (state) => state.posts.posts;
export const { reset } = postSlice.actions;
export default postSlice.reducer;
Form.js:
const Form = ({ postId, setPostId }) => {
const [formData, setFormData] = useState({
postCreator: '',
title: '',
body: '',
imageFile: '',
});
const dispatch = useDispatch();
const user = JSON.parse(localStorage.getItem('user'));
const post = useSelector((state) =>
postId ? state.posts.posts.find((post) => post._id === postId) : null
);
useEffect(() => {
if (post) setFormData(post);
}, [post]);
const clearPost = () => {
setPostId(0);
setFormData({
postCreator: '',
title: '',
body: '',
imageFile: '',
});
};
const handleSubmit = async (e) => {
e.preventDefault();
if (
!formData.postCreator &&
!formData.title &&
!formData.body &&
!formData.imageFile
) {
toast.warning(
'Please fill out all fields, and make sure you are also logged in'
);
} else if (postId) {
dispatch(updatePost(postId, formData));
console.log(postId);
} else {
dispatch(createPost(formData));
clearPost();
setPostId(null);
}
clearPost();
};
The second param of createAsyncThunk is the payloadCreator.
The first param of the payloadCreator is the arguments. The second param of payloadCreator is thunkAPI.
So you should combine id and postData into a single object to represent the arguments.
Update postSlice.js:
export const updatePost = createAsyncThunk(
'posts/updatePost',
async ({id, postData}, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await postService.updatePost(id, postData, token);
} catch (error) {
const message =
(error.response.data.message) ||
error.toString();
return thunkAPI.rejectWithValue(message);
}
}
);
Update where you dispatch the updatePost thunk:
updatePost({
id: 123,
postData: {
foo: 'bar'
}
})

ReactJS: Unable to retrieve properly localStorageItem after navigate to another component

I have this scenario that is after the user login and assuming it is success, user details / user token is stored to localStorage and will automatically navigate to dashboard page, dashboard page has some api calls and those api calls required/needs token that is stored in the localStorage, my problem is that it is unable to retrieve those values in localStorage, but when I check from localStorage using console, the key/value is there, I noticed that, I need to refresh the page to retrieve those details without a problem. How can I possibly fix this issue? to be able to get localStorage value after navigating to another component?
Here is my code for index.tsx
ReactDOM.render(
<AuthContextProvider>
<App />
</AuthContextProvider>,
document.getElementById("root")
);
AuthContext code:
const AuthContext = React.createContext({
user: "",
isLoggedIn: false,
login: (userdata: any, expirationTime: string) => {},
logout: () => {},
});
export const AuthContextProvider = (props: any) => {
const initialUser = localStorage.getItem("user") || "";
const [userData, setUserData] = useState(initialUser);
const userIsLoggedIn = !!userData;
const logoutHandler = () => {
setUserData("");
localStorage.removeItem("user");
};
const loginHandler = async (
user: any,
expirationTime: string
) => {
localStorage.setItem("user", JSON.stringify(user));
setUserData(user);
};
const contextValue = {
user: userData,
isLoggedIn: userIsLoggedIn,
login: loginHandler,
logout: logoutHandler,
};
return (
<AuthContext.Provider value={contextValue}>
{props.children}
</AuthContext.Provider>
);
};
export default AuthContext;
App.tsx code
function App() {
const authCtx = useContext(AuthContext);
return (
<BrowserRouter>
<Routes>
{!authCtx.isLoggedIn && (
<Route element={<LoginLayout />}>
<Route index element={<SignInForm />} />
{/*Other links here */}
</Route>
)}
{authCtx.isLoggedIn && (
<Route element={<AdminLayout />}>
<Route path="dashboard" element={<DashboardScreen />} />
{/*Other links here */}
</Route>
)}
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
);
}
Login code:
try {
await AuthService.login(email, password).then(
(res) => {
authCtx.login(res, "0");
navigate("../dashboard", { replace: true });
},
(error) => {
}
);
} catch (err) {
console.log(err);
}
Dashboard code:
const loadCountOnlineUsers = useCallback(async () => {
try {
await DashboardService.loadCountOnlineUsers().then(
(res) => {
setCntOnlineUsers(res.count);
setflagOnlineUsers(false);
},
(error) => {
setflagOnlineUsers(false);
}
);
} catch (err) {
console.log(err);
setflagOnlineUsers(false);
}
}, [setCntOnlineUsers, setflagOnlineUsers]);
useEffect(() => {
loadCountOnlineUsers();
}, [loadCountOnlineUsers]);
Dashboard service code:
const config = {
headers: {
"Content-Type": "application/json",
Authorization: AuthHeader(),
},
params: {},
};
const loadCountOnlineUsers = () => {
config["params"] = {};
return axios
.get(API_URL + "api/v1/dashboard-related/online-users", config)
.then((response) => {
return response.data;
});
};
const DashboardService = {
loadCountOnlineUsers,
};
export default DashboardService;
Auth-header code:
export default function AuthHeader() {
const user = JSON.parse(localStorage.getItem("user") || "{}");
if (user && user.token) {
return "Bearer " + user.token;
} else {
return "";
}
}
The problem is that the check to localStorage in AuthHeader() isn't updating reactively. The fix would be to rewrite AuthHeader to accept the user data like this:
export default function AuthHeader(user) {
const user = JSON.parse(user || "{}");
if (user && user.token) {
return "Bearer " + user.token;
} else {
return "";
}
}
and then continue the data piping into the area where AuthHeader() is called, perhaps like this:
const config = (user) => ({
headers: {
"Content-Type": "application/json",
Authorization: AuthHeader(),
},
params: {},
});
const loadCountOnlineUsers = (user) => {
config["params"] = {};
return axios
.get(API_URL + "api/v1/dashboard-related/online-users", config(user))
.then((response) => {
return response.data;
});
};
const DashboardService = {
loadCountOnlineUsers,
};
Lastly, using an effect in the dashboard to update it reactively, while connecting to context:
const authCtx = useContext(AuthContext);
const user = authCtx.user;
const loadCountOnlineUsers = (user) => {
return useCallback(async () => {
try {
await DashboardService.loadCountOnlineUsers(user).then(
(res) => {
setCntOnlineUsers(res.count);
setflagOnlineUsers(false);
},
(error) => {
setflagOnlineUsers(false);
}
);
} catch (err) {
console.log(err);
setflagOnlineUsers(false);
}
}, [setCntOnlineUsers, setflagOnlineUsers]);
}
useEffect(() => {
loadCountOnlineUsers(user);
}, [loadCountOnlineUsers, user]);

React setState callback won't update state

So I have 3 functions below. One containing calls to the two (getBooks), which are getting requests. I set my state (isLoading) to true before the calls and then to true after the calls. This is to also make sure that the data is properly loaded. However the state is not updating so, therefore, my data from the get request is invalid. The callbacks in my setstate work in my other components, so I am confused. Below are my 3 functions.
import React from 'react';
import ReactDOM from 'react-dom';
import SidePane from './SidePane.js';
import HomeNavBar from './HomeNavBar.js';
import axios from 'axios';
import qs from 'qs';
import Loading from './Loading.js';
class HomePage extends React.Component {
constructor(props) {
super(props);
this.state = {
bookSearch: "",
bookSearchResults: [],
bookSearchFound: false,
isLoading: false
};
this.handleSearch = this.handleSearch.bind(this);
this.alertBookName = this.alertBookName.bind(this);
this.getBooksFromIsbn = this.getBooksFromIsbn.bind(this);
this.getBooks = this.getBooks.bind(this);
axios.defaults.withCredentials = true;
}
changeBookName = (e) => {
var bookName = e.target.value;
bookName = bookName.split(' ').join('+');
this.setState({bookSearch: bookName})
}
getBooksFromIsbn(isbns){
var books = [];
axios
.get('http://localhost:9000/api/getBooksFromIsbn',
{
params: {
books: JSON.stringify(isbns)
}
})
.then(res =>
{
console.log(res.data);
books = res.data;
})
.catch(error => {
console.log(error.response);
});
}
getBooks(){
this.setState({
isLoading: true
},
function(){console.log("setState completed", this.state)}
);
var bookResults = this.handleSearch();
var books = this.getBooksFromIsbn(bookResults);
this.setState({
isLoading: false
},
function(){console.log("setState completed", this.state)}
);
this.props.setBookSearchResults(books);
}
handleSearch(){
var bookResults = [];
var url = 'http://localhost:9000/api/getOpenLibrarySearch';
axios
.get(url,
{
params: {
bookSearch: this.state.bookSearch
}
})
.then(res =>
{
//this.setState({bookSearchResults: res.data});
for(var i=0; i < res.data.docs[i].isbn.length; i++){
bookResults = bookResults.concat(res.data.docs[i].isbn);
}
console.log(bookResults);
})
.catch(error => {
console.log(error.response);
});
return bookResults;
}
render(){
if(this.state.isLoading == false){
return(
<div>
<HomeNavBar authToken = {this.props.authToken} email = {this.props.email} />
<SidePane changeBookName = {this.changeBookName} handleSearch = {this.getBooks} />
</div>
)
}
else
{
return <Loading />;
}
}
}
It looks like you need to actually return the books value from getBooksFromIsbn. Await the axios call resolution and return books.
async getBooksFromIsbn (isbns) {
const books = [];
try {
const res = await axios.get(
'http://localhost:9000/api/getBooksFromIsbn',
{
params: {
books: JSON.stringify(isbns)
}
});
books = res.data;
} catch(error) {
console.log(error.response);
}
return books;
}
Same for handleSearch, the code needs to wait and return the resolution of the GET request.

Paypal Checkout button with React not letting signed in

I am having issues integrating the Paypal with my react app using sandbox. When I click on the Button, a pop-up of PayPal opens and when I put in my credentials to log in, I get the following error:
I am able to see the sign in form, but it just won't let me sign in and instead I come to see that message.
App.js
import PaypalButton from './PaypalButton';
const CLIENT = {
sandbox: 'xxxxx',
production: 'xxxxx',
};
const ENV = process.env.NODE_ENV === 'production' ? 'production' : 'sandbox';
render() {
const onSuccess = (payment) =>
console.log('Successful payment!', payment);
const onError = (error) =>
console.log('Erroneous payment OR failed to load script!', error);
const onCancel = (data) =>
console.log('Cancelled payment!', data);
return(
<div>
<PaypalButton
client={CLIENT}
env={ENV}
commit={true}
currency={'USD'}
total={500.00}
onSuccess={onSuccess}
onError={onError}
onCancel={onCancel}
/>
</div>
)
}
PaypalButton
import React from 'react';
import ReactDOM from 'react-dom';
import scriptLoader from 'react-async-script-loader';
class PaypalButton extends React.Component {
constructor(props) {
super(props);
this.state = {
showButton: false,
};
window.React = React;
window.ReactDOM = ReactDOM;
}
componentDidMount() {
const {
isScriptLoaded,
isScriptLoadSucceed
} = this.props;
if (isScriptLoaded && isScriptLoadSucceed) {
this.setState({ showButton: true });
}
}
componentWillReceiveProps(nextProps) {
const {
isScriptLoaded,
isScriptLoadSucceed,
} = nextProps;
const isLoadedButWasntLoadedBefore =
!this.state.showButton &&
!this.props.isScriptLoaded &&
isScriptLoaded;
if (isLoadedButWasntLoadedBefore) {
if (isScriptLoadSucceed) {
this.setState({ showButton: true });
}
}
}
render() {
const {
total,
currency,
env,
commit,
client,
onSuccess,
onError,
onCancel,
} = this.props;
const {
showButton,
} = this.state;
const payment = () =>
paypal.rest.payment.create(env, client, {
transactions: [
{
amount: {
total,
currency,
}
},
],
});
const onAuthorize = (data, actions) =>
actions.payment.execute()
.then(() => {
const payment = {
paid: true,
cancelled: false,
payerID: data.payerID,
paymentID: data.paymentID,
paymentToken: data.paymentToken,
returnUrl: data.returnUrl,
};
onSuccess(payment);
});
return (
<div>
{showButton && <paypal.Button.react
env={env}
client={client}
commit={commit}
payment={payment}
onAuthorize={onAuthorize}
onCancel={onCancel}
onError={onError}
/>}
</div>
);
}
}
export default scriptLoader('https://www.paypalobjects.com/api/checkout.js')(PaypalButton);
Can someone please help me solve this issue?
I had the same issue last week. After working for a while, the sandbox started giving me that error. I reverted all my commits to ensure it wasn't an issue with my code. After a day or two, it started to work again.
Seems it was an issue with PayPal's sandbox environment. (Apparently it happens to the best of us).
If you had been sending incorrect data, you would have seen a console.log of the error.

Categories

Resources