Рефакторинг задачи из класса в функцию с крючками - PullRequest
0 голосов
/ 26 мая 2019

Я рефакторинг некоторого кода, и теперь я застрял.Оригинальный код от туто: https://github.com/EricLondon/rails5-react-reactstrap-crud-example Все хорошо там.Я пытаюсь использовать новую функцию хуков.Новый код, который я написал, просто не работает.

«Оригинальный код» в порядке, «новый код» не работает

//"New code"
import React, { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import { Container, Row, Col, Alert } from 'reactstrap'
import PostsTable from '../PostsTable'

const Api = require('../../api/Api.js')

export default function Posts() {
  const [posts, setPosts] = useState([])
  const [isLoaded, setIsLoaded] = useState(false)
  const [error, setError] = useState(null)

  useEffect( () => {
    Api.getPosts()
      .then( response => {
        const [errata, data] = response
        if (errata) {
          setIsLoaded(true)
          setPosts([])
          setError(data)
        } else {
          setIsLoaded(true)
          setPosts(data)
        }
      })
  })

  if (error) {

    return (
      <Alert color="danger">
          Error: {error}
      </Alert>
    )

  } else if (!isLoaded) {

    return (
      <Alert color="primary">
          Loading...
      </Alert>
    )

  } else {

    return (
      <Container>
        <Row>
          <Col>
            <PostsTable posts={posts}></PostsTable>
            <Link className="btn btn-primary" to="/posts/new">Add Post</Link>
          </Col>
        </Row>
      </Container>
    )

  }
}



//"Original code"
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import { Container, Row, Col, Alert } from 'reactstrap'
import PostsTable from '../PostsTable'

const Api = require('../../api/Api.js')

class Posts extends Component {
  constructor(props) {
    super(props)
    this.state = {
      posts: [],
      isLoaded: false,
      error: null
    }
  }

  componentDidMount() {
    Api.getPosts()
      .then(response => {
        const [error, data] = response
        if (error) {
          this.setState({
            isLoaded: true,
            posts: [],
            error: data
          })
        } else {
          this.setState({
            isLoaded: true,
            posts: data
          })
        }
      })
  }

  render() {
    const { error, isLoaded, posts } = this.state

    if (error) {

      return (
        <Alert color="danger">
          Error: {error}
        </Alert>
      )

    } else if (!isLoaded) {

      return (
        <Alert color="primary">
          Loading...
        </Alert>
      )

    } else {

      return (
        <Container>
          <Row>
            <Col>
              <PostsTable posts={posts}></PostsTable>
              <Link className="btn btn-primary" to="/posts/new">Add Post</Link>
            </Col>
          </Row>
        </Container>
      )

    }

  }
}

export default Posts

С «новым кодом» мой бэкэнд-сервер сводит с ума: ...Запустил GET "/ api / posts" для 127.0.0.1 в 2019-05-26 20:45:39 +0200 Обработка Api :: PostsController # index как HTML Post Load (1.3ms) ВЫБРАТЬ "posts". * FROM "posts "↳ app / controllers / api / posts_controller.rb: 5 Завершено 200 ОК за 6 мс (Просмотров: 4,4 мс | ActiveRecord: 1,3 мс) .... и т.д.

Ответы [ 5 ]

1 голос
/ 27 мая 2019

Я думаю, что проблема решена.Спасибо всем.Фактически мы столкнулись с двумя проблемами одновременно.1 / [] во втором аргументе было необходимо, 2 / затем, как сказал @olivier, в дочернем компоненте был какой-то плохой код: PostsTable."оригинальный код":

class PostsTable extends Component {
  constructor(props) {
    super(props)
    this.state = {
      posts: props.posts
    }
  }

  render() {
    const posts = this.state.posts
    if (posts.length === 0) {
      return <div></div> ......

Я сделал это: "Новый код":

class PostsTable extends Component {

  render() {
    const posts = this.props.posts
    if (posts.length === 0) {
      return <div></div> .....

Я просто отображаю PostsTable только с подпорками.PostsTable теперь не имеет состояния.Примечание: в оригинальном коде от Эрика Лондона я думаю, что объект PostsTable был создан из глобального набора состояний.Использование useEffect и setIsLoaded (true);setPosts (data) более сложен, поскольку, как только setIsLoaded (true) запускается, объект PostsTable создается и рендерится без вызова конструктора.

Я прекращаю рефакторинг следующим образом:

export default function PostsTable(props)  {
  const posts = props.posts
  if (posts.length === 0) {
    return <div></div> ....

thx@ оливье

0 голосов
/ 27 мая 2019

Извините за другие ответы, но пустой массив неверен. Вы можете прочитать эту 49-минутную статью Дэна Абрамова (создателя Redux и React Hooks) о том, почему это так.

https://overreacted.io/a-complete-guide-to-useeffect/

Я полагаю, что вы можете решить свою проблему, комбинируя все ваши хуки useState, например:

export default function Posts() {
  const [state, setState] = useState({posts: [],
                                      isLoaded: false,
                                      error: null })


  useEffect( () => {
    Api.getPosts()
      .then( response => {
        const [errata, data] = response
        if (errata) {
          setState({ ...state, 
                     isLoaded: true, 
                     posts: [],
                     error: data })
        } else {
          setState({ ...state, 
                     isLoaded: true, 
                     posts: [data],
                     error: null })
        }
      })
  }, [state.isLoaded] )

убедитесь, что вы переходите в предыдущее состояние с синтаксисом распространения, иначе ваше предыдущее состояние не будет сохранено.

Альтернативный способ использования условного выражения в useEffect и передача всего состояния в качестве зависимости для использованияEffect

export default function Posts() {
  const [state, setState] = useState({posts: [],
                                      isLoaded: false,
                                      error: null })


  useEffect( () => {
  if(!state.isLoaded) {
    Api.getPosts()
      .then( response => {
        const [errata, data] = response
        if (errata) {
          setState({ ...state, 
                     isLoaded: true, 
                     posts: [],
                     error: data })
        } else {
          setState({ ...state, 
                     isLoaded: true, 
                     posts: [data],
                     error: null })
        }
      })
    }
  }, [state] )
0 голосов
/ 26 мая 2019

По умолчанию функция, переданная для использования Effect, будет выполняться после каждого рендеринга компонента.

Когда вы обновляете переменную состояния внутри вашего эффекта, он вызовет повторную визуализацию вашего компонента, затем эффект будет выполнен в другой раз, в основном у вас бесконечный цикл.

Если вы передадите пустой массив в useEffect, эффект будет выполнен только один раз, поскольку он будет выполнен повторно только в случае изменения одного значения в массиве.

Следующий код должен решить проблему

useEffect( () => {
  Api.getPosts()
    .then( response => {
      const [errata, data] = response
      if (errata) {
        setIsLoaded(true)
        setPosts([])
        setError(data)
      } else {
        setIsLoaded(true)
        setPosts(data)
      }
  })
}, [])
0 голосов
/ 26 мая 2019
useEffect( () => {
    Api.getPosts()
      .then( response => {
        const [errata, data] = response
        if (errata) {
          setIsLoaded(true)
          setPosts([])
          setError(data)
          console.log('-+- showing data1 -+-')
          console.log(data)
        } else {
          setIsLoaded(true)
          setPosts(data)
          console.log('-+- showing data2 -+-')
          console.log(data)
        }
      })
  },[])

Это показывает '- + - показывает данные2 - + - с (5) [{…}, {…}, {…}, {…}, {…}] 0: {id: 14, заголовок: «qsdf», тело: «qsdf», созданный_ат: «2019-05- 26T13: 07: 58.006Z ", updated_at:" 2019-05-26T13: 07: 58.006Z "} 1: {id: 15, заголовок:" qsdf ", тело:" egg ", creat_at:" 2019-05-26T17 : 47: 48.323Z ", updated_at:" 2019-05-26T17: 47: 48.323Z "} 2: {id: 16, заголовок:" qdf ", тело:" qdf ", созданный_ат:" 2019-05-26T17: 48: 52.257Z ", updated_at:" 2019-05-26T17: 48: 52.257Z "} 3: {id: 17, заголовок:" qsdf ", тело:" h ", созданный_ат:" 2019-05-26T19: 49 : 41.108Z ", updated_at:" 2019-05-26T19: 49: 41.108Z "} 4: {id: 18, заголовок:" qsdf ", тело:" 123 ", creat_at:" 2019-05-26T19: 59: 38.002Z ", updated_at:" 2019-05-26T19: 59: 38.002Z "} длина: 5__proto: Array (0)

0 голосов
/ 26 мая 2019

Прямо сейчас ваш `useEffect работает на каждом рендере. Вы должны добавить пустой массив в качестве второго аргумента, чтобы запускаться только при монтировании:

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

Предоставляя массив в качестве второго аргумента, вы говорите функции useEffect: «Запускайте снова каждый раз, когда изменяется одно из этих значений». Если вы вообще не предоставите массив, он будет запускаться каждый раз при рендеринге компонента (в этом случае каждый раз, когда вызывается setIsLoading, setError или setPosts).

Предоставление ему пустого массива зависимостей по-прежнему говорит функции «запускать каждый раз, когда изменяется одно из этих значений», но поскольку нет никаких значений, которые будут меняться, он будет запускаться только один раз.

...