how to pass pass details of item using routes - reactjs

I've fetched a tracklist from API and when I click on the track name I have to be redirected to Details page where description of current track is displayed.
This is component where I fetch data and display in the list.
const TrackList = () => {
const url = `http://ws.audioscrobbler.com/2.0/?method=chart.gettoptracks&api_key=key=json`
const [trackList, setTrackList] = useState([])
useEffect(() => {
loadData()
}, [])
const loadData = async () => {
const res = await fetch(url)
const data = await res.json()
setTrackList(data.tracks.track)
console.log(data.tracks.track)
}
return (
<div>
<Container>
<h1 className='mb-5 mt-5'>Top TrackList</h1>
{trackList.map(item => {
return (
<Row className='mt-1' style={{padding: '5px', border: '1px solid #000', display: 'flex', justifyContent: 'flex-start', alignItems: 'center'}}>
<Col lg={1} md={1} sm={1}>
<a href={item.artist.url}><img src={item.image[1]['#text']} /></a>
</Col>
<Col lg={11} md={11} sm={11}>
<Link to='/:mbid'><h6>{item.artist.name}</h6></Link>
<p>"{item.name}"</p>
</Col>
</Row>
)
})}
</Container>
</div>
)
}
Here I created Details page where main info has to be displayed :
const TrackListDetails = () => {
console.log('props', this.props.match.mbid)
return (
<Container>
</Container>
)
}
But Routes I used in App.js
Am I right ?
function App() {
return (
<div>
<Router>
<NavBar />
<Route path="/" component={TrackList}/>
<Route path="/details/:mbid" component={TrackListDetails}/>
</Router>
</div>
);
}

As stated in react router documentation you can pass state property to link element
<Link
to={{
pathname: "/courses",
state: { description: 'some description' }
}}
/>
You can use it in details page like this:
const { state } = useLocation();
const { description } = state;
But the problem is that you have to persist description when user reloads page. That's why I recommend fetching track details when details page is mounted.

Related

How to use useState in loop?

I'm trying develop a little app in which on you can select multiple music album, using Next.js.
I display my albums like the image below, and I would like to add a check mark when clicked and hide it when clicked again.
My code looks like that :
import Image from "next/image";
import {Card,CardActionArea} from "#mui/material";
import { container, card } from "../styles/forms.module.css";
import album from "../public/album.json"
export default function Album() {
const albumList = {} ;
function addAlbum(albumId, image){
if ( !(albumId in albumList) ){
albumList[albumId] = true;
//display check on image
}
else{
delete albumList[albumId]
//hide check on image
}
console.log(albumList)
}
return (
<div className={container}>
{Object.keys(album.albums.items).map((image) => (
<Card className={card}>
<CardActionArea onClick={() => addAlbum(album.albums.items[image].id)}>
<Image alt={album.albums.items[image].artists[0].name} width="100%" height="100%" src={album.albums.items[image].images[1].url} />
</CardActionArea>
</Card>
))}
</div>
);
}
I know I should use useState to do so, but how can I use it for each one of my albums?
Sorry if it's a dumb question, I'm new with Hook stuff.
I think there are a few ways to go about this, but here is a way to explain the useState in a way that fits the question. CodeSandbox
For simplicity I made a Card component that knowns if it has been clicked or not and determines wither or not it should show the checkmark. Then if that component is clicked again a clickhandler from the parent is fired. This clickhandle moves the Card into a different state array to be handled.
The main Component:
export default function App() {
const [unselectedCards, setUnselectedCards] = useState([
"Car",
"Truck",
"Van",
"Scooter"
]);
const [selectedCards, setSelectedCards] = useState([]);
const addCard = (title) => {
const temp = unselectedCards;
const index = temp.indexOf(title);
temp.splice(index, 1);
setUnselectedCards(temp);
setSelectedCards([...selectedCards, title]);
};
const removeCard = (title) => {
console.log("title", title);
const temp = selectedCards;
const index = temp.indexOf(title);
temp.splice(index, 1);
setSelectedCards(temp);
setUnselectedCards([...unselectedCards, title]);
};
return (
<div className="App">
<h1>Current Cards</h1>
<div style={{ display: "flex", columnGap: "12px" }}>
{unselectedCards.map((title) => (
<Card title={title} onClickHandler={addCard} key={title} />
))}
</div>
<h1>Selected Cards</h1>
<div style={{ display: "flex", columnGap: "12px" }}>
{selectedCards.map((title) => (
<Card title={title} onClickHandler={removeCard} key={title} />
))}
</div>
</div>
);
}
The Card Component
export const Card = ({ onClickHandler, title }) => {
const [checked, setChecked] = useState(false);
const handleClickEvent = (onClickHandler, title, checked) => {
if (checked) {
onClickHandler(title);
} else {
setChecked(true);
}
};
return (
<div
style={{
width: "200px",
height: "250px",
background: "blue",
position: "relative"
}}
onClick={() => handleClickEvent(onClickHandler, title, checked)}
>
{checked ? (
<div
id="checkmark"
style={{ position: "absolute", left: "5px", top: "5px" }}
></div>
) : null}
<h3>{title}</h3>
</div>
);
};
I tried to make the useState actions as simple as possible with just a string array to help you see how it is used and then you can apply it to your own system.
You do not need to have a state for each album, you just need to set albumList as a state:
const [albumList, setAlbumList] = setState({});
function addAlbum(albumId, image) {
const newList = {...albumList};
if(!(albumId in albumList)) {
newList[albumId] = true;
} else {
delete albumList[albumId]
}
setAlbumList(newList);
}
And then in your loop you can make a condition to display the check mark or not by checking if the id is in albumList.

How do I change my URL on click in ReactJS

The below code adds a next button to get the next 20 items from my backend, on clicking the button the data changes and I get my next 20 items, but the url does not change.
function PokemonList() {
const classes = useStyles();
let [pageNum, setPageNum] = useState(0);
const { loading, error, data } = useQuery(pokemonList, { variables: { pageNum: pageNum } });
function handleClick(e){
e.preventDefault();
setPageNum(parseInt(pageNum)+1)
}
if(error) {
return <h1> error</h1>;
}
if(loading) {
return <h1> loading</h1>;
}
return (
<div className="App">
{data.pokemonList.map((data) => (
<Card className={classes.card} variant='outlined'>
<CardHeader className={classes.titleHead} title={data.id} />
<CardMedia
className={classes.media}
component='img'
image={data.url}
title='image'
/>
<CardContent>
<Typography variant='body2' color='textSecondary' component='span'>
<p>{data.name}</p>
<br/>
<br/>
<br></br>
</Typography>
</CardContent>
</Card>
))}
<Link onClick={handleClick} className='characterlink2' to={`/pokemon/page/${parseInt(pageNum)+1}`}>
<button>
Next
</button>
</Link>
</div>
);
}
export default PokemonList;
How can I fix this? I am not sure that the "to" and "onClick" work together. How do I change the url along with the data?
Issue
e.preventDefault(); in the click handler prevents the default navigation action from occurring.
Solution
I don't see any reason for this action to be prevented, so I suggest removing this call to prevent the default action.
function handleClick(e){
setPageNum(page => page + 1);
}
Preferred solution
Assuming you've a route with path="/pokemon/page/:page" you should use the useParams hook and "sniff" the current page. This completely eliminates the need to synchronize the URL path and local React state, there's only one source of truth, the URL path.
import { useParams } from 'react-router-dom';
...
function PokemonList() {
const classes = useStyles();
const { page } = useParams();
const { loading, error, data } = useQuery(
pokemonList,
{ variables: { pageNum: page } },
);
if (error) {
return <h1>error</h1>;
}
if (loading) {
return <h1>loading</h1>;
}
return (
<div className="App">
{data.pokemonList.map((data) => (
...
))}
<Link
className='characterlink2'
to={`/pokemon/page/${Number(page) + 1}`}
>
<button type="button">Next</button>
</Link>
</div>
);
}

React change view and then scroll to element

I want to link from one view to another and then scroll to a specific element. I'm not intrested in any animations, only want to have the element in view. The link from one view to another is done through react router.
I guess I could somehow create references on the elements I want to scroll to and pass them to the other view, but don't know if that's the correct approach?
A simple example. (Not working, but hopefully you understand what I want to achieve)
const ViewOne = () => {
const navigate = useNavigate(); // From react-router v6
return (
<p onClick={() =>
{
navigate("ViewTwo");
// What more do I have to add?
}}>
Link to section two, in view two
</p>
);
}
export default ViewOne;
const ViewTwo = () => {
return (
<>
<section style={{height: "100vh"}}></section>
<section style={{height: "100vh"}}>
Scroll here?
</section>
<section style={{height: "100vh"}}></section>
</>);
}
export default ViewTwo;
I'm using react-router-dom-v6
Give the sections you want to target and scroll to id attributes. Pass a target id in route state. Use a useEffect hook to target the element and scroll it into view.
Example:
const ViewOne = () => {
const navigate = useNavigate(); // From react-router v6
return (
<p
onClick={() => {
navigate("/viewtwo", { state: { targetId: "section2" } });
}}
>
Link to section two, in view two
</p>
);
};
...
const ViewTwo = () => {
const { state } = useLocation();
const { targetId } = state || {};
useEffect(() => {
const el = document.getElementById(targetId);
if (el) {
el.scrollIntoView();
}
}, [targetId]);
return (
<>
<section id="section1" style={{ height: "100vh" }}></section>
<section id="section2" style={{ height: "100vh" }}>
Scroll here?
</section>
<section id="section3" style={{ height: "100vh" }}></section>
</>
);
};
...
<Router>
<Routes>
...
<Route path="/viewone" element={<ViewOne />} />
<Route path="/viewtwo" element={<ViewTwo />} />
...
</Routes>
</Router>
you can use "useRef" to scroll to that position with click event or try useEffect for scroll to that position after component rendered.
const ViewTwo = () => {
const scroller = useRef(null)
const executeScroll = () => scroller.current.scrollIntoView()
return (
<>
<section style={{height: "100vh"}}></section>
<section ref={scroller} style={{height: "100vh"}}>
Scroll here?
</section>
<button onClick={executeScroll}> Click to scroll </button>
<section style={{height: "100vh"}}></section>
</>);
}
export default ViewTwo;

How to construct dynamic query string in ReactJS

I have a React app which I want to have this query string /contacts?page=${pageNumber}
where page number have to change when I go to the next page, I use newer version of react-router-dom which doesn't have useHistory hook. Now my URL is only http://localhost:3000/.
I have this code:
const Contact = (props) => {
let array = Object.values(props);
const navigate = useNavigate();
const [pageNumber, setPageNumber] = useState(1);
const [contacts, setContacts] = useState([]);
const [entry, setEntry] = useState([]);
const [searchTerm, setSearchTerm] = useState('');
const [isSynchronizedClicked, setIsSynchronizedClicked] = useState(false);
const handlePaging = (params) => {
setPageNumber(params + 1);
//navigate(`/contacts?page=${pageNumber}`);
};
useEffect(() => {
setTimeout(() => {
if (searchTerm !== '') {
search();
}
}, 500);
}, [searchTerm, search]);
return (
<>
<div style={{ height: 630, width: '100%' }}>
<DataGrid
id={() => searchTerm === '' ? array.map((contact) => contact.id) : contacts.map((contact) => contact.id)}
rows={searchTerm === '' ? array : contacts}
sx={{
'.MuiDataGrid-columnHeaderTitle': {
fontWeight: 'bold',
},
'.MuiDataGrid-columnHeaders': {
backgroundColor: '#d3d3d3'
},
'.MuiDataGrid-row': {
maxHeight: '51px !important',
minHeight: '51px !important'
},
'.MuiDataGrid-virtualScroller': {
overflow: 'hidden'
}
}}
onPageChange={handlePaging}
columns={columns}
pageSize={10}
rowsPerPageOptions={[10]}
checkboxSelection
onRowDoubleClick={e => navigateToDetails(e)}
onSelectionModelChange={e => setEntry(e)}
/>
</div>
{isSynchronizedClicked && <ClickAwayListener onClickAway={() => setIsSynchronizedClicked(false)}>
<div className='synchronize-popup'>
Do you want to synchronize all contacts ?
<div className='second-message'>This will take a while.</div>
<div className='synchronize-popup-buttons-container'>
<button className='buttons reject' onClick={() => setIsSynchronizedClicked(false)}>No</button>
<button className='buttons approve' onClick={synchronize}>Yes</button>
</div>
</div></ClickAwayListener>}
</>
);
};
And this code in App.js:
function App() {
return (
<BrowserRouter>
<div className="app">
<Routes>
<Route path='/' element={<RenderContacts />} />
<Route path='/details/:id' element={<Details />} />
</Routes>
</div>
</BrowserRouter>
);
}
If someone knows how to do that, I'll be grateful.
This is a common gotcha with React setState.
https://reactjs.org/docs/state-and-lifecycle.html
Read the section:
State Updates May Be Asynchronous
I assume this is the failure: pageNumber likely will not be ready when used.
setPageNumber(params + 1);
navigate(`/contacts?page=${pageNumber}`);
Instead, just use params + 1 inside the navigate function.
After navigation, you will want to use useSearchParams() (assuming router v6?) to get the updated value.

Passing a function (props) back to parent React Context Api

Hello I am having trouble passing the id on click back to the context file to render the correct info on the page. I have tried just about everything and am not to sure what to do to make this work?!
Context file:
this is where in headers.id = res.data[1].playerId i know i need a function to pass in the player id but am unsure how to do it from my handleClick.
// use mlbData in table
setMlbData(res.data)
// const pid = res.data.find(player => player.playerId == playerId)
console.log(res.data)
headers.id = res.data[1].playerId
mlbapi = `/mlb/player/${headers.id}`
// headers.id = res.data[1].playerId
console.log(res.data)
console.log(headers)
res = await axios.get(url + mlbapi, {headers})
setPlayerStats(res.data)
}
PlayerList.js file:
const handleClick = (e) => {
// console.log("I have been clicked", e.target.id)
// console.log(`${playerId}`)
history.push(`/stats/${e.target.id}`)
console.log(e.target.id)
}
// const { fullName, teamImage, mlbData, playerId } = this.context
const Team = ({ tableManager, value, field, data, column, colIndex, rowIndex }) => {
return (
<div onClick={ handleClick } className='rgt-cell-inner' style={{display: 'flex', alignItems: 'center', overflow: 'hidden'}}>
<img src={data.teamImage} alt="user avatar" id={ playerId }/>
<span className='rgt-text-truncate' style={{marginLeft: 10}}>{value}</span>
</div>
)
}
App.js file:
function App() {
return (
<div className="App">
<GetMlbData>
<Navbar />
<div className="content">
<Switch>
<Route exact path="/">
<PlayerList />
</Route>
<Route path="/stats/:id">
<Stats />
</Route>
</Switch>
</div>
</GetMlbData>
</div>
);
}

Resources