'React Router: props aren't passed down when directly accessing the URL of child component. Will I need to use the same state in both parent and child?
I'm pretty new to React and React Router so apologies if this is a silly question or answered elsewhere - I did a search but couldn't find a similar question.
I'm using React Router v6 and React Hooks. My very basic full-stack app is focused on keeping track of musical artists and their released projects (albums, mixtapes, EPs, etc.). My App component will render the ProjectList component at the '/' route, and the ArtistList component at the '/artists' route.
Relevant portions of App.js:
import React, { useState, useEffect } from 'react';
import { Routes, Route, Link } from 'react-router-dom';
import axios from 'axios';
import ProjectList from './ProjectList';
import ArtistList from './ArtistList';
import { Button } from './styles.js';
const App = () => {
const [ projects, setProjects ] = useState([]);
const getProjects = () => axios('projects').then(({ data }) => setProjects(data));
useEffect(getProjects, []);
return (<>
<Link to="/">
<Button>Projects</Button> // Button is using styled-components
</Link>
<Link to="/artists">
<Button>Artists</Button>
</Link>
<Routes>
<Route path="/" element={<ProjectList projects={projects} />} />
<Route path="/artists" element={<ArtistList projects={projects} />} />
</Routes>
</>);
};
This all works as expected when I click the Projects and Artists buttons. However, when I attempt to access the '/artists' route directly through the URL bar of my browser, nothing renders and I get an error about how the projects prop is undefined. My assumption is that because I'm trying to access the ArtistList component before rendering the App component itself, getProjects doesn't run and so no projects props are passed down to ArtistList.
I need the list of projects to render my ArtistList component. Each element of projects is an object that includes an artist property that I use to create my list of artist names and a dateAdded property that I use to sort the artist list by recency. I also keep track of how many projects each artist has.
Relevant portions of ArtistList.jsx (the return block contains some styled-components - Options, Header, TextWrapper, and Artist):
const ArtistList = ({ projects }) => {
const [ artists, setArtists ] = useState([]);
const [ sortBy, setSortBy ] = useState('name');
const getArtists = () => {
const artists = [];
let curArtist = projects[0].artist;
let projectCount = 0;
const now = new Date();
let firstAdded = now;
for (const { artist, dateAdded } of projects) {
if (artist !== curArtist) {
artists.push({ name: curArtist, projectCount, firstAdded });
curArtist = artist;
projectCount = 0;
firstAdded = now;
}
projectCount++;
const projectDate = new Date(dateAdded);
if (projectDate < firstAdded)
firstAdded = projectDate;
}
setArtists(artists);
};
useEffect(getArtists, [ projects, sortBy ]);
if (sortBy === 'number')
artists.sort((a, b) => b.projectCount - a.projectCount);
else if (sortBy === 'recency')
artists.sort((a, b) => b.firstAdded - a.firstAdded);
return (<>
<Options>
<label htmlFor="sortBy">Sort by: </label>
<select id="sortBy" value={sortBy} onChange={e => setSortBy(e.target.value)}>
<option value="artist">Name</option>
<option value="number"># of Projects</option>
<option value="recency">Recently Added</option>
</select>
</Options>
<Header>
<TextWrapper>Name</TextWrapper>
<TextWrapper># of Projects</TextWrapper>
</Header>
{artists.map(({ name, projectCount }, idx) => (
<Artist key={idx}>
<TextWrapper>{name}</TextWrapper>
<TextWrapper>{projectCount}</TextWrapper>
</Artist>
))}
</>);
};
I currently see 2 solutions to this issue (I'm sure I'm missing more):
Create a
projectsstate and agetProjectsin the ArtistList component, pretty much exactly as they are in the parent App component. Whenever I render ArtistList, I will make a request to my back end to retrieve a list of projects and then invokesetProjects. I'd then derive theartistsstate fromprojects, as before. Of course, I'd no longer need to pass theprojectsprops from App to ArtistList. However, I would then have the exact same state values/functionality in both parent and child components, and this feels to me like a violation of fundamental React principles.Put a collection of Artists into my Mongo database, in addition to the Projects collection I currently have. I would have to implement something similar to the
getArtistsfunction, but in the back end this time, then put the list of artist data into my database. Then, every time I try to render ArtistList, I will make a request to my back end to retrieve the list of artists and go from there.
The third option is doing nothing, but then I'm unable to access the '/artists' page directly through the URL.
What would be best practice and the best approach in this instance? Any help is appreciated!
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
