Home > Software design >  How to redirect from page to page by id?
How to redirect from page to page by id?

Time:11-15

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]);
  • Related