How to pass parameters from a URL link to a request? - reactjs

In my Main.tsx:
import React, { FC, useEffect, useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import { useAppDispatch, useAppSelector } from '../../hook'
import { getProducts } from '../../store/ProductsSlice'
import Filter from '../Filter/Filter'
import Pagination from '../Pagination/Pagination'
import Products from '../Products/Products'
import { ErrorMessage, FilterError } from './styled'
const Main: FC = () => {
const products = useAppSelector((state) => state.products.list)
const dispatch = useAppDispatch()
const [errorId, setErrorId] = useState<string>('')
const [errorMessage, setErrorMessage] = useState<string>('')
const [page, setPage] = useState<number>(1)
const [filterId, setFilterId] = useState<number>()
const [pageParams, setPageParams] = useSearchParams()
pageParams.get(`page`) || ''
pageParams.get(`id`) || ''
useEffect(() => {
async function fetchProducts(id?: number, productsPage = 1) {
const itemsPerPage = 5
let url: string
if (id) {
url = `https://reqres.in/api/products/${id}`
} else {
url = `https://reqres.in/api/pr231oducts?per_page=${itemsPerPage}&page=${productsPage}`
}
const requestOptions = {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
}
fetch(url, requestOptions)
.then(async (response) => {
const data = await response.json()
if (response.ok) {
setErrorId('')
setErrorMessage('')
if (id) {
dispatch(
getProducts({
page: 1,
per_page: 1,
total: 1,
total_pages: 1,
data: [data.data],
})
)
setPageParams({ page: `1`, id: `${id}` })
} else {
dispatch(getProducts(data))
setPageParams({ page: `${productsPage}` })
}
} else {
const error = (data && data.message) || response.status
return Promise.reject(error)
}
setErrorMessage(data.id)
})
.catch((error) => {
setErrorId(error.toString())
console.error('There was an error!', error)
})
}
fetchProducts(filterId, page)
}, [filterId, page])
return (
<div>
{!products ? (
<>
{errorId ? <ErrorMessage>{errorId}</ErrorMessage> : null}
{errorMessage ? (
<ErrorMessage>
Something went wrong
{errorMessage}
</ErrorMessage>
) : null}
</>
) : (
<>
<Filter setFilterId={setFilterId} />
{errorId ? (
<FilterError>
{errorId}:
{errorId === '404'
? ' Product not found'
: `${errorId}: ${errorMessage}`}
</FilterError>
) : (
<Products />
)}
<Pagination setPage={setPage} />
</>
)}
</div>
)
}
export default Main
Filter.tsx:
import React, { FC } from 'react'
import { FilterContainer, FilterInput } from './styled'
const Filter: FC<{
setFilterId: React.Dispatch<React.SetStateAction<number | undefined>>
}> = ({ setFilterId }) => {
return (
<FilterContainer>
<FilterInput
onChange={(e) => {
if (e.target.value === '0') {
e.target.value = ''
}
setFilterId(Number(e.target.value))
}}
placeholder="Search by id"
type="number"
/>
</FilterContainer>
)
}
export default Filter
Pagination.tsx:
import { FC } from 'react'
import { useAppSelector } from '../../hook'
import ArrowBackIosIcon from '#mui/icons-material/ArrowBackIos'
import ArrowForwardIosIcon from '#mui/icons-material/ArrowForwardIos'
import { PaginationBtn, PaginationContainer } from './styled'
const Pagination: FC<{
setPage: React.Dispatch<React.SetStateAction<number>>
}> = ({ setPage }) => {
let pageNumber = useAppSelector((state) => state.products.list.page)
const totalPages = useAppSelector((state) => state.products.list.total_pages)
return (
<PaginationContainer>
<PaginationBtn
onClick={() => {
setPage((pageNumber -= 1))
}}
disabled={pageNumber <= 1}
>
<ArrowBackIosIcon fontSize="large" />
</PaginationBtn>
<PaginationBtn
onClick={() => {
setPage((pageNumber += 1))
}}
disabled={pageNumber >= totalPages}
>
<ArrowForwardIosIcon fontSize="large" />
</PaginationBtn>
</PaginationContainer>
)
}
export default Pagination
The fetchProducts function makes a request to the API, using the productPage and id variables passed to the function, the corresponding request is sent and the necessary information is displayed on the screen.
I'm going to take the page and id from the link and pass them to the fetchProducts function so that if something happens, the site opens immediately with the necessary information.
I have useSearchParams() with which I make a link that can be "sent to other users". But I don’t understand how to implement that when parameters are inserted into the link, they are applied and the page with the necessary data is loaded.
Now the correct link is generated, but if you copy it and paste it in another browser window, the standard "list of products" will be loaded

I have already an exemple for make you understand How to pass parameters from a URL link to a request:
App.js
function App() {
return (
<>
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/add-book" element={<AddBook />} />
<Route path="/upd-book/:id" element={<UpdBook />} />
</Routes>
</BrowserRouter>
</>
)
}
export default App;
Home.js
<Link to={`/upd-book/${id}`} >Update</Link>
UpdatePage.js exemple url after you click to Link: localhost:3000/upd-book/30
import {useParams} from 'react-router-dom';
const {id} = useParams();
{id} is 30
I hope this exemple explain how this is work.

Issue
The Main component has competing "sources of truth", the queryString params and local state.
Solution
Use the queryString params as the source of truth for the API requests. Access the "page" and "id" query params in the component and pass as useEffect hook dependencies and on to the fetchProducts handler. Instead of enqueuing state updates enqueue navigation redirects that only update the URL queryString.
const Main: FC = () => {
const dispatch = useAppDispatch();
const products = useAppSelector((state) => state.products.list);
const [searchParams, setSearchParams] = useSearchParams();
// Read the queryString parameters, convert to number type
const page = Number(searchParams.get("page") || 1);
const filterId = Number(searchParams.get("id"));
const [errorId, setErrorId] = useState<string>('');
const [errorMessage, setErrorMessage] = useState<string>('');
useEffect(() => {
async function fetchProducts(id?: number, page: number = 1) {
const itemsPerPage = 5;
const url = id
? `https://reqres.in/api/products/${id}`
: `https://reqres.in/api/pr231oducts?per_page=${itemsPerPage}&page=${page}`;
const requestOptions = {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
}
try {
const response = await fetch(url, requestOptions);
const data = await response.json();
if (response.ok) {
setErrorId('');
setErrorMessage('');
if (id) {
dispatch(
getProducts({
page: 1,
per_page: 1,
total: 1,
total_pages: 1,
data: [data.data],
})
);
setSearchParams({ page, id }, { replace: true });
} else {
dispatch(getProducts(data));
setPageParams({ page }, { replace: true });
}
} else {
const error = data?.message || response.status;
return Promise.reject(error);
}
setErrorMessage(data.id);
} catch(error) {
setErrorId(error.toString());
console.error('There was an error!', error);
}
};
fetchProducts(filterId, page);
}, [filterId, page]);
// Callbacks to update the queryString parameters
const setPage = page => setSearchParams(params => {
params.set("page", page);
return params;
}, { replace: true });
const setFilterId = id => setSearchParams(params => {
params.set("id", id);
return params;
}, { replace: true });
return (
<div>
{!products ? (
<>
{errorId ? <ErrorMessage>{errorId}</ErrorMessage> : null}
{errorMessage && (
<ErrorMessage>
Something went wrong
{errorMessage}
</ErrorMessage>
)}
</>
) : (
<>
<Filter setFilterId={setFilterId} />
{errorId ? (
<FilterError>
{errorId}:
{errorId === '404'
? "Product not found"
: `${errorId}: ${errorMessage}`
}
</FilterError>
) : (
<Products />
)}
<Pagination setPage={setPage} />
</>
)}
</div>
);
};

Related

imported component is not displayed on output

i've a component that i import, but its not displayed on the page.
this is my app.js file. i imported the <LineGraph/>component but it is not getting displayed properly on the browser.
import React, { useEffect, useState } from "react";
import {
MenuItem,
FormControl,
Select,
Card,
CardContent,
} from "#material-ui/core";
import InfoBox from "./infoBox";
import Table from "./table";
import "./App.css";
import { sortData } from "./util";
import LineGraph from "./LineGraph";
const App = () =\> {
const \[countries, setCountries\] = useState(\[\]);
const \[country, setCountry\] = useState("worldwide");
const \[countryInfo, setCountryInfo\] = useState({});
const \[tableData, setTableData\] = useState(\[\]);
const \[casesType, setCasesType\] = useState("cases");
useEffect(() =\> {
fetch("https://disease.sh/v3/covid-19/all")
.then((response) =\> response.json())
.then((data) =\> {
setCountryInfo(data);
});
}, \[\]);
useEffect(() =\> {
const getCountriesData = async () =\> {
fetch("https://disease.sh/v3/covid-19/countries")
.then((response) =\> response.json())
.then((data) =\> {
const countries = data.map((country) =\> ({
name: country.country,
value: country.countryInfo.iso2,
}));
const sortedData = sortData(data);
setTableData(sortedData);
setCountries(countries);
});
};
getCountriesData();
}, \[\]);
const onCountryChange = async (event) =\> {
const countryCode = event.target.value;
console.log("s", countryCode);
setCountry(countryCode);
const url =
countryCode === "worldwide"
? "https://disease.sh/v3/covid-19/all"
: `https://disease.sh/v3/covid-19/countries/${countryCode}`;
await fetch(url)
.then((response) => response.json())
.then((data) => {
setCountry(countryCode);
setCountryInfo(data);
});
};
console.log("CuntryInfo: ", countryInfo);
return (
\<div className="App"\>
\<div className="app__left"\>
\<div className="app__header"\>
\<h1\>COVID-19 Tracker\</h1\>
\<FormControl className="app__dropdown"\>
\<Select
variant="outlined"
onChange={onCountryChange}
value={country}
\\>
\<MenuItem value="worldwide"\>Worldwide\</MenuItem\>
{countries.map((country) =\> (
\<MenuItem value={country.value}\>{country.name}\</MenuItem\>
))}
\</Select\>
\</FormControl\>
\</div\>
<div className="app__stats">
<InfoBox
title="Coronavirus cases"
cases={countryInfo.todayCases}
total={countryInfo.cases}
/>
<InfoBox
title="Recovered"
cases={countryInfo.todayRecovered}
total={countryInfo.recovered}
/>
<InfoBox
title="Deaths"
cases={countryInfo.todayDeaths}
total={countryInfo.deaths}
/>
</div>
</div>
<Card className="app__right">
<CardContent>
{/* Table */}
<h3>Live Cases by country</h3>
<Table countries={tableData} />
{/* Graph */}
<h3>Word Wide New </h3>
<LineGraph casesType={casesType} />
</CardContent>
</Card>
</div>
);
};
export default App;
and My content of LineGraph.js :
import React, { useState, useEffect } from "react";
import { Line } from "react-chartjs-2";
import numeral from "numeral";
const options = {
legend: {
display: false,
},
elements: {
point: {
radius: 0,
},
},
maintainAspectRatio: false,
tooltips: {
mode: "index",
intersect: false,
callbacks: {
label: function (tooltipItem, data) {
return numeral(tooltipItem.value).format("+0,0");
},
},
},
};
const buildChartData = (data, casesType) => {
let chartData = [];
let lastDataPoint;
for (let date in data.cases) {
if (lastDataPoint) {
let newDataPoint = {
x: date,
y: data[casesType][date] - lastDataPoint,
};
chartData.push(newDataPoint);
}
lastDataPoint = data[casesType][date];
}
return chartData;
};
function LineGraph({ casesType }) {
const [data, setData] = useState({});
useEffect(() => {
const fetchData = async () => {
await fetch("https://disease.sh/v3/covid-19/historical/all?lastdays=120")
.then((response) => {
return response.json();
})
.then((data) => {
let chartData = buildChartData(data, casesType);
setData(chartData);
console.log(chartData);
// buildChart(chartData);
});
};
fetchData();
}, [casesType]);
return (
<div>
{data?.length > 0 && (
<Line
data={{
datasets: [
{
backgroundColor: "rgba(204, 16, 52, 0.5)",
borderColor: "#CC1034",
data: data,
},
],
}}
options={options}
/>
)}
</div>
);
}
export default LineGraph;
When I import the LineGraph.js component in the App.js file, the output is not displayed without any error.
in console error is :
react-dom.development.js:25830
Uncaught Error: "category" is not a registered scale.

Component didn't recieve data from state using React/Toolkit and createAsyncThunk

I'm trying to pass data to the component, that i received from the API. I am using 'createAsyncThunk' to save it in the state, than when trying to get my data, get error "undefined". I understand that it happens, cause it's need some time to get data from API, but how i can force component "waiting"? What is wrong with my code?
Here is my code:
Step 1: Gettind data from API, filtered it and push it in state.
import { generateRandom } from "../helpers/randomInt";
const API_URL = "https://akabab.github.io/superhero-api/api/all.json";
export const fetchHeroes = createAsyncThunk(
"data_slice/fetchHeroes",
async function (_, { rejectWithValue }) {
try {
const res = await fetch(API_URL);
if (!res.ok) {
throw new Error("Could not fetch cart data!");
}
const data = await res.json();
const marvel_heroes = data.filter(
(item) => item.biography.publisher == "Marvel Comics"
);
const dark_horse_heroes = data.filter(
(item) => item.biography.publisher == "Dark Horse Comics"
);
const dc_heroes = data.filter(
(item) => item.biography.publisher == "DC Comics"
);
const filtered_data = [
...marvel_heroes,
...dark_horse_heroes,
...dc_heroes,
];
const heroesData = [];
for (let index = 0; index < 49; index++) {
const item = filtered_data[generateRandom(0, 439)];
heroesData.push(item);
}
const main_data = [filtered_data, heroesData];
return main_data;
} catch (error) {
return rejectWithValue(error.message);
}
}
);
const heroesSlice = createSlice({
name: "data_slice",
initialState: { heroes_data: [], isLoading: null, error: null },
extraReducers: {
[fetchHeroes.pending]: (state) => {
state.isLoading = true;
},
[fetchHeroes.fulfilled]: (state, action) => {
state.heroes_data = action.payload;
state.isLoading = false;
},
[fetchHeroes.rejected]: (state) => {
state.isLoading = false;
state.error = "Something go wrong!";
alert("aaa");
},
},
});
export default heroesSlice;
Step 2: Firing (using dispatch) fetch function "fetchHeroes" in 'App.js' with 'UseEffect' to get data when app starting
import { Fragment, useState, useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { fetchHeroes } from "./store/heroes-slice";
import { Routes, Route } from "react-router-dom";
import Main from "./pages/Main";
import Heroes from "./pages/Heroes";
import Hero_page from "./pages/Hero_page";
import LoginModal from "./components/LoginModal";
import RegisterModal from "./components/RegisterModal";
function App() {
const [scrollY, setScrollY] = useState(0);
const isLoginModal = useSelector((state) => state.modal.isLoginModal);
const isRegisterModal = useSelector((state) => state.modal.isRegisterModal);
const dispatch = useDispatch();
function logit() {
setScrollY(window.scrollY);
console.log(new Date().getTime());
}
useEffect(() => {
function watchScroll() {
window.addEventListener("scroll", logit);
}
watchScroll();
return () => {
window.removeEventListener("scroll", logit);
};
});
useEffect(() => {
dispatch(fetchHeroes());
}, [dispatch]);
return (
<Fragment>
{isRegisterModal && <RegisterModal></RegisterModal>}
{isLoginModal && <LoginModal></LoginModal>}
<Routes>
<Route path="/" element={<Main />} />
<Route path="/heroes" exact element={<Heroes scroll={scrollY} />} />
<Route path="/heroes/:heroId" element={<Hero_page />}></Route>
</Routes>
</Fragment>
);
}
export default App;
Step 3: I am trying to recieve data from state(heroes_fetched_data) using 'usSelector', but when trying parce it through 'map', get error 'undefined'
import classes from "./Heroes.module.css";
import Header from "../components/Header";
import Footer from "../components/Footer.js";
import Hero_card from "../components/Hero_card";
import { useSelector } from "react-redux";
import { Link } from "react-router-dom";
export default function Heroes(props) {
const heroes_fetched_data = useSelector((state) => state.heroes.heroes_data);
const loadingStatus = useSelector((state) => state.heroes.isLoading);
console.log(heroes_fetched_data);
const heroes_cards = heroes_fetched_data[1].map((item, i) => (
<Link to={`/heroes/${item.id}`} key={item.id + Math.random()}>
<Hero_card
key={i}
img={item.images.lg}
name={item.name}
publisher={item.biography.publisher}
/>
</Link>
));
return (
<div className={classes.main}>
<Header scroll={props.scroll} />
{!loadingStatus && (
<section className={classes.heroes}>
<ul className={classes.ully} id="heroes">
{heroes_cards}
</ul>
</section>
)}
{loadingStatus && <p>Loading...</p>}
<Footer />
</div>
);
}
Because the fetch is asynchronous, you cannot assume that heroes_fetched_data inside your Heroes component will have the data when the component first renders. You need to check whether this data is present before attempting to use it. If it's not yet present, the component should render an alternate "loading" state. When the fetch completes, your component should re-render automatically, at which point heroes_fetched_data will have the data you want and you can proceed.
Roughly, you want something like this pseudocode:
export default function Heroes(props) {
const heroes_fetched_data = useSelector((state) => state.heroes.heroes_data);
const loadingStatus = useSelector((state) => state.heroes.isLoading);
if (!heroes_fetched_data) {
return <p>{loadingStatus}</p>;
}
const heroes_cards = heroes_fetched_data[1].map((item, i) => (
// ...
);
// proceed as normal
}

ReactJS Error when using map function, cannot read properties of undefined

I'm trying to make a sport/tinder like app for a school project from a friend of mine. It came together well on my localhost, but for him it was a requirement to host it online. Not really a professional in hosting, but I was a bit familiar with Heroku. I used a client and a server side for my application, so I build the client side and put it into the server side folder. This server side is hosted on the Heroku page. But whenever I try to login, it won't work and I get this error message in my console.
TypeError: Cannot read properties of undefined (reading 'map')
The error says it is caused by this line of code.
const matchedUserIds = matches.map(({user_id}) => user_id)
This is the whole MatchDisplay file that is used in my Dashboard. I'm using a MongoDB for the storage of my users.
import axios from "axios";
import { useEffect, useState } from "react";
import { useCookies } from "react-cookie";
const MatchesDisplay = ({ matches, setClickedUser }) => {
const [matchedProfiles, setMatchedProfiles] = useState(null);
const [cookies, setCookie, removeCookie] = useCookies(null);
const [matched, setMatched] = useState(null);
const matchedUserIds = matches.map(({ user_id }) => user_id);
const userId = cookies.UserId;
const getMatches = async () => {
try {
const response = await axios.get(
"https://[app].herokuapp.com/users",
{
params: { userIds: JSON.stringify(matched()) },
}
);
setMatchedProfiles(response.data);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
getMatches();
}, [matches]);
const filteredMatchedProfiles = matchedProfiles?.filter(
(matchedProfile) =>
matchedProfile.matches.filter(
(profile) => profile.user_id === userId
).length > 0
);
return (
<div className="matches-display">
{filteredMatchedProfiles?.map((match) => (
<div
key={match.user_id}
className="match-card"
onClick={() => setClickedUser(match)}
>
<div className="img-container">
<img
src={match?.url}
alt={match?.first_name + "profile"}
/>
</div>
<h3>{match?.first_name}</h3>
</div>
))}
</div>
);
};
export default MatchesDisplay;
Any help is welcome. If you need more code examples, please reply ;)
EDIT
The ChatContainer that passes the user to the MatchesDisplay.
import ChatHeader from "./ChatHeader";
import MatchesDisplay from "./MatchesDisplay";
import ChatDisplay from "./ChatDisplay";
import { useState } from 'react';
const ChatContainer = ({user}) => {
const [ clickedUser, setClickedUser] = useState(null)
return (
<div className="chat-container">
<ChatHeader user={user}/>
<div>
<button className="option" onClick={() => setClickedUser(null)}>Matches</button>
<button className="option" disabled={!clickedUser}>Chat</button>
<button className="option" >Prices</button>
</div>
{!clickedUser && <MatchesDisplay matches={user.matches} setClickedUser={setClickedUser}/>}
{clickedUser && <ChatDisplay user={user} clickedUser={clickedUser}/>}
</div>
)
}
export default ChatContainer
The Dashboard that passes the user to the Chatcontainer.
import TinderCard from 'react-tinder-card';
import {useEffect, useState} from 'react';
import {useCookies} from 'react-cookie';
import ChatContainer from '../components/ChatContainer'
import axios from "axios";
const Dashboard = () => {
const [user, setUser] = useState(null)
const [genderedUsers, setGenderedUsers] = useState(null)
const [lastDirection, setLastDirection] = useState(null)
const [cookies, setCookie, removeCookie] = useCookies(['user'])
const [matchedUserIds, setMatchedUserIds] = useState(null)
const [filteredGenderedUsers, setFilteredGenderedUsers] = useState(null)
const userId = cookies.UserId
const getUser = async () => {
try {
const response = await axios.get('https://funfit-webpage.herokuapp.com/user', {
params: {userId}
})
return setUser(response.data)
} catch (error) {
console.log(error)
}
}
const getGenderedUsers = async () => {
try {
const response = await axios.get('https://funfit-webpage.herokuapp.com/gendered-users', {
params: {gender: user?.gender_interest}
})
return setGenderedUsers(response.data)
} catch (error) {
console.log(error)
}
}
useEffect(() => {
getUser()
}, [])
useEffect(() => {
setMatchedUserIds(user?.matches.map(({user_id}) => user_id).concat(userId))
if (user) return getGenderedUsers()
}, [user])
useEffect(() => {
if (genderedUsers) {
return setFilteredGenderedUsers(genderedUsers?.filter(
genderedUser => !matchedUserIds.includes(genderedUser.user_id)
))
}
}, [genderedUsers])
const updateMatches = async (matchedUserId) => {
try {
await axios.put('https://funfit-webpage.herokuapp.com/addmatch', {
userId,
matchedUserId
})
return getUser()
} catch (error) {
console.log(error)
}
}
const swiped = (direction, swipedUserId) => {
console.log(direction, swipedUserId)
if (direction === 'right') {
updateMatches(swipedUserId)
}
return setLastDirection(direction)
}
const outOfFrame = (name) => {
console.log(name + ' left the screen!')
}
return (<>
{user && <div className="dashboard">
<ChatContainer user={user}/>
<div className="swipe-container">
<div className="card-container">
{filteredGenderedUsers?.map((genderedUser) =>
<TinderCard
className='swipe'
key={genderedUser.user_id}
onSwipe={(dir) => swiped(dir, genderedUser.user_id)}
onCardLeftScreen={() => outOfFrame(genderedUser.first_name)}>
<div style={{backgroundImage: 'url(' + genderedUser.url + ')'}} className='card'>
<h3>{'Name: ' + genderedUser.first_name} <br/> {'Sport: ' + genderedUser.about}</h3>
</div>
</TinderCard>)}
<div className="swipe-info">
{lastDirection ? <p>You swiped {lastDirection}</p> : <p/>}
</div>
</div>
</div>
</div>}
</>)
}
export default Dashboard

Keycloak integration with react-admin

Searching for some examples on how to integrate keycloak with react admin application for authentication purposes.
App.js example:
// your other imports
import { ReactKeycloakProvider } from "#react-keycloak/web";
import Keycloak from "keycloak-js";
import Cookies from "js-cookie";
// we used cookies that backend is writing, because we had front as a production static build
const initOptions = {
url: Cookies.get("REACT_APP_KEYCLOAK_URL"),
realm: Cookies.get("REACT_APP_KEYCLOAK_REALM"),
clientId: Cookies.get("REACT_APP_KEYCLOAK_CLIENT_ID"),
onLoad: "login-required",
};
const keycloak = Keycloak(initOptions);
const onToken = () => {
if (keycloak.token && keycloak.refreshToken) {
localStorage.setItem("token", keycloak.token);
localStorage.setItem("refresh-token", keycloak.refreshToken);
}
};
const onTokenExpired = () => {
keycloak
.updateToken(30)
.then(() => {
console.log("successfully get a new token", keycloak.token);
})
.catch(() => {
console.error("failed to refresh token");
});
};
// for data provider, it writes token to an authorization header
const fetchJson = (url, options = {}) => {
if (!options.headers) {
options.headers = new Headers({ Accept: "application/json" });
}
if (keycloak.token) {
options.headers.set("Authorization", "Bearer " + keycloak.token);
} else if (localStorage.getItem("token")) {
options.headers.set(
"Authorization",
"Bearer " + localStorage.getItem("token")
);
}
return fetchUtils.fetchJson(url, options);
};
const customDataProvider = dataProvider("/api/v1", fetchJson);
const theme = createMuiTheme({
...defaultTheme,
sidebar: {
width: 110,
closedWidth: 40,
},
});
const fetchResources = (permissions) => {
let knownResources = [];
if (permissions) {
const resource = (
<Resource
name="feeds"
list={FeedList}
create={FeedCreate}
edit={StateEdit}
icon={CollectionsBookmark}
/>
);
knownResources.push(resource);
} else {
const resource = (
<Resource name="feeds" list={FeedList} icon={CollectionsBookmark} />
);
knownResources.push(resource);
}
return knownResources;
};
const CustomAdminWithKeycloak = () => {
const customAuthProvider = useAuthProvider(
Cookies.get("REACT_APP_KEYCLOAK_CLIENT_ID")
);
return (
<Admin
theme={theme}
dataProvider={customDataProvider}
authProvider={customAuthProvider}
loginPage={false}
title="Inventory Splitter"
layout={CustomLayout}
>
{fetchResources}
</Admin>
);
};
const CustomAdmin = () => {
return (
<Admin
theme={theme}
dataProvider={customDataProvider}
loginPage={false}
title="Inventory Splitter"
layout={CustomLayout}
>
<Resource
name="feeds"
list={FeedList}
create={FeedCreate}
edit={StateEdit}
icon={CollectionsBookmark}
/>
</Admin>
);
};
// we have a feature to completely switch off the authorization process through env variable on backend
const App = () => {
const useKeycloak = Cookies.get("USE_KEYCLOAK") === "true";
return useKeycloak ? (
<ReactKeycloakProvider
authClient={keycloak}
LoadingComponent={<div></div>}
initOptions={initOptions}
onTokens={onToken}
onTokenExpired={onTokenExpired}
>
<React.Fragment>
<CustomAdminWithKeycloak />
<ThemeProvider theme={theme}>
<Footer />
</ThemeProvider>
</React.Fragment>
</ReactKeycloakProvider>
) : (
<React.Fragment>
<CustomAdmin />
<ThemeProvider theme={theme}>
<Footer />
</ThemeProvider>
</React.Fragment>
);
};
export default App;
authProvider.js
import { useKeycloak } from '#react-keycloak/web'
import jwt_decode from 'jwt-decode'
const useAuthProvider = (clientID:string) => {
const { keycloak } = useKeycloak();
return ({
login: () => keycloak.login(),
checkError: () => Promise.resolve(),
checkAuth: () => {
return keycloak.authenticated &&
keycloak.token ? Promise.resolve() : Promise.reject("Failed to obtain access token.");
},
logout: () => keycloak.logout(),
getIdentity: () => {
if (keycloak.token) {
const decoded : any = jwt_decode(keycloak.token);
const id = decoded.sub
const fullName = decoded.name
return Promise.resolve({id, fullName});
}
return Promise.reject("Failed to get identity");
},
getPermissions:() => {
let hasRole = false;
if (keycloak.token) {
const decoded : any = jwt_decode(keycloak.token);
decoded.resource_access[clientID].roles.forEach((el: string) => {
if (el === "admin") {
hasRole = true;
return
}
});
}
if (hasRole) {
return Promise.resolve(true);
}
return Promise.resolve(false);
},
});
};
export default useAuthProvider;
if you want to hide some components depending on permissions, use smth like this:
import * as React from 'react';
import {
useListContext,
usePermissions,
TopToolbar,
CreateButton,
} from 'react-admin';
import PublishIcon from '#material-ui/icons/Publish';
const CustomListActions = (props) => {
const permissions = usePermissions();
const { basePath } = useListContext();
return (
<TopToolbar>
{permissions.permissions && <CreateButton label='Upload' basePath={basePath} icon={<PublishIcon/>}/>}
</TopToolbar>
);
};
export default CustomListActions;

Prevent React from re-fetching data in parent component

In my parent component, Dashboard.tsx, I have a child component, Expenses.tsx, that makes an API fetch call and then displays the data. The parent component has a Router that allows you to navigate to different URL's in the parent component, which forces everything to re-render every time you navigate to a new path or render a new child component. How can I make it so that this fetch call is only made one time? I've tried using the useRef() hook but it re-initializes every time there is a re-render and I have the same problem.
Here is Dashboard.tsx:
export const Dashboard = () => {
const d = new Date()
const history = useHistory()
const { user, setUser } = useAuth()
const [categories, setCategories] = useState({
expenseCategories: [],
incomeCategories: []
})
const getCategories = async(user_id: number) => {
await fetch(`/api/getCategories?user_id=${user_id}`)
.then(result => result.json())
.then(result => setCategories(result))
}
useEffect(() => {
if (user.info.user_id) {
getCategories(user.info.user_id)
}
}, [])
const dashboardItems = [
{
value: 'Add Expense',
path: '/dashboard/addExpense'
},
{
value: 'Add Income',
path: '/dashboard/addIncome'
},
{
value: 'Logout',
path: '/login',
onClick : async() => {
localStorage.clear()
setUser({
info: {
user_id: null,
email: null,
username: null
},
token: null
})
},
float: 'ml-auto'
}
]
return(
<div>
<DashboardNavbar items={dashboardItems}/>
<div className="wrapper">
<p>{`Hello, ${user.info.username}!`}</p>
<DateAndTime />
<Expenses date={d}/>
<Income date={d}/>
<Switch>
<Route path='/dashboard/addExpense'>
<AddItemForm user={user} type={'expenses'} categories={categories.expenseCategories} />
</Route>
<Route path='/dashboard/addIncome'>
<AddItemForm user={user} type={'income'} categories={categories.incomeCategories} />
</Route>
</Switch>
<Logout />
</div>
</div>
)
}
And here is Expenses.tsx, where the fetch call is being made:
export const Expenses = (props: ExpensesProps) => {
const [isLoading, setIsLoading] = useState(true)
const { date } = props
const { user } = useAuth()
const m = date.getMonth() + 1
const s = '0'.concat(m.toString())
const [total, setTotal] = useState<number>(0)
useEffect(() => {
const getTotalExpenses = async() => {
await fetch(`/api/expenses?user_id=${user.info.user_id}&month=${s}`)
.then(response => response.json())
.then(result => {
if (result) {
setTotal(parseFloat(result))
}
})
.then(result => {
setIsLoading(false)
})
}
if (user.info.user_id) {
getTotalExpenses()
}
}, [])
return isLoading ? (
<div>
loading...
</div>
) : (
<div>
{`Your monthly expenses so far are: $${total}.`}
</div>
)
}

Resources