Проблемы с реакцией @reach / Router Как заставить коммутатор работать

#javascript #reactjs #axios

#javascript #reactjs #axios

Вопрос:

Я пытаюсь сделать свой App.js маршрут к my People.jsx и т. Д., Но он работает некорректно. Я надеюсь, что смогу устранить проблему оттуда, если смогу заставить это работать. Я пытался сделать это около 2 часов с помощью правила 20 минут, но с этим мне нужна помощь. Я пробовал другие варианты, но моя цель — theID передать и Person. Я подумываю об использовании {useContext } для этого, но я даже не могу заставить его маршрутизировать. Хотел бы я знать, что я делаю неправильно, чтобы я мог это исправить, но другие люди используют разные типы маршрутизаторов, и я был смущен ими еще больше.

Я обновил его ссылками, которые по-прежнему не подходят для меня, какие-либо другие предложения?

App.js

 import './App.css';
import { useState } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import People from './components/People'
import Planet from './components/Planets'
import Starship from './components/Starships'
import { Router, Link } from '@reach/router';

function App() {
  const [starwarsState, setStarwarsState] = useState('')
  const [theID, setTheID] = useState('')

  const selectedState = (e) => {
    setStarwarsState(e.target.value)
  }

  const switchItem = () => {
    switch (starwarsState) {
      case 'people':
        <Link path='/people/' />;
        break;
      case 'planets':
        <Link path="/planets/" />;
        break;
      case 'starships':
        <Link path='/starships/' />;
        break;
      default:
        return null;
    }
  }

  const addId = e => {
    setTheID(e.target.value)
    console.log(theID)
  }
  return (
    <div className='App'>
      <header className='App-header' >
        Search For: amp;nbsp;
        <select onChange={selectedState} className='form-control-lg bg-dark text-white'>
          <option value='people' active >People</option>
          <option value='planets' >Planets</option>
          <option value='starships' >Starships</option>
        </select>

           amp;nbsp; ID: amp;nbsp;
           <input type='text' onChange={addId} className='form-control-lg col-sm-1 bg-dark text-white' />amp;nbsp;
           <button className='btn-lg btn-warning' onClick={switchItem}  >Search Item</button>
        <Router>
          <People path='/people/' />
          <Planet path="/planets/" />
          <Starship path='/starships/' />
        </Router>
      </header>
      {starwarsState}
    </div>
  )
}

export default App;
  

People.jsx

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

import { Link } from '@reach/router';

const People = props => {

    const [peopleData, setpeopleData] = useState([]);
    useEffect(() => {
        axios.get(`https://swapi.dev/api/people/${props.theID}`)
            .then(response => { setpeopleData(response.data) })

        console.log(peopleData)
    }, []);
    return (
        <div>
            <span> the People have spoken</span> 
            <Link to='/people' />
        </div>
    )
}

export default People;
  

Комментарии:

1. Также не могли бы вы описать, когда поправите меня, чтобы я мог учиться.

2. Любой JSX, который вы хотите отобразить, должен быть отображен с помощью возврата функционального компонента или возвращен функцией в JSX. Вы не можете просто вернуть JSX из обратного вызова click и ожидать, что он будет отображен. Почему вы пытаетесь условно отобразить 3 разных маршрутизатора? Просто безоговорочно отобразите один маршрутизатор со всеми тремя маршрутами и условно перейдите к пути маршрута, отображающему компонент, который вы хотите отобразить. Именно на этом этапе вы можете передавать дополнительные данные в маршруты.

3. можете ли вы привести мне пример того, что вы имеете в виду? Я действительно немного смущен тем, что вы подразумеваете под этим

4. Также я хочу, чтобы он передавал информацию, которую я также установил, поэтому, когда я нажимаю кнопку, она переходит на страницу. Вот как я придумал это в своей голове, поэтому я пытаюсь отображать в зависимости от того, что нажимается. Я пытаюсь найти лучший способ сделать это.

5. Можете ли вы показать мне пример? Как будто это не имеет для меня никакого смысла, за исключением того, что я должен сделать что-то вроде switch(example) People <Link path='/people/'>

Ответ №1:

Проблемы

На самом деле вы не отображаете маршруты / ссылки, switchItem поскольку onClick обратные вызовы не могут возвращать визуализируемый пользовательский интерфейс непосредственно в метод визуализации.

Решение

Безоговорочно отображайте все ваши маршруты одновременно в одном Router и обязательно переходите к ним в switchItem обработчике.

Приложение

 ...
import { Router, navigate } from "@reach/router";
...

function App() {
  const [starwarsState, setStarwarsState] = useState("");
  const [theID, setTheID] = useState("");

  ...

  const switchItem = () => {
    switch (starwarsState) {
      case "people":
        navigate("/people"); // <-- imperative navigation
        break;
      case "planets":
        navigate("/planets");
        break;
      case "starships":
        navigate("/starships");
        break;
      default:
        return null;
    }
  };

  return (
    <div className="App">
      <header className="App-header">
        Search For: amp;nbsp;
        <select
          onChange={selectedState}
          value={starwarsState}
          className="form-control-lg bg-dark text-white"
        >
          <option disabled value="">
            Choose Path
          </option>
          <option value="people">
            People
          </option>
          <option value="planets">Planets</option>
          <option value="starships">Starships</option>
        </select>
        amp;nbsp; ID: amp;nbsp;
        <input
          type="text"
          onChange={addId}
          className="form-control-lg col-sm-1 bg-dark text-white"
        />
        amp;nbsp;
        <button className="btn-lg btn-warning" onClick={switchItem}>
          Search Item
        </button>
      </header>
      <Router>
        <People path="/people" theID={theID} /> // <-- pass `theID` state as prop
        <Planet path="/planets" />
        <Starship path='/starships' />
      </Router>
    </div>
  );
}
  

Люди

 const People = ({ theID }) => {
  const [peopleData, setpeopleData] = useState([]);

  useEffect(() => {
    axios.get(`https://swapi.dev/api/people/${theID}`)
      .then(response => { setpeopleData(response.data) });
  }, [theID]);

  return (
    <div>
      <div>The ID: {theID}</div>
      <span>the People have spoken</span>
    </div>
  );
};
  

Редактировать react-reach-router-issues-как заставить коммутатор работать

Комментарии:

1. Это здорово, но единственная проблема, с которой я сталкиваюсь, заключается в том, что когда я его использую, если я уже использовал его в People.jsx и пытаюсь изменить человека, мне не нужно нажимать на планеты, а затем возвращаться к людям, если я уже нахожусь на этом маршруте.

2. @ALTHEPAL78 Я не понимаю, что вы имеете в виду под всем этим. Что уже используется в People.jsx ? Какой человек меняется? Вы имеете в theID виду состояние, в App котором оно передается People компоненту?

3. Я нажимаю на людей и набираю 1, я получаю эту информацию, но если я попытаюсь ввести 2 в поле ID и нажать поиск, это не позволит мне сделать это снова. Состояние изменяется, но оно не будет запускать или отправлять новый запрос в API

4. @ALTHEPAL78 Ах, я понимаю. Это связано с тем, что эффект имеет пустой массив зависимостей и запускается только один раз при монтировании. Вы можете просто добавить theID в массив зависимостей, если хотите, чтобы эффект снова запускался при theID обновлении в родительском. Я обновил код в ответе и в связанном codesandbox.

Ответ №2:

Используйте императивную маршрутизацию (не оператор switch) с обработчиками событий

В вашем коде используется оператор switch в сочетании с функцией switchItem() . Это не то, как перенаправить пользователя в обязательном порядке (то есть через что-то другое, кроме ссылки, на которую пользователь нажимает непосредственно).

Чтобы принудительно маршрутизировать своих пользователей, используйте navigate метод.

Через документы Reach Router (ссылка):

Иногда вам нужно перемещаться в ответ на что-то другое, а не на то, что пользователь нажимает на ссылку. Для этого у нас есть навигация. Давайте импортируем navigate.

 import {
  Router,
  Link,
  navigate
} from "@reach/router"
  

В вашем случае весь оператор switch можно переписать следующим образом:

 useEffect(() => navigate(`/${starwarsState}`), [starwarsState])
  

useEffect будет следить за изменениями в starwarsState, которые либо будут «людьми», «планетами», либо «звездолетами». Как только произойдет изменение, оно перенаправит пользователя на соответствующий путь.

Решение: только маршрутизация

Следующее решение не реализует axios, оно фокусируется исключительно на логике маршрутизации на стороне клиента.

Я обнаружил некоторые другие проблемы с вашим кодом, когда работал над решением. Вот версия, которую я написал, которая реализует маршрутизацию на уровне параметров, а также выполняет некоторую другую очистку (рефакторинг категорий swapi в объект конфигурации и т. Д.).

App.js

 import React, { useState, useEffect } from 'react'
import { useWhatChanged } from "@simbathesailor/use-what-changed";
import { Router, Link, navigate } from "@reach/router";
import 'bootstrap/dist/css/bootstrap.min.css';
import { People, Person } from './components/People'
import { Planets, Planet } from './components/Planets'
import { Starships, Starship } from './components/Starships'
import './App.css';

function App() {

  // destructure categories from config
  const { people, planets, starships } = config.categories

  // initialize state
  const [starwarsState, setStarwarsState] = useState(people);
  const [theID, setTheID] = useState('');

  // log updates to ID and starwarsState 
  useWhatChanged([starwarsState, theID], 'starwarsState, theID')

  // change state on input from user
  const addId = e => setTheID(e.target.value)
  const selectCategory = (e) => setStarwarsState(e.target.value)

  // route the user based on starwarsState
  useEffect(() => navigate(`/${starwarsState}`), [starwarsState])

  // search swapi based on category and id
  const searchSwapi = e => {
    e.preventDefault()
    navigate(`/${starwarsState}/${theID}`)
  }

  return (
    <div className="App">
      <header className='App-header' >

        Search For:
        <form onSubmit={searchSwapi}>
          <select onChange={selectCategory} className='form-control-lg bg-dark text-white'>
            <option value={people} >People</option>
            <option value={planets} >Planets</option>
            <option value={starships} >Starships</option>
          </select>
          ID:
        <input type='text' onChange={addId} className='form-control-lg col-sm-1 bg-dark text-white' />
          <button className='btn-lg btn-warning' >Search Item</button>
        </form>
      </header>

      <Router>
        <People path='/people/'>
          <Person path=':personId' />
        </People>
        <Planets path="/planets/">
          <Planet path=':planetId' />
        </Planets>
        <Starships path='/starships/'>
          <Starship path=':starshipId' />
        </Starships>
      </Router>

    </div>
  )
}

const config = {
  categories: {
    people: 'people',
    planets: 'planets',
    starships: 'starships'
  }
}

export default App;
  

Planets.js

 import React from 'react'

export const Planets = props => {

  return (
    <div>
      <span> the Planets have spoken</span>
      {props.children}

    </div>
  )
}


export const Planet = props => {

  return (
    <div>
      Planet Data
    </div>
  )
}
  

People.js

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

export const People = props => {
  return (
    <div>
      <span> the People have spoken</span>

      {props.children}
    </div>
  )
}

export const Person = props => {

  return (
    <div>
      Person Data
    </div>
  )
}
  

Starships.js

 import React from 'react'

export const Starships = props => {

  return (
    <div>
      <span> the Starships have spoken</span>

      {props.children}

    </div>
  )
}

export const Starship = props => {

  return (
    <div>
      Starship Data
    </div>
  )
}
  

[ОБНОВЛЕНИЕ] Решение: маршрутизация с помощью вызовов API

Следующее решение берет код сверху и реорганизует его, используя шаблон управления состоянием, предложенный Ли Холлидеем. Решение добавляет три вещи:

  • useContext для управления глобальным состоянием
  • React.memo() для запоминания компонента AppContent
  • react-запрос для управления удаленными вызовами API для SWAPI.

Посмотреть код на GitHub

App.js

 // App.js
import React, {
  useState,
  useEffect,
  createContext,
  useContext,
  memo
} from 'react'
import { ReactQueryDevtools } from "react-query-devtools";
import { useWhatChanged } from "@simbathesailor/use-what-changed";
import { Router, navigate } from "@reach/router";
import 'bootstrap/dist/css/bootstrap.min.css';
import { People, Person } from './components/People'
import { Planets, Planet } from './components/Planets'
import { Starships, Starship } from './components/Starships'
import './App.css';
import Axios from 'axios';


// APP w/ CONTEXT PROVIDER
export default function App() {
  return (
    <>
      <StarwarsProvider>
        <AppContent />
      </StarwarsProvider>
      <ReactQueryDevtools initialIsOpen={false} />
    </>

  )
}

// CREATE CONTEXT
export const StarwarsContext = createContext()

// CONTEXT PROVIDER
function StarwarsProvider({ children }) {

  // import categories
  const categories = config.categories

  // destructure default category of search selection
  const { people } = categories

  // initialize state
  const [category, setCategory] = useState(people);
  const [theID, setTheID] = useState('');


  return (
    <StarwarsContext.Provider value={{
      category,
      setCategory,
      theID,
      setTheID,
      categories,
      fetchStarwarsData
    }}>
      <AppContent />
    </StarwarsContext.Provider>
  )
}

async function fetchStarwarsData(category, id) {
  if (!id) {
    return
  }

  const response = await Axios.get(
    `https://swapi.dev/api/${category}/${id}`
  ).then(res => res.data)
  // const data = await response.json()
  const data = response
  // console.log(data)
  return data
}


// APP CONTENT
const AppContent = memo(() => {

  // import global state into component
  const { category, setCategory } = useContext(StarwarsContext)
  const { theID, setTheID } = useContext(StarwarsContext)

  // destructure categories
  const { categories: { people, planets, starships } } = useContext(StarwarsContext)

  // log updates to ID and category 
  useWhatChanged([category, theID], 'category, theID')

  // change state on input from user
  const addId = e => setTheID(e.target.value)
  const selectCategory = (e) => setCategory(e.target.value)

  // route the user based on category
  useEffect(() => navigate(`/${category}`), [category])

  // search swapi based on category and id
  const searchSwapi = e => {
    e.preventDefault()
    navigate(`/${category}/${theID}`)
  }

  return (
    <div className="App">
      <header className='App-header' >

        Search For:
        <form onSubmit={searchSwapi}>
          <select onChange={selectCategory} className='form-control-lg bg-dark text-white'>
            <option value={people} >People</option>
            <option value={planets} >Planets</option>
            <option value={starships} >Starships</option>
          </select>
          ID:
        <input type='text' onChange={addId} className='form-control-lg col-sm-1 bg-dark text-white' />
          <button className='btn-lg btn-warning' >Search Item</button>
        </form>
      </header>

      <Router>
        <People path='/people/'>
          <Person path=':personId' fetchStarwarsData />
        </People>
        <Planets path="/planets/">
          <Planet path=':planetId' fetchStarwarsData />
        </Planets>
        <Starships path='/starships/'>
          <Starship path=':starshipId' fetchStarwarsData />
        </Starships>
      </Router>

    </div>
  )
})

const config = {
  categories: {
    people: 'people',
    planets: 'planets',
    starships: 'starships'
  }
}
  

People.js

 // People.js
import React from 'react'
import { useQuery } from "react-query";
import { StarwarsContext, StarwarsProvider } from "../App"

export const People = props => {
  return (
    <div>
      <span> the People have spoken</span>
      {props.children}
    </div>
  )
}

export const Person = () => {

  const { category, theID, fetchStarwarsData } = React.useContext(StarwarsContext)

  const { data, isLoading, error } = useQuery([category, theID], fetchStarwarsData)

  if (isLoading) return <div>loading...</div>
  if (error) return <div>oop!! error ocurred</div>

  return (
    <div>
      <h1>/{category}/{theID}</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>

    </div>
  )
}
  

Planets.js

 // Planets.js
import React from 'react'
import { useQuery } from "react-query";
import { StarwarsContext, StarwarsProvider } from "../App"

export const Planets = props => {
  return (
    <div>
      <span> the Planets have spoken</span>
      {props.children}
    </div>
  )
}


export const Planet = props => {

  const { category, theID, fetchStarwarsData } = React.useContext(StarwarsContext)

  const { data, isLoading, error } = useQuery([category, theID], fetchStarwarsData)

  if (isLoading) return <div>loading...</div>
  if (error) return <div>oop!! error ocurred</div>

  return (
    <div>
      <h1>/{category}/{theID}</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>

    </div>
  )
}
  

Starships.js

 // Starships.js
import React from 'react'
import { useQuery } from "react-query";
import { StarwarsContext, StarwarsProvider } from "../App"

export const Starships = props => {
  return (
    <div>
      <span> the Starships have spoken</span>
      {props.children}
    </div>
  )
}

export const Starship = () => {

  const { category, theID, fetchStarwarsData } = React.useContext(StarwarsContext)

  const { data, isLoading, error } = useQuery([category, theID], fetchStarwarsData)

  if (isLoading) return <div>loading...</div>
  if (error) return <div>oop!! error ocurred</div>

  return (
    <div>
      <h1>/{category}/{theID}</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>

    </div>
  )
}
  

Комментарии:

1. Я попробовал это, но тогда как мне извлечь данные с сайта api, которого больше нет в этой версии

2. Я тоже работал над этим прошлой ночью, но столкнулся с некоторыми проблемами, поэтому решил сначала сосредоточиться на логике переключения. Позже я еще раз взгляну на код, чтобы подумать о том, как реализовать вызовы API с использованием этой архитектуры.

3. @ALTHEPAL78 Сначала посмотрите это видео об управлении состоянием: ссылка . Я взял тот же шаблон и использовал его, чтобы заставить мое решение работать с SWAPI. Полное решение опубликовано здесь и на GitHub ( ссылка ). Если это решение работает и для вас, пожалуйста, подумайте о том, чтобы пометить его как правильный ответ.

4. Это выглядит потрясающе, но теперь мне нужен класс во всех разных @ things, которые вы используете. Я пытаюсь предпринять к этому небольшие шаги. Это сбивает меня с толку, потому что я только видел, но не использовал, многие из тех импортных файлов, которые вы импортируете, поэтому я пытаюсь сначала разобраться с основами.

5. @ALTHEPAL78 Это действительно отличный отзыв, и я полностью понимаю, к чему вы клоните. Позвольте мне посмотреть, смогу ли я разбить тот же код, используя меньше сторонних библиотек, чтобы его было легче понять.