React: am I doing it wrong?
So I’ve been working with React for a while, and I’ve been able to create some really cool projects by utilizing what React has to offer; Hooks, props, etc. The thing is. My workflow always comes to a stop and I end up having a bad case of spaghetti-code when I try to pass variables and state between local and global functions. 9/10 I end up getting stuck and disobeying the React Hooks Rules, and have hack my way out of it with a very vanilla JS way of doing things. And then I think to myself: “What a wonderf… No, I mean: Why am I using React if I end up writing vanilla JS when I try to do something that is a bit more advanced than rendering components on a page?”. Is my approach all wrong?
Here's an example: I have a webpage which fetches to an API written in Express, which in turn returns data from a MongoDB database. I use a custom hook to fetch with an async function, and then I display everything on a page. I have a functional component that renders out everything. I also send some query-data with the API fetch, which in this example is a string representation of numbers, which in turn sets the limit of how many elements are gathered from the database. And on the useEffect hook – which is inside the custom hook I mentioned earlier – I have the number of elements to display as a dependency, so that I fetch the API every time that value changes. That value in turn, is chosen by a slider between 1-1000. Every time I fetch, the component renders again and everything flashes. This is because the data from the DB, as well as my h1, slider, and p-tags, are all in the same component. I want to avoid that, so my initial thought is to extract everything BUT the data from the DB, to a different component and render it separately. And this is where it goes wrong. The slidervalue which sets state, which in turn the custom hook uses to send as a query parameter to the API, they do not have any connection to each other anymore. Am I using React all wrong? Is this where the context API would be smart to use? I basically want to share state between to different functional components, and render them separately on a webpage.
This is my frontend code:
import React, { useEffect, useState } from "react";
function useLoading(loadingFunction, sliderValue) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState();
const [data, setData] = useState([]);
async function load() {
try {
setLoading(true);
setData(await loadingFunction());
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}
useEffect(() => {
load();
}, [sliderValue]);
return { loading, error, data };
}
async function fetchJSON(url, sliderValue) {
const res = await fetch(url `?numberOfMovies=${sliderValue}`);
if (!res.ok) {
throw new Error(`${res.status}: ${res.statusText}`);
}
return await res.json();
}
function randomNumber() {
return Math.floor(Math.random() * 20000000000);
}
function LoadingPage() {
return (
<>
<div className="loading one" />
<div className="loading two" />
<div className="loading three" />
<div className="loading four" />
</>
);
}
function MovieCard({ movie: { title, plot, year, poster } }) {
return (
<div className={"movie-card"}>
<h3>
{title} ({year})
</h3>
<p>{plot}</p>
{poster && <img width={100} src={poster} alt="Poster" />}
</div>
);
}
function ListMovies() {
const [sliderValue, setSliderValue] = useState("300");
const { loading, error, data } = useLoading(
async () => fetchJSON("/api/movies", sliderValue),
sliderValue
);
if (loading) {
return <LoadingPage />;
}
if (error) {
return (
<div>
<h1>Error</h1>
<div>{error.toString()}</div>
</div>
);
}
function handleSliderChange(e) {
let value = (document.getElementById("slider").value = e.target.value);
document.getElementById("slider-value").innerHTML =
value <= 1 ? `${value} movie` : `${value} movies`;
setSliderValue(value);
}
return (
<div className={"movies-container"}>
<h1>Movies</h1>
<p>Sorted by highest rated on Metacritic. All movies are from Ukraine.</p>
<input
onChange={handleSliderChange}
type="range"
min="1"
max="1000"
className="slider"
id="slider"
/>
<p id="slider-value" />
<div>
{data.map((movie) => (
<MovieCard key={randomNumber()} movie={movie} />
))}
</div>
</div>
);
}
export function MainPage() {
return (
<div>
<ListMovies />
</div>
);
}
CodePudding user response:
It might be enough to "lift" the state to a common ancestor. State management in React is a surprisingly complex topic and worth reading up on standard approaches. Lifting state is one of them, because components don't "usually" talk to each other "horizontally". Props flow down. There are other ways to manage this such as Context or Redux, or even "non" React approaches such as pub/sub.
The good news is that having experienced the pain points first hand, you'll appreciate some of the patterns for solving the problems.
In my opinion I'm not sure there is a "wrong" way to do things, as long as it works. But there are definitely approaches that make life hard and others that make life easier.
If you could whittle down your issue to a very specific question, without so much explanation, you're likely to get better help.