React.js. Использование хуков. Часть 2 из 3

11.08.2021

Теги: FrontendHookJavaScriptReact.jsWeb-разработкаТеорияФункция

Хук контекста useContext

const value = useContext(MyContext);

Принимает объект контекста (значение, возвращённое из React.createContext) и возвращает текущее значение контекста для этого контекста. Текущее значение контекста определяется пропом value ближайшего <MyContext.Provider> над вызывающим компонентом в дереве.

Когда ближайший <MyContext.Provider> над компонентом обновляется, этот хук вызовет повторный рендер с последним значением контекста. Даже если родительский компонент использует React.memo или реализует shouldComponentUpdate, то повторный рендер будет выполняться, начиная c компонента, использующего useContext.

import React, {useContext} from 'react';

// Контекст позволяет передавать значение глубоко в дерево компонентов без передачи пропсов
// на каждом уровне. Создадим контекст для текущей темы со значением «light» по умолчанию.
const ThemeContext = React.createContext('light');

export default class App extends React.Component {
    render() {
        return (
            <ThemeContext.Provider value="dark">
                <Toolbar />
            </ThemeContext.Provider>
        );
    }
}

class Toolbar extends React.Component {
    render() {
        return <Button />
    }
}

function Button() {
    const theme = useContext(ThemeContext);
    return <button className={theme}>Button</button>
}
.dark {
    background-color: #333;
    color: #fff;
    border: 2px solid #333;
}
.light {
    background-color: #eee;
    color: #333;
    border: 2px solid #eee;
}

Давайте рассмотрим пример посложнее, чтобы лучше понять использование хука. У нас будет два контекста — CatalogContext и BasketContext. Первый будет хранить товары каталога, а второй отвечать за корзину покупателя. Мы обернем верхний элемент <App/> компонентами <CatalogContextProvider> и <BasketContextProvider> — так что все потомки будут иметь доступ к двум контекстам. Доступ возможен либо через <CatalogContext.Consumer> и <BasketContext.Consumer>, либо через useContext(CatalogContext) и useContext(BasketContext).

[src]
    App.js
    [components]
        CatalogContext.js
        BasketContext.js
        Content.js
        Catalog.js
        Basket.js
import React from 'react';
import Content from './components/Content';
import {CatalogContextProvider} from './components/CatalogContext';
import {BasketContextProvider} from './components/BasketContext';

export default function App() {
    return (
        <CatalogContextProvider>
            <BasketContextProvider>
                <Content />
            </BasketContextProvider>
        </CatalogContextProvider>
    );
}
import React from 'react';

export const CatalogContext = React.createContext();

const products = [
    {id: 123, title: 'JavaScript', price: 567.00},
    {id: 456, title: 'React.js', price: 678.00},
    {id: 789, title: 'Node.js', price: 789.00},
];

export function CatalogContextProvider(props) {
    return (
        <CatalogContext.Provider value={products}>
            {props.children}
        </CatalogContext.Provider>
    )
}
import React, {useState} from 'react';

export const BasketContext = React.createContext();

export function BasketContextProvider(props) {
    const [products, setProducts] = useState([]);

    const add = (item) => {
        if (!products.includes(item)) {
            setProducts([item, ...products]);
        }
    };

    const remove = (item) => {
        setProducts(products.filter(product => product !== item));
    };

    const cost = () => {
        return products.reduce((cost, product) => cost + product.price, 0);
    };

    const clear = () => {
        setProducts([]);
    }

    const context = {
        products: products,
        add: add,
        remove: remove,
        cost: cost,
        clear: clear,
    };

    return (
        <BasketContext.Provider value={context}>
            {props.children}
        </BasketContext.Provider>
    )
}
import React from 'react';
import Basket from './Basket';
import Catalog from './Catalog';

export default function Content() {
    return (
        <div>
            <Basket />
            <Catalog />
        </div>
    );
}
import {useContext} from 'react';
import {CatalogContext} from './CatalogContext';
import {BasketContext} from './BasketContext';

export default function Catalog() {
    const products = useContext(CatalogContext); // товары каталога
    const basket = useContext(BasketContext); // корзина покупателя
    return (
        <div>
            <h1>Каталог</h1>
            <table border="1" cellSpacing="0" cellPadding="5">
                <tr>
                    <th>Код</th>
                    <th>Наименование</th>
                    <th>Цена</th>
                    <th>В корзину</th>
                </tr>
                {products.map(product => (
                    <tr key={product.id}>
                        <td>{product.id}</td>
                        <td>{product.title}</td>
                        <td>{product.price}</td>
                        <td>
                            <button onClick={() => basket.add(product)}>В корзину</button>
                        </td>
                    </tr>
                ))}
            </table>
        </div>
    );
}
import {useContext} from 'react';
import {BasketContext} from './BasketContext';

export default function Basket() {
    const basket = useContext(BasketContext);
    return (
        <div>
            <h3>Корзина</h3>
            <table border="1" cellSpacing="0" cellPadding="5">
                <tr>
                    <th>Код</th>
                    <th>Наименование</th>
                    <th>Цена</th>
                    <th>Удалить</th>
                </tr>
                {basket.products.map(product => (
                    <tr key={product.id}>
                        <td>{product.id}</td>
                        <td>{product.title}</td>
                        <td>{product.price}</td>
                        <td>
                            <button onClick={() => basket.remove(product)}>Удалить</button>
                        </td>
                    </tr>
                ))}
                <tr>
                    <td colSpan="2">Сумма</td>
                    <td>{basket.cost()}</td>
                    <td>
                        <button onClick={() => basket.clear()}>Очистить</button>
                    </td>
                </tr>
            </table>
        </div>
    );
}

И вот что у нас получилось в итоге. Есть каталог товаров и есть корзина — товары можно как добавлять в корзину, так и удалять из нее. Причем доступ к товарам каталога и к корзине через контекст есть у любого компонента приложения, который находится ниже <App/>.

Хук эффекта useLayoutEffect

Хук useEffect() в функциональных компонентах выполняет ту же роль, что и componentDidMount(), componentDidUpdate() и componentWillUnmount() в классовых компонентах. Но тут важно понимать, что есть существенное отличие в работе этой функции и методов жизненного цикла.

Например, если внутри componentDidMount() мы вызываем метод setState(), это приводит к повторному рендеру. Но пользователь не увидит промежуточное состояние между рендерами — изменения будут отправлены в браузерный DOM только после второго рендера. С другой стороны, если в useEffect() при монтировании компонента изменить состояние, то изменения в браузерный DOM будут отправлены дважды — после каждого рендера.

Такое поведение useEffect() иногда может вызывать неприятный эффект мерцания для пользователей приложения. Исправить это можно, если использовать хук useLayoutEffect() вместо useEffect() — тогда промежуточное состояние пользователь не увидит. Изменения будут отправлены в браузерный DOM только один раз, после второго рендера.

Разработчики React рекомендуют использовать useLayoutEffect() только в случае острой необходимости, чтобы вдруг не возникло проблем с правильным рендерингом компонентов.

Давайте рассмотрим небольшой пример, где реализуем классовый и функциональный комоненты, которые просто рисуют на экране квадрат и изменяют его цвет. Чтобы было более наглядно, добавим вызов функции sleep(), который добавит задержку в componentDidMount() и в функцию, которую мы передаем в useEffect().

import React from "react";

const sleep = (duration) => {
    const start = new Date().getTime();
    let end = start;
    while(end < start + duration) {
        end = new Date().getTime();
    }
}

class Component extends React.Component {
    state = {
        color: "green"
    };

    componentDidMount() {
        sleep(3000);
        if (this.state.color === "green") {
            this.setState({color: "red"});
        }
    }

    render() {
        console.log('Рендер классового компонента, цвет', this.state.color);
        return (
            <div style={{backgroundColor: this.state.color, width: "100px", height: "100px"}}></div>
        );
    }
}

export default Component;
Рендер классового компонента, цвет green
Рендер классового компонента, цвет red

При запуске приложения мы увидим, как браузер зависает на три секунды, а потом показывает красный квадрат. Хотя рендеров было два, зеленого квадрата мы не видим, изменения в браузерный DOM были отправлены только один раз — после второго рендера.

import React, {useState, useEffect, useLayoutEffect} from "react";

const sleep = (duration) => {
    const start = new Date().getTime();
    let end = start;
    while(end < start + duration) {
        end = new Date().getTime();
    }
}

const Component = () => {
    const [color, setColor] = useState("green");

    useEffect(() => {
        sleep(3000);
        if (color === "green") {
            setColor("red");
        }
    }, []); // только при монтировании

    console.log('Рендер функционального компонента, цвет', color);
    return (
        <div style={{backgroundColor: color, width: "100px", height: "100px"}}></div>
    );
};

export default Component;
Рендер функционального компонента, цвет green
Рендер функционального компонента, цвет red

При запуске приложения мы увидим, как браузер показывает зеленый квадрат, а через три секунды — красный. Рендеров было два, изменения в браузерный DOM были отправлены дважды — после каждого рендера. Чтобы поведение стало таким же, как и у классового компонента, надо заменить useEffect() на useLayoutEffect().

Хук мемоизации useCallback

Пусть у нас есть компонент Parent и дочерний компонент Child, комонент Child получает от родителя проп name. У компонента Parent есть кнопка, которая изменяет его состояние. Клик по кнопке вызывает рендер компонента Parent — а заодно и компонента Child.

import {useState} from 'react';

const Parent = (props) => {
    const [rand, setRand] = useState(0);

    const handleParentClick = () => {
        console.log('Изменение состояния Parent');
        setRand(Math.random());
    };

    console.log('Рендер компонента Parent');
    return (
        <div>
            <h1>Родитель</h1>
            <Child name="Ребенок" />
            <button onClick={handleParentClick}>Изменить состояние Parent</button>
        </div>
    );
};
const Child = (props) => {
    console.log('Рендер компонента Child');
    return <h2>{props.name}</h2>
};

После клика по кнопке мы увидим в консоли следующие сообщения:

Изменение состояния Parent
Рендер компонента Parent
Рендер компонента Child

Поскольку мы передаем компоненту Child всегда одно и тот же значение пропа name, нет необходимости в его повторном рендере. Этого можно добиться, используя React.memo():

import React from 'react';

const Child = React.memo((props) => {
    console.log('Рендер компонента Child');
    return <h2>{props.name}</h2>
});

После клика по кнопке мы увидим в консоли следующие сообщения:

Изменение состояния Parent
Рендер компонента Parent

Мы обернули компонент в вызов React.memo(), чтобы мемоизировать результат и избежать повторного рендера при тех же пропсах.

React.memo() затрагивает только изменения пропсов. Если функциональный компонент обёрнут в React.memo() и использует useState, useReducer или useContext, он будет повторно рендериться при изменении состояния или контекста.

Это прекрасно работает, пока мы не передадим компоненту Child через пропсы callback-функцию:

import {useState} from 'react';

const Parent = (props) => {
    const [rand, setRand] = useState(0);

    const handleParentClick = () => {
        console.log('Изменение состояния Parent');
        setRand(Math.random());
    };

    const handleChildClick = () => {
        console.log('Обработчик клика Child');
    };

    console.log('Рендер компонента Parent');
    return (
        <div>
            <h1>Родитель</h1>
            <Child name="Ребенок" childClickHandler={handleChildClick} />
            <button onClick={handleParentClick}>Изменить состояние Parent</button>
        </div>
    );
};
import React from 'react';

const Child = React.memo((props) => {
    console.log('Рендер компонента Child');
    return <h2 onClick={props.childClickHandler}>{props.name}</h2>
});

После клика по кнопке мы увидим в консоли следующие сообщения:

Изменение состояния Parent
Рендер компонента Parent
Рендер компонента Child

Что происходит в этом случае? Рендер компонента Parent — это вызов функции Parent. Каждый новый вызов функции выполняет все тело функции, создавая новую ссылку handleParentClick, которую мы передаем Child как childClickHandler. А React.memo() сравнивает старую и новую ссылки на функцию. И видит, что они разные — значит, нужен новые рендер компонента Child.

const one = () => {};
const two = () => {};
console.log(one === two); // false

Ну вот, мы и добрались до самого главного. Если обернуть callback-функцию вызовом useCallback() — то useCallback() всегда будет возвращать ссылку на одну и ту же функцию (пока не изменятся зависимости, это второй аргумент хука, подробности здесь).

import React, {useState, useCallback} from 'react';
import Child from './Child';

const Parent = (props) => {
    const [rand, setRand] = useState(0);

    const handleParentClick = () => {
        console.log('Изменение состояния Parent');
        setRand(Math.random());
    };

    const handleChildClick = useCallback(() => {
        console.log('Обработчик клика Child')
    }, []);

    console.log('Рендер компонента Parent');
    return (
        <div>
            <h1>Родитель</h1>
            <Child name="Ребенок" childClickHandler={handleChildClick} />
            <button onClick={handleParentClick}>Изменить состояние Parent</button>
        </div>
    );
};

После клика по кнопке мы увидим в консоли следующие сообщения:

Изменение состояния Parent
Рендер компонента Parent

Хук мемоизации useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

Хук useMemo() возвращает мемоизированное значение. Принимает в качестве аргуменов «создающую» функцию и массив зависимостей. Позволяет улучшить производительность, когда при каждом ренедере нужно выполнять повторяющиеся сложные вычисления. Если массив зависимостей не был передан, новое значение будет вычисляться при каждом рендере.

Вызов useCallback(fn, deps) — это эквивалент вызова useMemo(() => fn, deps).

Второе назначение хука — использование его по аналогии с хуком useCallback, но для массивов и объектов. Массивы и объекты имеют ссылочный тип, как и функции — и если их передавать дочернему компоненту как пропсы — будут лишние рендеры.

Без использования хука useMemo:

const Parent = (props) => {
    const [rand, setRand] = useState(0);

    const changeState = () => {
        console.log('Изменение состояния Parent');
        setRand(Math.random());
    };

    const obj = {one: 1, two: 2};
    const arr = ['one', 'two'];

    console.log('Рендер компонента Parent');
    return (
        <div>
            <h1>Родитель</h1>
            <Child name="Ребенок" obj={obj} arr={arr} />
            <button onClick={changeState}>Изменить состояние Parent</button>
        </div>
    );
};
const Child = React.memo((props) => {
    console.log('Рендер компонента Child');
    return <h2>{props.name}</h2>
});
Изменение состояния Parent
Рендер компонента Parent
Рендер компонента Child

Теперь будем использовать хук:

const Parent = (props) => {
    const [rand, setRand] = useState(0);

    const changeState = () => {
        console.log('Изменение состояния Parent');
        setRand(Math.random());
    };

    const obj = useMemo(() => ({one: 1, two: 2}), []);
    const arr = useMemo(() => ['one', 'two'], []);

    console.log('Рендер компонента Parent');
    return (
        <div>
            <h1>Родитель</h1>
            <Child name="Ребенок" obj={obj} arr={arr} />
            <button onClick={changeState}>Изменить состояние Parent</button>
        </div>
    );
};
Изменение состояния Parent
Рендер компонента Parent

Хук useImperativeHandle

Надо сразу сказать, что без этого хука можно обойтись. Того, что мы уже знаем о хуках, достаточно, чтобы создать тот функционал, который предоставляет useImperativeHandle(). Вообще не очень понятно, зачем он нужен — но разработчики React иногда так делают. Например, это можно видеть на примере useCallback() и useMemo().

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

Рассмотрим простой пример получения ссылки на DOM-элемент, когда мы в компоненте Form создаем объект mailInputRefer со свойством current, передаем его через проп reference дочернему компоненту Input, а дочерний компонент передает его элементу <input> через специальный атрибут ref. В итоге компонент Form получит ссылку на элемент <input> и сможет установить фокус, задать стиль и т.п.

import {useRef, useState} from 'react';

const Form = () => {
    const [mail, setMail] = useState('');
    const mailInputRefer = useRef();

    const handleChange = (event) => {
        setMail(event.target.value);
    }

    const handleSubmit = (event) => {
        /*
         * проверяем, что поле mail заполнено
         */
        if (mail.trim() === "") {
            event.preventDefault(); // отменяем отправку формы
            mailInputRefer.current.style.backgroundColor = "#fdd"; // красный фон
            mailInputRefer.current.focus(); // устанавливаем фокус для ввода mail
        } else {
            mailInputRefer.current.style.backgroundColor = "";
        }
    }

    return (
        <form onSubmit={handleSubmit}>
            <Input
                name="mail"
                value={mail}
                changeHandler={handleChange}
                reference={mailInputRefer}
                placeholder="Адрес почты"
            />
            <input type="submit" value="Отправить" />
        </form>
    );
};
const Input = (props) => (
    <input
        type="text"
        name={props.name}
        value={props.value}
        onChange={props.changeHandler}
        ref={props.reference}
        placeholder={props.placeholder}
    />
);

Так что же предлагает хук useImperativeHandle? А предлагает он усложнить этот простой и понятный код, чтобы разработчику жизнь мёдом не казалась.

import {useRef, useState} from 'react';

const Form = () => {
    const [mail, setMail] = useState('');
    const mailInputRefer = useRef();

    const handleChange = (event) => {
        setMail(event.target.value);
    }

    const handleSubmit = (event) => {
        /*
         * проверяем, что поле mail заполнено
         */
        if (mail.trim() === "") {
            event.preventDefault(); // отменяем отправку формы
            mailInputRefer.current.style.backgroundColor = "#fdd"; // красный фон
            mailInputRefer.current.focus(); // устанавливаем фокус для ввода mail
        } else {
            mailInputRefer.current.style.backgroundColor = "";
        }
    }

    return (
        <form onSubmit={handleSubmit}>
            <Input
                name="mail"
                value={mail}
                changeHandler={handleChange}
                ref={mailInputRefer}
                placeholder="Адрес почты"
            />
            <input type="submit" value="Отправить" />
        </form>
    );
};
import {useRef, forwardRef, useImperativeHandle} from 'react';

const Input = forwardRef((props, refer) => {
    const textInputRefer = useRef();
    useImperativeHandle(refer, () => ({
        style: textInputRefer.current.style,
        focus: () => textInputRefer.current.focus()
    }));
    return (
        <input
            type="text"
            name={props.name}
            value={props.value}
            onChange={props.changeHandler}
            ref={textInputRefer}
            placeholder={props.placeholder}
        />
    );
});

В компоненте Input мы получаем ссылку на элемент <input>, а потом в useImperativeHandle определяем, что должно быть в свойстве current объекта mailInputRefer, который мы создали в компоненте Form. Поскольку нам нужно изменять стиль и устанавливать фокус, именно эти свойства HTMLInputElement туда и записываем.

{
    current: {
        style: CSSStyleDeclaration {...},
        focus: () => textInputRefer.current.focus()
    }
}

Но в принципе, нам было бы достаточно записать туда только свойство input — которое было бы ссылкой на <input> элемент. И через это свойство получить style, focus — да все, что потребуется. Пожалуй, это будет более универсальное решение — мало ли, что может потребоваться.

useImperativeHandle(refer, () => ({
    input: textInputRefer
}));
{
    current: {
        input: ссылка_на_input_элемент
    }
}

Но в этом случае нам вообще не нужен хук useImperativeHandle. Можно просто передать refer через специальный атрибут ref. Потому как хук нужен, чтобы записать в mailInputRefer только часть свойств textInputRefer. А если мы хотим сохранить в mailInputRefer DOM-элемент <input> целиком — все упрощается.

import {forwardRef} from 'react';

const Input = forwardRef((props, refer) => {
    return (
        <input
            type="text"
            name={props.name}
            value={props.value}
            onChange={props.changeHandler}
            ref={refer}
            placeholder={props.placeholder}
        />
    );
});

Хук useImperativeHandle используется в паре с forwardRef и никак иначе. Ссылку на объект mailInputRefer мы передаем из Form в Input через специальный атрибут ref. В компоненте Input мы создаем переменную textInputRefer, которую надо передать в input через специальный атрибут ref. У компонента Input, обернутого в forwardRef, будет второй аргумент refer. И этот refer нужно передать useImperativeHandle первым параметром. Главное — ничего не перепутать и ничего не забыть.

Хук состояния useReducer

Хук состояния useReducer очень похож на useState, но дает больший контроль над управлением состояния. Он принимает функцию редюсер и начальное состояние в качестве аргументов, а возвращает состояние и метод dispatch.

const [state, dispatch] = React.useReducer(reducerFn, initialState, initFn);

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

const reducer = (prevState, action) => {
    /* .......... */
    return newState;
}

Действие представляет собой строку, которая описывает — что произойдет. И основываясь на этой информации, редюсер определяет — как должно измениться состояние. Действия передаются через функцию dispatch(action).

Лучше использовать useReducer вместо useState, когда следующее состояние зависит от предыдущего. Это даст предсказуемость в изменении состояния — вся логика сосредоточена в одной функции и эта функция не зависит от React.

const reducer = (state, action) => {
    switch (action) {
        case 'INCREMENT':
            return state + 1;
        case 'DECREMENT':
            return state - 1;
        default:
            return state;
    }
}

function Counter() {
    const [state, dispatch] = React.useReducer(reducer, 0);
    return (
        <div>
            <button onClick={() => dispatch('DECREMENT')}>minus</button>
            <strong>{state}</strong>
            <button onClick={() => dispatch('INCREMENT')}>plus</button>
        </div>
    );
}

Редюсер — «чистая» функция, это значит, что у нее нет побочных эффектов. Она возвращает одно и то же значение, если задать одни и те же аргументы. Такую функцию намного проще тестировать — нет побочных эффектов и она не зависит от React.

Еще один пример использования хука — компонент Color позволяет задать цвет заголовка с помощью шести кнопок, каждая из которых увеличивает или уменьшает значение одной из RGB-составляющей цвета.

import React from 'react';

const limitRGB = (number) => (number < 0 ? 0 : number > 255 ? 255 : number);
const step = 50;

const reducer = (state, action) => {
    // обычно action — объект с ключами type и payload; type — действие, которое нужно
    // выполнить, а payload — дополнительные данные, которые необходимы для вычисления
    switch (action.type) {
        case 'INCREMENT_RED': 
            return {
                ...state,
                red: limitRGB(state.red + step)
            }
        case 'DECREMENT_RED':
            return {
                ...state,
                red: limitRGB(state.red - step)
            }
        case 'INCREMENT_GREEN': 
            return {
                ...state,
                green: limitRGB(state.green + step)
            }
        case 'DECREMENT_GREEN':
            return {
                ...state,
                green: limitRGB(state.green - step)
            }
            case 'INCREMENT_BLUE': 
            return {
                ...state,
                blue: limitRGB(state.blue + step)
            }
        case 'DECREMENT_BLUE':
            return {
                ...state,
                blue: limitRGB(state.blue - step)
            }
        default:
            return state;
    }
}

export default function Color() {
    const [state, dispatch] = React.useReducer(reducer, {red: 0, green: 0, blue: 0});

    return (
        <React.Fragment>
            <h1 style={{color: `rgb(${state.red}, ${state.green}, ${state.blue})`}}>
                Выбор цвета
            </h1>
            <p>
                Красный: {state.red}
                <button onClick={() => dispatch({type: 'INCREMENT_RED'})}>плюс</button>
                <button onClick={() => dispatch({type: 'DECREMENT_RED'})}>минус</button>
            </p>
            <p>
                Зеленый: {state.green}
                <button onClick={() => dispatch({type: 'INCREMENT_GREEN'})}>плюс</button>
                <button onClick={() => dispatch({type: 'DECREMENT_GREEN'})}>минус</button>
            </p>
            <p>
                Синий: {state.blue}
                <button onClick={() => dispatch({type: 'INCREMENT_BLUE'})}>плюс</button>
                <button onClick={() => dispatch({type: 'DECREMENT_BLUE'})}>минус</button>
            </p>
        </React.Fragment>
    );
}

Поиск: Hook • JavaScript • React.js • Web-разработка • Frontend • Теория • Функция • Хук • useContext • useCallback • useMemo

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.