Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save evgeniyworkbel/0e30cd388394f146fb8ab53a5b41203b to your computer and use it in GitHub Desktop.
Save evgeniyworkbel/0e30cd388394f146fb8ab53a5b41203b to your computer and use it in GitHub Desktop.
Курс "JS: React Hooks"
App.jsx
// @ts-check
import React, { useState } from 'react';
import { useImmer } from 'use-immer';
import getModal from './modals/index.js';
// BEGIN (write your solution here)
const renderTask = (item, setActiveModal, updateClickedTaskId, updateClickedTaskName) => {
const { taskName, id } = item;
return (
<React.Fragment key={id}>
<div>
<span className="mr-3">{taskName}</span>
<button
type="button"
className="border-0 btn btn-link mr-3 text-decoration-none"
data-testid="item-rename"
onClick={() => {
updateClickedTaskId(id);
updateClickedTaskName(taskName);
setActiveModal('renaming');
}}
>
rename
</button>
<button
type="button"
className="border-0 btn btn-link mr-3 text-decoration-none"
data-testid="item-remove"
onClick={() => {
updateClickedTaskId(id);
setActiveModal('removing');
}}
>
remove
</button>
</div>
</React.Fragment>
);
};
const renderModal = (activeModal, props) => {
if (!activeModal) {
return null;
}
const Component = getModal(activeModal);
return <Component props={props} />;
};
const handleClick = (modalType, setActiveModal) => (ev) => {
ev.preventDefault();
setActiveModal(modalType);
};
const App = () => {
const [activeModal, setActiveModal] = useState(null);
const [tasks, setTasks] = useImmer([]);
const [clickedTaskId, updateClickedTaskId] = useState(null);
const [clickedTaskName, updateClickedTaskName] = useState(null);
const onHide = () => setActiveModal(null);
return (
<>
<div className="mb-3">
<button
type="button"
data-testid="item-add"
className="btn btn-secondary"
onClick={handleClick('adding', setActiveModal)}
>
add
</button>
</div>
{tasks.length > 0
&& tasks.map((item) => renderTask(item, setActiveModal, updateClickedTaskId, updateClickedTaskName))}
{renderModal(activeModal, { setActiveModal, setTasks, onHide, clickedTaskId, clickedTaskName })}
</>
);
};
export default App;
// END
Add.jsx
import React, { useEffect, useRef } from 'react';
import _ from 'lodash';
import { useFormik } from 'formik';
import { Modal, FormGroup, FormControl } from 'react-bootstrap';
// BEGIN (write your solution here)
function Add(props) {
const { setActiveModal, setTasks, onHide } = props.props;
const inputEl = useRef(null);
const formik = useFormik({
initialValues: { body: '' },
onSubmit: (values) => {
const newTask = { taskName: values.body, id: _.uniqueId() };
setActiveModal(null);
setTasks((prevTasks) => [...prevTasks, newTask]);
},
});
useEffect(() => {
inputEl.current.focus();
}, []);
return (
<Modal show onHide={onHide}>
<Modal.Dialog>
<Modal.Header closeButton>
<Modal.Title>Add</Modal.Title>
</Modal.Header>
<Modal.Body>
<form onSubmit={formik.handleSubmit}>
<FormGroup className="form-group" controlId="body">
<FormControl
name="body"
type="text"
onChange={formik.handleChange}
value={formik.values.body}
ref={inputEl}
data-testid="input-body"
required
/>
</FormGroup>
<input className="btn btn-primary" type="submit" value="submit" />
</form>
</Modal.Body>
</Modal.Dialog>
</Modal>
);
}
export default Add;
// END
Remove.jsx
import React from 'react';
import { Modal, FormGroup } from 'react-bootstrap';
// BEGIN (write your solution here)
const handleSubmit = ({ setActiveModal, setTasks, clickedTaskId }) => (ev) => {
ev.preventDefault();
setActiveModal(null);
setTasks((items) => items.filter(({ id }) => id !== clickedTaskId));
};
function Remove(props) {
const { setActiveModal, setTasks, onHide, clickedTaskId } = props.props;
return (
<Modal show onHide={onHide}>
<Modal.Dialog>
<Modal.Header closeButton>
<Modal.Title>Remove</Modal.Title>
</Modal.Header>
<Modal.Body>
<form onSubmit={handleSubmit({ setActiveModal, setTasks, clickedTaskId })}>
<FormGroup className="form-group">
<input className="btn btn-danger" type="submit" value="remove" />
</FormGroup>
</form>
</Modal.Body>
</Modal.Dialog>
</Modal>
);
}
export default Remove;
// END
Rename.jsx
import React, { useEffect, useRef } from 'react';
import { useFormik } from 'formik';
import { Modal, FormGroup, FormControl } from 'react-bootstrap';
// BEGIN (write your solution here)
function Rename(props) {
const { setActiveModal, setTasks, onHide, clickedTaskId, clickedTaskName } = props.props;
const inputEl = useRef(null);
const formik = useFormik({
initialValues: { body: clickedTaskName },
onSubmit: (values) => {
setActiveModal(null);
setTasks((draft) => {
const index = draft.findIndex(({ id }) => id === clickedTaskId);
draft[index].taskName = values.body;
});
},
});
useEffect(() => {
inputEl.current.focus();
inputEl.current.select();
}, []);
return (
<Modal show onHide={onHide}>
<Modal.Dialog>
<Modal.Header closeButton>
<Modal.Title>Rename</Modal.Title>
</Modal.Header>
<Modal.Body>
<form onSubmit={formik.handleSubmit}>
<FormGroup className="form-group" controlId="body">
<FormControl
name="body"
type="text"
onChange={formik.handleChange}
value={formik.values.body}
ref={inputEl}
data-testid="input-body"
required
/>
</FormGroup>
<input className="btn btn-primary" type="submit" value="submit" />
</form>
</Modal.Body>
</Modal.Dialog>
</Modal>
);
}
export default Rename;
// END
App.jsx
// @ts-check
import React, { useState } from 'react';
import { useImmer } from 'use-immer';
import getModal from './modals/index.js';
// BEGIN
const renderItem = ({ item, showModal }) => (
<div key={item.id}>
<span className="mr-3">{item.body}</span>
<button type="button" className="border-0 btn btn-link mr-3 text-decoration-none" data-testid="item-rename" onClick={() => showModal('renaming', item)}>rename</button>
<button type="button" className="border-0 btn btn-link text-decoration-none" data-testid="item-remove" onClick={() => showModal('removing', item)}>remove</button>
</div>
);
const renderModal = ({ modalInfo, hideModal, setItems }) => {
if (!modalInfo.type) {
return null;
}
const Component = getModal(modalInfo.type);
return <Component modalInfo={modalInfo} setItems={setItems} onHide={hideModal} />;
};
const App = () => {
const [items, setItems] = useImmer([]);
const [modalInfo, setModalInfo] = useState({ type: null, item: null });
const hideModal = () => setModalInfo({ type: null, item: null });
const showModal = (type, item = null) => setModalInfo({ type, item });
return (
<>
<div className="mb-3">
<button type="button" onClick={() => showModal('adding')} data-testid="item-add" className="btn btn-secondary">add</button>
</div>
{items.map((item) => renderItem({ item, showModal }))}
{renderModal({ modalInfo, hideModal, setItems })}
</>
);
};
export default App;
// END
Add.jsx
import React, { useEffect, useRef } from 'react';
import _ from 'lodash';
import { useFormik } from 'formik';
import { Modal, FormGroup, FormControl } from 'react-bootstrap';
// BEGIN
const generateOnSubmit = ({ setItems, onHide }) => (values) => {
const item = { id: _.uniqueId(), body: values.body };
setItems((items) => {
items.push(item);
});
onHide();
};
const Add = (props) => {
const { onHide } = props;
const f = useFormik({ onSubmit: generateOnSubmit(props), initialValues: { body: '' } });
const inputRef = useRef();
useEffect(() => {
inputRef.current.focus();
}, []);
return (
<Modal show>
<Modal.Header closeButton onHide={onHide}>
<Modal.Title>Add</Modal.Title>
</Modal.Header>
<Modal.Body>
<form onSubmit={f.handleSubmit}>
<FormGroup>
<FormControl
required
ref={inputRef}
onChange={f.handleChange}
onBlur={f.handleBlur}
value={f.values.body}
data-testid="input-body"
name="body"
/>
</FormGroup>
<input type="submit" className="btn btn-primary mt-2" value="submit" />
</form>
</Modal.Body>
</Modal>
);
};
export default Add;
// END
Remove.jsx
import React from 'react';
import { Modal, FormGroup } from 'react-bootstrap';
// BEGIN
const generateOnSubmit = ({ modalInfo, setItems, onHide }) => (e) => {
e.preventDefault();
setItems((items) => items.filter((i) => i.id !== modalInfo.item.id));
onHide();
};
const Remove = (props) => {
const { onHide } = props;
const onSubmit = generateOnSubmit(props);
return (
<Modal show>
<Modal.Header closeButton onHide={onHide}>
<Modal.Title>Remove</Modal.Title>
</Modal.Header>
<Modal.Body>
<form onSubmit={onSubmit}>
<FormGroup>
<input type="submit" className="btn btn-danger mt-2" value="remove" />
</FormGroup>
</form>
</Modal.Body>
</Modal>
);
};
export default Remove;
// END
Rename.jsx
import React, { useEffect, useRef } from 'react';
import { useFormik } from 'formik';
import { Modal, FormGroup, FormControl } from 'react-bootstrap';
// BEGIN
const generateOnSubmit = ({ modalInfo, setItems, onHide }) => (values) => {
setItems((items) => {
const item = items.find((i) => i.id === modalInfo.item.id);
item.body = values.body;
});
onHide();
};
const Rename = (props) => {
const { onHide, modalInfo } = props;
const { item } = modalInfo;
const f = useFormik({ onSubmit: generateOnSubmit(props), initialValues: item });
const inputRef = useRef();
useEffect(() => {
inputRef.current.select();
}, []);
return (
<Modal show>
<Modal.Header closeButton onHide={onHide}>
<Modal.Title>Rename</Modal.Title>
</Modal.Header>
<Modal.Body>
<form onSubmit={f.handleSubmit}>
<FormGroup>
<FormControl
required
ref={inputRef}
onChange={f.handleChange}
onBlur={f.handleBlur}
value={f.values.body}
data-testid="input-body"
name="body"
/>
</FormGroup>
<input type="submit" className="btn btn-primary mt-2" value="submit" />
</form>
</Modal.Body>
</Modal>
);
};
export default Rename;
// END
App.jsx
Реализуйте и экспортируйте по умолчанию компонент, который реализует приложение "записная книжка".
В этом приложении можно добавлять, удалять и редактировать задачи с помощью модальных окон. На любое действие возникает модальное окно, внутри которого можно выполнять разные действия.
Для прохождения испытания нужно познакомиться с новыми хуками и использовать готовые компоненты бутстрапа. Рекомендуем проходить это испытание после выполнения предыдущего.
Примеры
Начальный HTML:
<div class="mb-3">
<button type="button" data-testid="item-add" class="btn btn-secondary">add</button>
</div>
После клика на добавление:
<div class="mb-3">
<button type="button" data-testid="item-add" class="btn btn-secondary">add</button>
</div>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<div class="modal-title h4">Add</div>
<button type="button" class="btn-close" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<input class="form-control" data-testid="input-body" name="body" required="" value="" />
</div>
<input class="btn btn-primary" type="submit" value="submit" />
</form>
</div>
</div>
</div>
После добавления первой задачи:
<div class="mb-3">
<button type="button" data-testid="item-add" class="btn btn-secondary">add</button>
</div>
<div>
<span class="mr-3">first task!</span>
<button type="button" class="border-0 btn btn-link mr-3 text-decoration-none" data-testid="item-rename">rename</button>
<button type="button" class="border-0 btn btn-link text-decoration-none" data-testid="item-remove">remove</button>
</div>
Клик по переименованию:
<div class="mb-3">
<button type="button" data-testid="item-add" class="btn btn-secondary">add</button>
</div>
<div>
<span class="mr-3">first task!</span>
<button type="button" class="border-0 btn btn-link mr-3 text-decoration-none" data-testid="item-rename">rename</button>
<button type="button" class="border-0 btn btn-link text-decoration-none" data-testid="item-remove">remove</button>
</div>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<div class="modal-title h4">Rename</div>
<button type="button" class="btn-close" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<input class="form-control" data-testid="input-body" name="body" required="" value="first task!" />
</div>
<input class="btn btn-primary" type="submit" value="submit" />
</form>
</div>
</div>
</div>
После переименования все возвращается к предыдущему HTML, но с новым именем.
Клик по удалению:
<div class="mb-3">
<button type="button" data-testid="item-add" class="btn btn-secondary">add</button>
</div>
<div>
<span class="mr-3">changed name!</span>
<button type="button" class="border-0 btn btn-link mr-3 text-decoration-none" data-testid="item-rename">rename</button>
<button type="button" class="border-0 btn btn-link text-decoration-none" data-testid="item-remove">remove</button>
</div>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<div class="modal-title h4">Remove</div>
<button type="button" class="btn-close" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<input class="btn btn-danger" type="submit" value="remove" />
</div>
</form>
</div>
</div>
</div>
После удаления, запись пропадает.
modals/Add.jsx
Реализуйте модальное окно для добавления задачи. Сделайте так, чтобы при появлении окна происходила фокусировка на поле для ввода. Это важно с точки зрения удобства.
modals/Rename.jsx
Реализуйте модальное окно для обновления названия задачи. Сделайте так, чтобы при появлении окна происходила фокусировка на поле для ввода и при этом выделялся весь текст. Это важно с точки зрения удобства.
modals/Remove.jsx
Реализуйте модальное окно для удаления задачи
Все модальные окна должны закрываться после отправки формы
Подсказки
Документация на встроенные хуки: useState, useEffect, useRef https://ru.reactjs.org/docs/hooks-state.html | https://ru.reactjs.org/docs/hooks-effect.html | https://ru.reactjs.org/docs/hooks-reference.html#useref
Документация на сторонние хуки: useImmer, useFormik https://github.com/immerjs/use-immer | https://formik.org/docs/api/useFormik
Документация React Bootstrap https://react-bootstrap.github.io/
useImmer() используется для точечного обновления в мутабельном стиле, при этом сохраняется преимущество иммутабельных структур данных. Его удобно использовать при необходимости внести изменения в сложные структуры данных, например, когда вам нужно найти объект из списка и изменить у него одно поле. useState() подходит для управления простым состоянием.
Вёрстка может немного отличаться
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment