There is a page on which there are many Stories, when you click on any story, a new page with this Story should open. But when I open a new page, I don't get the "id".json of this Story, instead I get undefined.json . Help me please
Code with home page `
import React, { useEffect, useState } from 'react';
import { Story } from '../components/Story';
import { getStoryIds } from '../services/hnApi';
import { useInfiniteScroll } from '../hooks/useInfiniteScroll';
export const Home = () => {
const {count} = useInfiniteScroll();
const [storyIds, setStoryIds] = useState([]);
useEffect(() => {
getStoryIds().then(data => setStoryIds(data));
}, []);
return storyIds.slice(0, count).map(storyId => (
<Story key={storyId} storyId={storyId} />
));
};
export default Home;
`
Code with stories
`
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useState, useEffect} from 'react';
import { getStory } from '../services/hnApi';
import { Card, Container, Nav } from 'react-bootstrap'
import { mapTime } from '../mappers/mapTime';
import { Link } from 'react-router-dom';
export const Story = ({storyId}) => {
const[story, setStory] = useState({});
const {id} = story;
useEffect(() => {
const timer = setTimeout(() => {
window.location.reload();
}, 60000);
return () => clearTimeout(timer);
}, []);
useEffect(() => {
getStory(storyId).then((data) => data.url && setStory(data))
}, []);
return story && story.url ? (
<Container data-id={id} fluid="xl" style={{marginTop: "1%", marginBottom: "1%"}}>
<Nav.Item>
<Link to={`/storyPage/${id}`} state={{ id: 'id' }}
style={{ textDecoration: 'none' }}>
<Card style={{ paddingLeft: "1%", paddingTop: "1%", paddingBottom: "1%"}}>
<Card.Title>{story.title}</Card.Title>
<br/>
<Card.Text style={{color: 'black', fontStyle: 'italic', fontSize: '15px'}}>Author: {story.by}</Card.Text>
<Card.Text style={{color: 'black', fontStyle: 'italic', fontSize: '15px'}}>Posted: {mapTime(story.time)}</Card.Text>
<Card.Text style={{color: 'black', fontStyle: 'italic', fontSize: '15px'}}>Rating: {story.score}</Card.Text>
</Card>
</Link>
</Nav.Item>
</Container>
):null;
};
export default Story;
`
The code that should open the story I need
`
import React, { useEffect, useState } from 'react';
import { getStoryIds } from '../services/hnApi';
import { Button, Container } from 'react-bootstrap';
import { ArrowLeft } from 'react-bootstrap-icons';
import { StorySingular } from './StorySingular';
export const StoryPage = ( ) => {
const [storyId, setStoryId] = useState([]);
const {id} = storyId;
useEffect(() => {
getStoryIds().then(data => setStoryId(data));
}, []);
return storyId.slice(0, 1).map(storyId => (
<Container key={storyId}>
<Button style={{marginLeft: '1%', marginTop:'1%'}} variant="outline-info" href='/'><ArrowLeft/></Button>
<br/>
<StorySingular storyId={id} />
</Container>
));
};
export default StoryPage;
`
`
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useEffect, useState } from 'react';
import { getStory } from '../services/hnApi';
import { Card, Container, Nav } from 'react-bootstrap';
import { Comments } from '../comments/Comments';
import { mapTime } from '../mappers/mapTime';
export const StorySingular = ({ storyId }) => {
const [story, setStory] = useState({});
const { kids, id, url } = story;
useEffect(() => {
getStory(storyId).then((data) => {
if (data && data.url) {
setStory(data);
}
});
}, []);
return story && url ? (
<>
<Container data-id={id}>
<Card style={{paddingLeft: "1%", paddingTop: "1%"}}>
<Card.Title style={{paddingLeft: "1%"}}>{story.title}</Card.Title>
<Nav.Link href={story.url}>{url}</Nav.Link>
<br/>
<Card.Text style={{color: 'black', fontStyle: 'italic',paddingLeft: "1%", fontSize: '14px'}}>Author: {story.by}</Card.Text>
<Card.Text style={{color: 'black', fontStyle: 'italic',paddingLeft: "1%", fontSize: '14px'}}>Posted: {mapTime(story.time)}</Card.Text>
<Card.Text style={{color: 'black', fontStyle: 'italic',paddingLeft: "1%", fontSize: '14px'}}>Comments: {story.descendants}</Card.Text>
<Card.Text>{kids && <Comments commentIds={kids} root />}</Card.Text>
</Card>
</Container>
</>
): null;
};
export default StorySingular;
`
<Router>
<Switch>
<Route exact path="/storyPage/:id" component={StoryPage} />
</Switch>
</Router>
CodePudding user response:
Issue
From what I can understand of the code, the Home
component fetches the story ids and maps them to an array of Story
components.
export const Home = () => {
const {count} = useInfiniteScroll();
const [storyIds, setStoryIds] = useState([]);
useEffect(() => {
getStoryIds().then(data => setStoryIds(data));
}, []);
return storyIds.slice(0, count).map(storyId => (
<Story key={storyId} storyId={storyId} />
));
};
The Story
component then takes the passed storyId
prop and fetches the specified story by id and renders it to a link and some basic details in a card component.
export const Story = ({ storyId }) => {
const[story, setStory] = useState({});
const {id} = story;
useEffect(() => {
const timer = setTimeout(() => {
window.location.reload();
}, 60000);
return () => clearTimeout(timer);
}, []);
useEffect(() => {
getStory(storyId).then((data) => data.url && setStory(data))
}, []);
return story && story.url ? (
<Container data-id={id} fluid="xl" style={{ marginTop: "1%", marginBottom: "1%" }}>
<Nav.Item>
<Link
to={`/storyPage/${id}`}
state={{ id: 'id' }}
style={{ textDecoration: 'none' }}
>
....
</Link>
</Nav.Item>
</Container>
) : null;
};
export default Story;
The StoryPage
component is rendered on the path being linked to:
<Route exact path="/storyPage/:id" component={StoryPage} />
It's from here where it looks like the logic goes a little sideways. The StoryPage
component requests all the story ids again instead of fetching a specific story. This should be the same array of ids that was fetched by the Home
component.
export const StoryPage = () => {
const [storyId, setStoryId] = useState([]);
const { id } = storyId;
useEffect(() => {
getStoryIds().then(data => setStoryId(data)); // <-- fetches ids again
}, []);
return storyId.slice(0, 1).map(storyId => (
<Container key={storyId}>
<Button
style={{ marginLeft: '1%', marginTop: '1%' }}
variant="outline-info"
href='/'
>
<ArrowLeft />
</Button>
<br/>
<StorySingular storyId={id} />
</Container>
));
};
The StorySingular
component then appears to fetch the specific story by id.
export const StorySingular = ({ storyId }) => {
const [story, setStory] = useState({});
const { kids, id, url } = story;
useEffect(() => {
getStory(storyId).then((data) => {
if (data && data.url) {
setStory(data);
}
});
}, []);
return story && url ? (
<>
....
</>
) : null;
};
Solution
I believe your UI could be simplified a bit. The Home
component fetches the story ids and rendered them with links to specific story.
<Router>
<Switch>
<Route path="/storyPage/:id" component={StoryPage} />
<Route path="/" component={Home} />
</Switch>
</Router>
StoryPage
is updated to read the story id value from the route path params and fetch the specific story to be rendered.
import { useParams } from 'react-router-dom';
export const StoryPage = () => {
const { id } = useParams();
const [story, setStory] = useState();
useEffect(() => {
getStory(id)
.then((data) => {
if (data && data.url) {
setStory(data);
}
});
}, [id]); // <-- fetch story when id changes
return (
<Container>
<Button
style={{ marginLeft: '1%', marginTop: '1%'}}
variant="outline-info"
href='/'
>
<ArrowLeft />
</Button>
<br/>
{story && <StorySingular story={story} />}
</Container>
);
};
...
export const StorySingular = ({ story }) => {
const { kids, id, url } = story;
return story && url ? (
<Container data-id={id}>
<Card .... >
<Card.Title .... >
{story.title}
</Card.Title>
<Nav.Link href={story.url}>{url}</Nav.Link>
<br/>
<Card.Text .... >
Author: {story.by}
</Card.Text>
<Card.Text .... >
Posted: {mapTime(story.time)}
</Card.Text>
<Card.Text .... >
Comments: {story.descendants}
</Card.Text>
<Card.Text>
{kids && <Comments commentIds={kids} root />}
</Card.Text>
</Card>
</Container>
): null;
};
CodePudding user response:
Not sure, add storyId dependency and check.
useEffect(() => {
getStory(storyId).then((data) => data.url && setStory(data));
}, [storyId]);