Как поступить, когда пользователь вводит URL-адрес вручную, а не по ссылке? - PullRequest
3 голосов
/ 12 июля 2020

У меня есть компонент (ContainerMain. js), который извлекает список контейнеров из API, а затем отображает каждое имя контейнера внутри кнопки. При нажатии одной из кнопок состояние будет передано другому компоненту (ContainerContents. js) с использованием «history.pu sh (/ containercontents / $ {id}, {container: container})». Это работает, когда я выбираю кнопку в ContainerMain. js.

Для обработки, когда я обновляю sh и вручную ввожу URL-адрес («http://localhost: 3000 / containercontents / 6»), что приводит к При рендеринге ContainerContents. js я использую "props.match.params.id" для получения идентификатора контейнера и go оттуда.

Это все, что я получил, потому что я не Не знаю, как использовать только один источник данных, в зависимости от того, исходят ли эти данные от URL-адреса или кнопки. Имеет ли это смысл? Даже если бы я это сделал, это решение кажется ужасным, и я чувствую, что мне не хватает очевидного способа справиться с этим сценарием. Я не очень знаком с Redux / Context, но верю, что могу использовать один из них, чтобы исправить это. Я просто хотел бы знать, есть ли более простой и распространенный способ обработки этого сценария перед использованием Redux / Context, предполагая, что я могу использовать их для этого.

ContainerMain. js

import React, { useState, useEffect } from 'react'
import { Link, Route, useHistory } from 'react-router-dom'
import ContainerContents from './ContainerContents';

const ContainerMain = (props) => {
  const [containers, setContainers] = useState([]);
  // const [showContainerContents, setShowContainerContents] = useState(false);
  // const [selectedContainerId, setSelectedContainerId] = useState();
  const history = useHistory();
  
  const getContainers = async () => {
    const url = "/api/containers";    

    await fetch(url)
      .then(response => response.json())
      .then(response => setContainers(response))
      .catch(() => console.log("Can’t access " + url + " response. Blocked by browser?"))
  }

  const deleteContainer = async (containerId) => {
    let result = window.confirm("Are you sure you want to delete your " + containers.find(container => container.id === containerId).name + "?");
    if (result) {
        setContainers(containers.filter(container => container.id !== containerId))

        fetch(`api/containers/${containerId}`, {
             method: 'DELETE',
        })
        .then(res => res.json()) // or res.json()
        .then(res => console.log(res))
    } 
    else {
        return
    }
  }


  // const onButtonClick = (containerId) => {
  //   setShowContainerContents(true)
  //   setSelectedContainerId(containerId)
  // }

  useEffect(() => {
    getContainers();  
  }, []);

  
  return (
    <div>   
      <strong>MY FOOD CONTAINERS</strong>
      <br></br><br></br>

      <ul>
        {containers.map(container => (
          <li key={container.id}>
            <div>
              <button onClick={() => deleteContainer(container.id)} className="button is-rounded is-danger">Delete</button>
              <button onClick={() => history.push(`/containercontents/${container.id}`, {container: container})} className="button is-primary">{container.name}</button>
              

              {/* <Route 
              path="/ContainerContents" component={HomePage}/> */}

              {/* <Link to={`/containercontents/${container.id}`}>{container.name}</Link> */}
              {/* <Link to={{
                  pathname: `/containercontents/${container.name}`,
                  state: {container: container}
                }}>{container.name}
              </Link> */}
            </div>
          </li>
        ))}
      </ul>  

   
      {/* {showContainerContents ? <ContainerContents containerId={selectedContainerId}  /> : null}   */}
      
      
    </div>
  );
}

export default ContainerMain;

Содержимое контейнера. js

import React, { useEffect, useState} from 'react';

const ContainerContents = (props) => {
  const [container, setContainer] = useState({});



  useEffect(() => {
    const fetchContainer = async () => { 
      const { id } = props.match.params;

      console.log("This is the id: " + id)

      let url = `/api/containers/${id}`;
  
      await fetch(url)
        .then(response => response.json())
        .then(response => setContainer(response))
        .catch(() => console.log("Can’t access " + url + " response. Blocked by browser?"))
  }

  fetchContainer();   

      
  }, [props.match.params]);



  


  
  return (
    <div>

          {/* will not work when I refresh and enter url as expected*/}
         <h1>{props.location.state.container.name}</h1> 



         <h1>{container.name}</h1> 
         

    </div>
  )
}

export default ContainerContents;

Ответы [ 2 ]

1 голос
/ 12 июля 2020

Я не знаю, как использовать только один источник данных в зависимости от того, исходят ли эти данные от URL-адреса или кнопки.

Это действительно вся идея использования Redux, или какая-то другая система управления состоянием - наличие одного универсального хранилища истины для всего приложения, чтобы вы всегда знали, откуда поступают ваши данные. Так что да, в конечном итоге было бы лучше реализовать что-то подобное, и вы, вероятно, захотите это после того, как столкнетесь с огромными состояниями React и буровыми работами, но это ни в коем случае не является существенным. ваши данные containers находятся достаточно высоко в дереве компонентов для всех компонентов, которым они нужны для доступа к ним, вы можете передать их им через props. Контейнеры всегда будут жить только в одном месте, поэтому вы можете быть уверены, что если его там нет, его нужно доставить. Из всего вашего прокомментированного кода немного неясно, действительно ли ContainerMain должен отображать ContainerContents, но, допустим, это не так. Вот упрощенная версия вашей структуры компонентов - идея в основном заключается в том, что вы проверяете, существует ли свойство данных, прежде чем запрашивать его в дочернем элементе.

Настройка родительского компонента, который отвечает за хранение всех containers:

export default function App() {
  const [containers, setContainers] = useState([]);

  return (
    <Router>
      <Switch>
        <Route exact path="/">
          <ContainerMain
            containers={containers}
            setContainers={setContainers}
          />
        </Route>
        <Route exact path="/containercontents/:id">
          <ContainerContents
            containers={containers}
            setContainers={setContainers}
          />
        </Route>
      </Switch>
    </Router>
  );
}

Компонент, из которого мы извлекаем все containers, отправляя их родительскому объекту и получая их обратно как реквизиты:

function ContainerMain({ containers, setContainers }) {
  const history = useHistory();

  const getContainers = async () => {
    const url = "/api/containers";

    await fetch(url)
      .then(response => response.json())
      .then(response => setContainers(response))
      .catch(() =>
        console.log("Can’t access " + url + " response. Blocked by browser?")
      );
  };

  useEffect(() => {
    getContainers();
  }, [getContainers]);

  return (
    <div>
      <strong>MY FOOD CONTAINERS</strong>
      <br />
      <br />

      <ul>
        {containers.map(container => (
          <li key={container.id}>
            <div>
              <button
                onClick={() =>
                  history.push(`/containercontents/${container.id}`)
                }
                className="button is-primary"
              >
                {container.name}
              </button>
            </div>
          </li>
        ))}
      </ul>
    </div>
  );
}

Specifi c container component. Если контейнер не существует в родительском состоянии, выберите его - функция возврата автоматически отобразит его, если / когда он существует в родительском состоянии.

function ContainerContents({ containers, setContainers }) {
  const match = useRouteMatch();

  useEffect(() => {
    const fetchContainer = async id => {
      let url = `/api/containers/${id}`;

      await fetch(url)
        .then(response => response.json())
        .then(response => {
          setContainers([...containers, response]);
        })
        .catch(() =>
          console.log("Can’t access " + url + " response. Blocked by browser?")
        );
    };

    const found = containers.find(el => el.id === match.params.id);

    if (!found) {
      fetchContainer(match.params.id);
    }
  }, [match.params, containers, setContainers]);

  function selectContainer() {
    const found = containers.find(el => el.id === match.params.id);

    return found ? (
      <div>
        <h1>{found.name}</h1>
      </div>
    ) : null;
  }

  return selectContainer();
}
1 голос
/ 12 июля 2020

Насколько я понимаю, вы хотите иметь возможность брать идентификатор из нажатия кнопки, когда используется этот метод, и вместо этого брать идентификатор из URL-адреса, когда он достигается вручную.

A простой способ сделать это может заключаться в том, чтобы просто получить идентификатор из URL-адреса в случае, если props.match.params не существует? Вы должны иметь возможность использовать хук useLocation для доступа к нему, и я уверен, что есть более простой способ, но вы можете просто проанализировать нужный идентификатор.

например:

  const location = useLocation();

  let url_segs = location.pathname.split("/");
  let id = url_segs[url_segs.length - 1]

Также я считаю, что response-router-dom теперь поддерживает useParams для более простого метода доступа к параметрам, если вам интересно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...