У меня есть коды ниже
code removed
Проблема в том, что он печатает строки расходов, а затем строки доходов (т. е. сначала печатаются сообщения о расходах, а затем о доходах). Мне нужно, чтобы они были смешаны и распечатаны соответственно по дате и времени (самый новый пост идет вверху, а самый старый внизу)
Подождите, так дублируются ли данные между ExpenseRow
и IncomeRow
или нужно отображать все данные? Есть ли между ними что-то общее, например title
?
Чтобы свести к минимуму изменения вашего кода, я бы предложил добавить еще одно состояние под названием 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;
Я не запускал этот код локально, но попытался уловить некоторые идеи по реализации. Если у вас есть какие-либо проблемы с кодом, вы можете сказать мне здесь. @Бекр
Я пытался запустить этот код, но получаю много ошибок
Не могли бы вы сделать скриншот, какую ошибку вы получаете? Я могу помочь вам решить вашу проблему @Bekr
Думаю, мне не хватило одной скобки в map
. Не могли бы вы попробовать еще раз с обновленным кодом? @Бекр
хорошо, секундочку
Я только что открыл песочницу, чтобы проверить ошибки и соответствующим образом обновил код, надеюсь, теперь он будет работать на вас: D @Bekr
На этот раз у меня не было ошибок, но он ничего не печатает, я не уверен, что не так.
Не могли бы вы добавить console.info({ sortedRow })
перед этой строкой this.setState({ rows: sortedRows })
и проверить, что у вас получилось? @Бекр
и проверьте, что у вас есть в this.state.expenses
и this.state.incomes
тоже. Они потенциально пусты @Bekr
Это странно, потому что я определил это здесь const sortedRows = [...updatedExpenses, ...updatedIncomes].sort((a,b) => b.created_at - a.created_at)
. Куда ты добавил это console.info
?
Прямо перед этой строкой — this.setState({ rows: sortedRows })
Как насчет значений для updatedExpenses
и updatedIncomes
? Они тоже пустые? @Бекр
Я также получаю эту ошибку «Ошибка: превышена максимальная глубина обновления. Это может произойти, когда компонент неоднократно вызывает setState внутри componentWillUpdate или componentDidUpdate. React ограничивает количество вложенных обновлений, чтобы предотвратить бесконечные циклы».
Я только что обновил это условие, prevState.incomes !== this.state.incomes
. Не могли бы вы обновить и свой? @Бекр
К счастью, мы зашли так далеко, я думаю, что последняя проблема sort
. Вы можете изменить сортировку примерно так sort((a,b) => new Date(b.created_at) - new Date(a.created_at))
@Bekr
Фух, наконец-то мы это сделали! давно у меня не было хорошего парного программирования. Я рад, что могу помочь :D @Bekr
Спасибо, Ник, не могли бы вы подсказать, как мне добавить функцию фильтра, например, добавить поле даты и выбрать месяц, а затем распечатать результат: D?
Хм, это будет еще одна огромная реализация... Но я могу дать вам краткую идею. Вы можете добавить эту логику в свою функцию фильтра [...rows].filter(row => new Date (row.created_at) + 1 === monthValue)
и обновить ее до состояния rows
. monthValue
будет из вашего события раскрывающегося списка onChange
, а значения для этого раскрывающегося списка — от 1 до 12. @Bekr
Если вы не беспокоитесь о рендеринге повторяющихся данных, это должно сработать для вас. Я просто объединил два состояния, отсортировал их по дате и передал это вашему новому объединенному компоненту 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 ой! Я забыл поставить this.
рядом с sortByDate
. Добавьте это и посмотрите, как это работает.
Как это работает сейчас?
Я предлагаю добавить помощника 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;
// ...
}
}
Вы хотите объединить
ExpenseRow
иIncomeRow
или оставить их отдельно? Вроде бы есть объединяемые.