Не отрабатывает первый рендер
Есть страница с пиццами и панель с сортировками пицц
Центральная сортировка пока не нужна, только левая и правая. Сортировки работают отлично, но есть одно но. Вторая сортировка (популярность, цена...) работает только после того, как активируется первая сортировка. Помогите с решением данной проблемы. Код:
function App() {
const data = [
{
title: "Пепперонни",
image: "/images/pizzaPeperoni.png",
price: 120,
modalContent: "20 грамм картошки",
isMeat: 1,
popularity: 36,
},
{
title: "Салями",
image: "/images/pizzaSalami.png",
price: 99,
modalContent: "40 грамм мандавошки",
isMeat: 1,
popularity: 24,
},
{
title: "Гавайская",
image: "/images/pizzaGavaiskia.png",
price: 81,
modalContent: "15 мандаринок",
isMeat: 1,
popularity: 18,
},
{
title: "Грибная",
image: "/images/pizzaGribnaia.png",
price: 144,
modalContent: "90 ведер воды",
isMeat: 0,
popularity: 22,
},
{
title: "4 сезона",
image: "/images/pizzaFourSesons.png",
price: 120,
modalContent: "150 домов",
isMeat: 0,
popularity: 12,
},
{
title: "Терияки",
image: "/images/pizzaTeriyaki.png",
price: 72,
modalContent: "3 царства",
isMeat: 1,
popularity: 25,
}
]
const [isRender, setIsRender] = useState(data)
const [fillerValue, setFillerValue] = useState(2)
const [value, setValue] = useState('')
const filterByTitle = data.filter(item => {
return item.title.toLowerCase().includes(value.toLowerCase())
})
let filterByFiller = (fillerValue) => {
return fillerValue === 2 ? data : data.filter((item) => {
return item.isMeat === fillerValue
})
}
function sortByField(field) {
return field === 'popularity' ? filterByFiller(fillerValue).sort((a, b) => a[field] < b[field] ? 1 : -1) :
filterByFiller(fillerValue).sort((a, b) => a[field] < b[field] ? -1 : 1)
}
function cardsRender(state) {
return state.map(({...item}, index) =>
<Card
key={index}
{...item}
/>
)
}
return (
<div className="App">
<PizzaBackground />
<Cart />
<div className="wrapper">
<Header
setValue= {id => setValue(id)}
filterByTitle={filterByTitle}
setIsRender={setIsRender}
/>
<div className="sorting">
<ol id="left">
<li padding-right="300px" onClick={() => {
setIsRender(filterByFiller(1))
setFillerValue(1)
}}>Мясные</li>
<li padding-right="400px" onClick={() => {
setIsRender(filterByFiller(0))
setFillerValue(0)
}}>Вегетарианские</li>
<li padding-right="200px" onClick={() => {
setIsRender(filterByFiller(2))
setFillerValue(2)
}}>Все</li>
</ol>
<ol id="center">
<li>От <input maxLength={11} name="number" onChange={(e) => console.log(e.target.value)} /> руб.</li>
<li>До <input maxLength={11} name="number" onChange={(e) => console.log(e.target.value)} /> руб.</li>
</ol>
<ol id="right">
<p>Сортировать по:</p>
<li><button onClick={() => {
setIsRender(sortByField('popularity'))
console.log(isRender)
}}>Популярности</button></li>
<li><button onClick={() => {
setIsRender(sortByField('price'))
console.log(isRender)
}}>Цене (по возрастанию)</button></li>
<li><button onClick={() => {
setIsRender(sortByField('title'))
console.log(isRender)
}}>Алфавиту</button></li>
</ol>
</div>
<div className="menu">
{cardsRender(isRender)}
</div>
</div>
</div>
);
}
export default App;
В консоли при клике на правую сортировку правильно выдаёт всё, проблема только в отрисовке на странице
Ответы (1 шт):
React - это библиотека/фреймворк javaScript.
В javaScript объекты при копировании передают ссылки на себя, а не сами данные.
Массив это тоже объект.
Т.е. один объект существует в javaScript в одном экземпляре, все переменные ссылаются на него.
const arr = [1, 2];
const arrCopy = arr;
arrCopy.push(2);
console.log(arr === arrCopy);
console.log(arr, arrCopy);
Как видно из примера, 2 переменных равны друг другу, хотя визуально мы изменили вторую переменную.
Скажите: Так ведь данные одни и те же! Поэтому равно. Проверим
const arrOne = [1,2];
const arrTwo = [1,2];
console.log(arrOne === arrTwo);
Данные одинаковы, но массивы разные, т.к. создано 2 разных объекта.
У массивов есть методы, которые возвращают новый массив, например filter() или map().
Метод sort не возвращает новый объект, а возвращает ссылку на исходный объект, но отсортированный.
Самый простой способ создать новый объект, это оператор spread( ... ).
Spread не делает полный клон объекта, а только копирует первый уровень, однако для React этого вполне достаточно, что бы понять, что данные изменились и надо обновить состояние компонента.
const mas = [1, 2, 5, 3, 8, 0, 2];
const mas1 = mas.sort();
const mas2 = mas.filter(item => item < 6);
console.log(mas === mas1);
console.log(mas === mas2);
console.log(mas === [ ...mas1]);
console.log(mas, [ ...mas1 ]);
Теперь посмотрим на вашу функцию, обладая знаниями выше
let filterByFiller = (fillerValue) => {
return fillerValue === 2 ? data : data.filter((item) => {
return item.isMeat === fillerValue
})
}
При значении fillerValue === 2 функция вернет data, где data это ссылка на объект.
const [isRender, setIsRender] = useState(data) - здесь isRender имеет ту же ссылку, т.е. ссылаются они на один и тот же объект.
При изменении состояния компонента setIsRender(sortByField('price')) передаем ту же ссылку на объект, который был до этого. React думает, что данные не изменились и рендер не нужен.
Для того, что бы React понял, что данные изменились, необходимо передать новый объект.
Таким образом, выполняя изменения состояния, с использованием spread оператора setIsRender([ ...sortByField('price') ]) приводит к обновлению react компонента.
Почему это работает при первоначальном клике на другие сортировки? Потому что вы возвращали результат метода filter(), который возвращает новый объект.