Реагировать на объединение двух компонентов и сортировку данных по дате и времени (самые новые вверху, самые старые и внизу)

У меня есть коды ниже

code removed

Проблема в том, что он печатает строки расходов, а затем строки доходов (т. е. сначала печатаются сообщения о расходах, а затем о доходах). Мне нужно, чтобы они были смешаны и распечатаны соответственно по дате и времени (самый новый пост идет вверху, а самый старый внизу)

Вы хотите объединить ExpenseRow и IncomeRow или оставить их отдельно? Вроде бы есть объединяемые.

Nick Vu 06.05.2022 17:54

Подождите, так дублируются ли данные между ExpenseRow и IncomeRow или нужно отображать все данные? Есть ли между ними что-то общее, например title?

AttemptedMastery 06.05.2022 18:00
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
2
64
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Чтобы свести к минимуму изменения вашего кода, я бы предложил добавить еще одно состояние под названием row и использовать componentDidUpdate для прослушивания изменений состояний expenses и incomes для обновления состояния row.

import axios from 'axios';
import React, { Component } from 'react'
import ExpenseRow from './ExpenseRow';
import IncomeRow from './IncomeRow';

class IncomeExpenseSummary extends Component {

    constructor(props) {
        super(props);
        this.state = {
            expenses:[],
            incomes:[],
            rows: []
        }
    }
   
    componentDidUpdate(prevProps, prevState) {
       //trigger when incomes or expenses change
       if (prevState.incomes !== this.state.incomes || prevState.expenses !== this.state.expenses) {
          //add type to recognize `income` in mixed list
          const updatedIncomes = [...this.state.incomes].map(income => ({...income, type: 'income'})) 
          //add type to recognize `expense` in mixed list
          const updatedExpenses = [...this.state.expenses].map(expense => ({...expense, type: 'expense'})) 
          //sort all rows with the mixed list
          const sortedRows = [...updatedExpenses, ...updatedIncomes].sort((a,b) => new Date(b.created_at) - new Date(a.created_at)) 
          this.setState({ rows: sortedRows })
       }
    }

    componentDidMount(){
        this.getExpenseList();
        this.getIncomeList();
    }

    getExpenseList = () =>{
        //if you use an arrow function, you don't need to pass variable for `this`
        axios.get('/get/expenses',{
            params:{_limit: 3}
        }).then((response) => {
            this.setState({
                expenses: response.data
            });
        });
    }

    getIncomeList =() =>{
        //if you use an arrow function, you don't need to pass variable for `this`
        axios.get('/get/incomes',{
            params:{_limit: 3}
        }).then((response) => {
            this.setState({
                incomes: response.data
            });
        });
    }

    render(){
        return(
            <div>
                <table className = "table"> 
                {/* <toast/> */}
                <thead>
                    <tr>
                        <th scope = "col">Date</th>
                        <th scope = "col">Name</th>
                        <th scope = "col">Price</th>
                        <th scope = "col">Category</th>
                    </tr>
                </thead>
      <tbody>
            {this.state.rows.map((row) => row.type === 'expense' ? <ExpenseRow data = {row} key = {row.title}/> : <IncomeRow data = {row} key = {row.title}/>)}
      </tbody>
            </table>
            </div>
    );
    }
  }


export default IncomeExpenseSummary;

Я не запускал этот код локально, но попытался уловить некоторые идеи по реализации. Если у вас есть какие-либо проблемы с кодом, вы можете сказать мне здесь. @Бекр

Nick Vu 06.05.2022 18:14

Я пытался запустить этот код, но получаю много ошибок

Bekr 06.05.2022 18:17

Не могли бы вы сделать скриншот, какую ошибку вы получаете? Я могу помочь вам решить вашу проблему @Bekr

Nick Vu 06.05.2022 18:20

Думаю, мне не хватило одной скобки в map. Не могли бы вы попробовать еще раз с обновленным кодом? @Бекр

Nick Vu 06.05.2022 18:21

хорошо, секундочку

Bekr 06.05.2022 18:23

Я только что открыл песочницу, чтобы проверить ошибки и соответствующим образом обновил код, надеюсь, теперь он будет работать на вас: D @Bekr

Nick Vu 06.05.2022 18:33

На этот раз у меня не было ошибок, но он ничего не печатает, я не уверен, что не так.

Bekr 06.05.2022 18:36

Не могли бы вы добавить console.info({ sortedRow }) перед этой строкой this.setState({ rows: sortedRows }) и проверить, что у вас получилось? @Бекр

Nick Vu 06.05.2022 18:37

и проверьте, что у вас есть в this.state.expenses и this.state.incomes тоже. Они потенциально пусты @Bekr

Nick Vu 06.05.2022 18:39

Это странно, потому что я определил это здесь const sortedRows = [...updatedExpenses, ...updatedIncomes].sort((a,b) => b.created_at - a.created_at). Куда ты добавил это console.info?

Nick Vu 06.05.2022 18:40

Прямо перед этой строкой — this.setState({ rows: sortedRows })

Bekr 06.05.2022 18:42

Как насчет значений для updatedExpenses и updatedIncomes? Они тоже пустые? @Бекр

Nick Vu 06.05.2022 18:42

Я также получаю эту ошибку «Ошибка: превышена максимальная глубина обновления. Это может произойти, когда компонент неоднократно вызывает setState внутри componentWillUpdate или componentDidUpdate. React ограничивает количество вложенных обновлений, чтобы предотвратить бесконечные циклы».

Bekr 06.05.2022 18:44

Я только что обновил это условие, prevState.incomes !== this.state.incomes. Не могли бы вы обновить и свой? @Бекр

Nick Vu 06.05.2022 18:44

К счастью, мы зашли так далеко, я думаю, что последняя проблема sort. Вы можете изменить сортировку примерно так sort((a,b) => new Date(b.created_at) - new Date(a.created_at)) @Bekr

Nick Vu 06.05.2022 18:51

Фух, наконец-то мы это сделали! давно у меня не было хорошего парного программирования. Я рад, что могу помочь :D @Bekr

Nick Vu 06.05.2022 18:56

Спасибо, Ник, не могли бы вы подсказать, как мне добавить функцию фильтра, например, добавить поле даты и выбрать месяц, а затем распечатать результат: D?

Bekr 06.05.2022 18:59

Хм, это будет еще одна огромная реализация... Но я могу дать вам краткую идею. Вы можете добавить эту логику в свою функцию фильтра [...rows].filter(row => new Date (row.created_at) + 1 === monthValue) и обновить ее до состояния rows. monthValue будет из вашего события раскрывающегося списка onChange, а значения для этого раскрывающегося списка — от 1 до 12. @Bekr

Nick Vu 06.05.2022 19:14

Если вы не беспокоитесь о рендеринге повторяющихся данных, это должно сработать для вас. Я просто объединил два состояния, отсортировал их по дате и передал это вашему новому объединенному компоненту ExpenseIncomeRow. Если вы на самом деле пытаетесь объединить данные на основе свойств key/value, то это решение может вам не подойти.

import axios from 'axios';
import React, { Component } from 'react'
import ExpenseIncomeRow from './ExpenseIncomeRow';

class IncomeExpenseSummary extends Component {

    constructor(props) {
        super(props);
        this.state = {
            expenses:[],
            incomes:[],
        }
    }

    componentDidMount(){
        this.getExpenseList();
        this.getIncomeList();
    }

    sortByDate = (data) => {
        data.sort((a, b) => new Date(b['created_at']) - new Date(a['created_at']))
    }

    getExpenseList =() =>{
        let self=this;
        axios.get('/get/expenses',{
            params:{_limit: 3}
        }).then(function(response)
            {self.setState({
                expenses: response.data
            });
        });
    }

    getIncomeList =() =>{
        let self=this;
        axios.get('/get/incomes',{
            params:{_limit: 3}
        }).then(function(response)
            {self.setState({
                incomes: response.data
            });
        });
    }


    render(){
        return(
            <div>
                <table className = "table"> 
                {/* <toast/> */}
                <thead>
                    <tr>
                        <th scope = "col">Date</th>
                        <th scope = "col">Name</th>
                        <th scope = "col">Price</th>
                        <th scope = "col">Category</th>
                    </tr>
                </thead>
                <tbody>
                {this.sortByDate(this.state.expenses.slice(0, 4).concat(this.state.incomes.slice(0, 4)).map(function(x, i){
                    return <ExpenseIncomeRow key = {i} data = {x} />
                }))}
            </tbody>
            </table>
            </div>
        );
    }

}

export default IncomeExpenseSummary;

Вот новый компонент:

import React, { Component } from 'react'


class ExpenseIncomeRow extends Component {
    render(){
        return (
            <tr className='alert alert-success'>
                <th>{this.props.data.created_at}</th>
                <td>{this.props.data.title}</td>
                <td>{this.props.data.amount}</td>
                <td>{this.props.data.category}</td>
            </tr>
        )
    }
}
export default ExpenseIncomeRow;

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

Хм, я попытался запустить этот код, он ничего не печатает, я проверил консоль, и он говорит Uncaught ReferenceError: sortByDate не определен

Bekr 06.05.2022 18:26

@Bekr ой! Я забыл поставить this. рядом с sortByDate. Добавьте это и посмотрите, как это работает.

AttemptedMastery 06.05.2022 18:28

Как это работает сейчас?

AttemptedMastery 06.05.2022 18:46

Я предлагаю добавить помощника zip():

// Zip array arguments together.
//
//     zip([1, 2, 3], [4, 5, 6])
//     //=> [[1, 4], [2, 5], [3, 6]]
//     zip([1], [2, 3], [])
//     //=> [[1, 2, undefined], [undefined, 3, undefined]]
//
function zip(...arrays) {
  if (!arrays.length) return [];
  const length = Math.max(...arrays.map(array => array.length));
  return Array.from({ length }, (_, i) => arrays.map(array => array[i]));
}

Если вы уже используете библиотеку со вспомогательными функциями (Underscore.js, Lodash, Ramda и т. д.), велика вероятность, что через нее доступна эта функция.

Затем вы можете использовать его в своем JSX следующим образом:

// moved taking the first 3 elements up, to simplify the JSX
const take = (n, array) => array.slice(0, n + 1);
const expenses = take(3, this.state.expenses);
const incomes = take(3, this.state.incomes);

// ...

<tbody>
  {zip(expenses, incomes).map(([expense, income], i) => (
    <>
      <ExpenseRow key = {i} data = {expense} />
      <IncomeRow key = {i} data = {income} />
    </>
  ))}
</tbody>

Это решение предполагает, что HTTP-запрос возвращает элементы в правильном порядке. Если это не так, вам нужно сначала отсортировать оба массива.


Если длина expenses и incomes может быть разной. Скажем, expenses имеет длину 1, а incomes имеет длину 3. Тогда вы, вероятно, захотите добавить дополнительную проверку как для expenses, так и для incomes (в примере показано только expenses).

{expense && <ExpenseRow key = {i} data = {expense} />}

Или вы можете вернуть null в ExpenseRow, если данные не предоставлены.

class ExpenseRow extends Component {
    render() {
        if (!this.props.data) return null;
        // ...
    }
}

Другие вопросы по теме