React Redux components with REST Endpoints and reusable components - reactjs

I am working on a React-Redux (with hooks) project, where I have a baseURL with different endpoints. For example
baseURL = "https://jsonplaceholder.typicode.com"
endpoints = "/posts", "/comments", "/albums"
I have 2 questions:
Where to keep the endpoint in the react (comments) component (for example: "/comments")
How to reuse the code for other components like posts and albums because the accessToken code and headers are same for all of them.
const accessToken = localStorage.getItem("accessToken");
Cookies.set("XSRF-TOKEN", Cookies.get("XSRF-TOKEN"));
var bodyParameters = {
page: 1,
pageSize: 50,
};
return fetch(baseURL, {
credentials: "include",
method: "post",
body: JSON.stringify(bodyParameters),
headers: {
Authorization: `JWT ${accessToken}`,
"X-XSRF-TOKEN": Cookies.get("XSRF-TOKEN"),
"cache-control": "no-cache",
pragma: "no-cache",
},
My redux action looks like this
export const readList = () => {
return (dispatch) => {
const accessToken = localStorage.getItem("accessToken");
Cookies.set("XSRF-TOKEN", Cookies.get("XSRF-TOKEN"));
var bodyParameters = {
page: 1,
pageSize: 50,
};
return fetch(baseURL, {
credentials: "include",
method: "post",
body: JSON.stringify(bodyParameters),
headers: {
Authorization: `JWT ${accessToken}`,
"X-XSRF-TOKEN": Cookies.get("XSRF-TOKEN"),
"cache-control": "no-cache",
pragma: "no-cache",
},
})
.then((response) => {
return response.json();
})
.then((data) =>
dispatch(
{
type: READ_LIST,
payload: data,
},
console.log("Actions: ", data)
)
)
.catch((error) => {
console.log(error.response);
throw error;
});
};
};
and the react component looks like this
import "./styles.css";
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { readList } from "./redux/actionCreators";
import { FormattedMessage } from "react-intl";
import { DataGrid } from "#material-ui/data-grid";
export default function App() {
const dispatch = useDispatch();
const historyList = useSelector(
(state) => state.reducers.commentsData.data || []
);
useEffect(() => {
dispatch(readList());
}, [dispatch]);
let rows = historyList.map((obj, index) => {
return (rows = {
id: index,
"User ID": obj.userId,
Title: obj.title,
Body: obj.body
});
});
const columns = [
{
field: "User ID",
flex: 1,
renderHeader: () => <FormattedMessage id={"userId"} />
},
{
field: "Title",
flex: 1,
value: "dropdown",
renderHeader: () => <FormattedMessage id={"title"} />
},
{
field: "Body",
flex: 1,
type: "date",
renderHeader: () => <FormattedMessage id={"body"} />
}
];
return (
<div className={"uhes-pageWrapper"}>
<h1 className="uhes-pageHeader">
<FormattedMessage id="History" />
</h1>
<div style={{ height: "90%", width: "100%" }}>
<DataGrid
pageSize={50}
rowsPerPageOptions={[50, 100, 150]}
rows={rows}
columns={columns}
/>
</div>
</div>
);
}
Thank you!

The ideas to reuse api configs, includes baseURL, accesstoken,... somethings like this
You will have a service called apiService: where you manage your fetch configs like headers, and you can also add your token in there. apiService will return REST functions: POST/PUT/GET/DELETE/PATCH with available header configurations
Example:
const customFetch = (path, options) => {
const accessToken = localStorage.getItem("accessToken");
Cookies.set("XSRF-TOKEN", Cookies.get("XSRF-TOKEN"));
var bodyParameters = {
page: 1,
pageSize: 50
};
return fetch(`${baseURL}/${path}`, {
credentials: "include",
headers: {
Authorization: `JWT ${accessToken}`,
"X-XSRF-TOKEN": Cookies.get("XSRF-TOKEN"),
"cache-control": "no-cache",
pragma: "no-cache"
},
...options
});
};
const post = (path, bodyParameters) =>
customFetch(path, {
method: "POST",
body: JSON.stringify(bodyParameters)
});
const get = (path, queries) =>
customFetch(queries ? `${path}/${qs.stringify(queries)}` : path, {
method: "GET"
});
const put = (path, bodyParameters) =>
customFetch(path, {
method: "PUT"
body: JSON.stringify(bodyParameters)
});
const delete = (path, id) =>
customFetch(`${path}/${id}`, {
method: "DELETE"
});
After that you can custom your readList with dynamic endpoint like this
const readList = (resource) => apiService.get(resource)
readList('/posts');
readList('/comments');
readList('/albums');

Related

How do I persist my next-auth user session? so i could use the ID provided to fetch data in other routes

What I want to achieve here is, whenever a user logs in, I want to store the data returned because the data holds an ID that I would use to fetch data in other routes.
When a user successfully logs in, he would be redirected to the /home route and the ID gotten from the session would be used to fetch data. Everything works fine initially, but if I refresh the home page, the user becomes null.
This is what my [...nextauth].js looks like.
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import axios from "axios";
export default NextAuth({
providers: [
CredentialsProvider({
name: "credentials",
credentials: {
username: { label: "Username", type: "text", placeholder: "justin" },
password: {label: "Password",type: "password",placeholder: "******"},
},
async authorize(credentials, req) {
const url = req.body.callbackUrl.split("/auth")[0];
const { username, password } = credentials;
const user = await axios({
url: `${url}/api/user/login`,
method: "POST",
data: {
username: username,
password: password,
},
"content-type": "application/json",
})
.then((res) => {
return res.data;
})
.catch((err) => {
if (err.response.data) {
throw new Error(err.response.data);
} else {
return null;
}
return null;
});
return user;
},
}),
],
callbacks: {
jwt: ({ token, user }) => {
if (user) {
token.user = user;
}
return token;
},
session: ({ session, token }) => {
if (token) {
session.user = token.user;
}
return session;
},
},
pages: {
signIn: "/auth/login",
newUser: "/auth/register",
},
});
and this is what my /home route looks like
import Card from "#/components/card/Card";
import React, { useEffect, useState } from "react";
import styles from "./home.module.css";
import { Ubuntu } from "#next/font/google";
import { useSession } from "next-auth/react";
import { useDispatch, useSelector } from "react-redux";
const ubuntu = Ubuntu({ weight: "500", subsets: ["cyrillic"] });
const getData = async (id) => {
const res = await fetch({
url: "http://localhost:3000/api/note/getall",
method: "POST",
"content-type": "application/json",
data: {
id: id,
},
});
if (!res.ok) {
console.log(id);
throw new Error("Unable to fetch");
} else {
return res.json();
console.log(res);
}
};
function home() {
const colors = ["#E9F5FC", "#FFF5E1", "#FFE9F3", "#F3F5F7"];
const random = Math.floor(Math.random() * 5);
const rc = colors[random];
const [pop, setPop] = useState("none");
const { user } = useSelector((state) => state.user);
const getDataa = async () => {
console.log(user)
const data = await getData(user._id);
console.log(data);
};
useEffect(() => {
if (user) {
alert(user)
}
}, []);
return (
<div className={styles.home}>
<header>
<h3 className={ubuntu.className}>
Hello, <br /> {user?.username}!
</h3>
<input type="text" placeholder="search" />
</header>
<div className={styles.nav}>
<h1 className={ubuntu.className}>Notes</h1>
</div>
<div className={styles.section}>
<div className={styles.inner}>
{/* {data &&
data.map((e) => (
<Card
rawData={e}
color={colors[Math.floor(Math.random() * colors.length)]}
/>
))} */}
</div>
</div>
<div className="new"></div>
</div>
);
}
export default home;
Add this component to your App.js file :
function Auth({ children }) {
const router = useRouter();
const { status } = useSession({
required: true,
onUnauthenticated() {
router.push("/sign-in");
},
});
if (status === "loading") {
return <div>Loading ...</div>;
}
return children;
}
Now in your App function instead of returning <Component {...pageProps} /> you check first if the component has auth property, so you wrapp it with <Auth> to ensure that every component that requires session will only mount when the session finishes loading (that's why the user is null because the session is still loading)
{
Component.auth ? (
<Auth>
<Component {...pageProps} />
</Auth>
) : (
<Component {...pageProps} />
);
}
finally you add .auth = {} to every page in whitch you want the session to be defined (Home in your case)
const Home = () => {
//....
}
Home.auth = {};
This also helps to redirect user to /sign-in page if the session is expired
This code seems like it would create a problem / race-condition since you're mixing two different async promise handling styles:
const user = await axios({
url: `${url}/api/user/login`,
method: "POST",
data: {
username: username,
password: password,
},
"content-type": "application/json",
})
.then((res) => {
return res.data;
})
.catch((err) => {
if (err.response.data) {
throw new Error(err.response.data);
} else {
return null;
}
return null;
});
return user;
It should either be this:
try {
const user = await axios({
url: `${url}/api/user/login`,
method: "POST",
data: {
username: username,
password: password,
},
"content-type": "application/json",
});
return user.data;
} catch (err) {
if (err.response.data) {
throw new Error(err.response.data);
} else {
return null;
}
}
Or this:
axios({
url: `${url}/api/user/login`,
method: "POST",
data: {
username: username,
password: password,
},
"content-type": "application/json",
}).then((res) => {
return res.data;
}).catch((err) => {
if (err.response.data) {
throw new Error(err.response.data);
} else {
return null;
}
return null;
});

converting custom react function to async

I made this custom hook.
import axios from "axios";
import Cookies from "js-cookie";
import React from "react";
const useGetConferList= () => {
let token = JSON.parse(localStorage.getItem("AuthToken"));
const Idperson = JSON.parse(Cookies.get("user")).IdPerson;
const [response, setResponse] = React.useState();
const fetchConfer= (datePrensence, idInsurance, timePrensence) => {
axios({
method: "post",
url: `${process.env.REACT_APP_API_URL_API_GET_ERJASERVICE_LIST}`,
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
data: JSON.stringify({
datePrensence,
idInsurance,
Idperson,
searchfield: "",
timePrensence: parseInt(timePrensence) * 60,
}),
})
.then((r) => {
setResponse(r.data.Data);
})
.catch(() => alert("NetworkError"));
};
return { fetchConfer, response };
};
export default useGetConferList;
as you can see I export the fetchConfer function. but I want to make it async. for example, calling the function and then doing something else like this:
fetchConfer(Date, Time, Id).then((r) => {
if (search !== "") {
window.sessionStorage.setItem(
"searchList",
JSON.stringify(
r.data
)
);
}
});
as you can see in non async situation, I can't use then.
You can try this
const fetchConfer = async (datePrensence, idInsurance, timePrensence) => {
try {
const response = await axios({
method: "post",
url: `${process.env.REACT_APP_API_URL_API_GET_ERJASERVICE_LIST}`,
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
data: JSON.stringify({
datePrensence,
idInsurance,
Idperson,
searchfield: "",
timePrensence: parseInt(timePrensence) * 60,
}),
})
setResponse(response.data.Data);
// need to return data
return response.data.Data
} catch(error) {
alert("NetworkError")
}
};
use the function in another async function
const someAsyncFunc = async () => {
// try catch
const r = fetchConfer(Date, Time, Id)
if (search !== "") {
window.sessionStorage.setItem(
"searchList",
JSON.stringify(
r.data
)
);
}
...
or use it how you are currently using it
Hope it helps

Setting a jsx element value to fetch call value

I'm making a custom jsx element. I want to set the element's value to data, that a fetch call returns:
const BoardPage = () => {
const id = useParams().id
fetch('http://localhost:8000/getBoardByID', {
headers: {
'Content-type': 'application/json'
},
method: 'POST',
body: JSON.stringify({ id: id })
}).then(response => response.json()).then(data => {
console.log(data)
return (
<div>
<h1>board #{data.id}</h1>
</div>
)
})
}
export default BoardPage
In console i see an object: {id: 31, board_content: '', width: 1223, height: 2323, user_privileges: '[]'}
But i get nothing as the output
You have to perform the request inside the useEffect hook.
const MyComponent = () => {
const id = useParams().id;
const [data, setData] = useState({});
React.useEffect(() => {
fetch("http://localhost:8000/getBoardByID", {
headers: {
"Content-type": "application/json",
},
method: "POST",
body: JSON.stringify({ id: id }),
})
.then((response) => response.json())
.then((data) => {
setData(data);
});
}, []);
return (
<div>
<h1>board #{data?.id}</h1>
</div>
);
};

why is axios get + post request timing out after 5 button requests?

I'm attempting to get and post data using axios from server (express, axios, react native) and after 5x pressing the button it disconnects from server.
please see web example - console log
Is there a default timeout after 5 or 6 request calls - iOS is 3 calls before disconnect - android 5 calls before disconnect - web 6 calls - it varies across platforms. Logs perfectly fine until disconnect.
//client:
const [i, setI] = useState(0)
const [connecter, setConnecter] = useState()
const mainURL = "http://192.168.0.26:8080/usr"
const datas = {
val : "counter!" + " " + i
}
const func = () => {
setI(i + 1)
axios({
method: 'post',
url: mainURL + "/play",
data: JSON.stringify(datas),
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
maxContentLength: Infinity,
maxBodyLength: Infinity
})
.then(() => {
console.log("sent!")
})
.catch(err => console.log(err))
};
const red = axios.get(mainURL + "/connect",
{headers:{"Content-Type": "application/json"}})
.then((res) => {
setConnecter(res.status)
}).catch((err) => setConnecter(err))
useEffect(() => {
}, [red])
//server
router.get("/connect", (req, res) => {
res.send("Server Connected")
})
router.post("/play", async(req, res) => {
const resser = await req.body
console.log(resser)
})
You can try using this code:
import * as React from 'react';
import {useEffect, useState} from 'react';
import { Button, View, StyleSheet } from 'react-native';
import Constants from 'expo-constants';
import * as axios from "axios";
export default function App() {
const mainURL = "http://192.168.0.26:8080/usr";
const [counter, setCounter] = useState(0);
const [connecter, setConnecter] = useState(null);
const [data, setData] = useState({
val: `counter! ${counter}`
});
useEffect(() => {
setData({
val: `counter! ${counter}`
});
}, [counter]);
const callApi = async () => {
try {
const connectRes = await axios.post('https://reqres.in/api/users', JSON.stringify(data), { headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }});
console.log(connectRes);
setConnecter(connectRes);
const result = await axios.get('https://reqres.in/api/products/3', { headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }});
setCounter(prev => prev + 1);
console.log(counter);
console.log(result);
} catch (e) {
console.log('error', e);
}
}
return (
<View style={styles.container}>
<Button title="test" onPress={() => callApi()} />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
});
you can check this working example from here.

Not able to save my new playlist in my Spotify account (I get an empty list of tracks)

This is my function in my util module Spotify:
savePlaylist1(name, trackUris) {
if (!name || !trackUris.length) {
return;
}
const accessToken = Spotify.getAccessToken();
const headers = { Authorization: `Bearer ${accessToken}` };
let userID;
return fetch('https://api.spotify.com/v1/me', { headers: headers }
).then(response => {
return response.json();
}).then(jsonResponse => {
userID = jsonResponse.id;
return fetch(`https://api.spotify.com/v1/users/${userID}/playlists`, {
headers: headers,
method: 'POST',
body: JSON.stringify({ name: name })
}).then(response => {
return response.json();
}).then(jsonResponse => {
const playlistID = jsonResponse.id;
return fetch(`https://api.spotify.com/v1/playlists/${playlistID}/tracks`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({ uris: trackUris })
})
})
})
} // end of savePlaylist1 method
}
This is my container module where I'm calling the function from: (followed by the render function)
savePlaylist() {
const trackUris = this.state.playlistTracks.map(track => track.uri);
Spotify.savePlaylist1(this.state.playlistName, trackUris).then(() => {
this.setState({
playlistName: 'New Playlist',
playlistTracks: []
});
})
}
render() {
return (
<div>
<div className="App" >
< SearchBar onSearch={this.search} />
<div className="App-playlist">
< SearchResults searchResults={this.state.searchResults}
onAdd={this.addTrack} />
< Playlist playlistName={this.state.playlistName}
playlistTracks={this.state.playlistTracks}
onRemove={this.removeTrack}
onNameChange={this.updatePlaylistName}
onSave={this.savePlaylist} />
</div>
</div>
</div>);
}
this is the playlist component with the safe button>
render() {
return (
<div className="Playlist">
<input defaultValue={"New Playlist"} onChange={this.handleNameChange} />
<TrackList
tracks={this.props.playlistTracks}
onRemove={this.props.onRemove}
isRemoval={true}
/>
<button className="Playlist-save" onClick={this.props.onSave}>
SAVE TO SPOTIFY
</button>
</div>
);
}
it is saving with an empty list and I get the following error:
{
"error" : {
"status" : 400,
"message" : "Invalid track uri: null"
}
}
Finally, this is the jamming project in codecademy, the problem was that it was not loading the URIs, when being called from the API, this was because it was not included in the search function as part of the list:
search(term) {
const accessToken = Spotify.getAccessToken();
return fetch(`https://api.spotify.com/v1/search?type=track&q=${term}`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
}).then(response => {
return response.json();
}).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 //this was missing!!!!
}));
});
},

Resources