Last active
May 17, 2025 13:21
-
-
Save sunmeat/8012b9bc740e5d551d4caca6041fb8e5 to your computer and use it in GitHub Desktop.
interaction between a parent form and child input components react
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
src / forms / FormContainer.jsx: | |
import {useState} from 'react'; | |
import './css/FormContainer.css'; | |
// дочерняя компонента получает данные и функции от родителя через пропсы | |
// в идеале, естественно, нужно разместить родителя и дочерние компоненты в отдельных файлах! | |
function FormInput({label, type, name, value, onChange}) { | |
const handleInputChange = (e) => { | |
onChange(e.target.value); // вызов коллбэк-функции для обновления состояния родительской формы | |
}; | |
return ( | |
<div className="form-group"> | |
<label htmlFor={name}>{label}:</label> | |
<input | |
id={name} | |
type={type} | |
name={name} | |
value={value} | |
onChange={handleInputChange} | |
/> | |
</div> | |
); | |
} | |
// родительская компонента (сама форма) | |
function FormContainer() { | |
const [formData, setFormData] = useState({ | |
name: '', | |
email: '' | |
}); | |
const [submitResult, setSubmitResult] = useState(null); | |
// коллбэк, который обновляет состояние formData в родительском компоненте | |
// принимает два параметра: field (имя поля, например, name или email) и value (новое значение поля) | |
// используя setFormData, функция изменяет только указанное поле в объекте formData, сохраняя остальные поля неизменными | |
const handleChange = (field, value) => { | |
setFormData((prev) => ({ | |
...prev, | |
[field]: value | |
})); | |
}; | |
const handleSubmit = async (e) => { | |
e.preventDefault(); // отмена стандартного поведения формы (в виде перезагрузки страницы например) | |
setSubmitResult(null); | |
try { | |
const response = await fetch('https://jsonplaceholder.typicode.com/users', { | |
method: 'POST', | |
headers: {'Content-Type': 'application/json'}, | |
body: JSON.stringify(formData) | |
}); | |
const data = await response.json(); | |
console.log('ответ от API:', data); | |
setSubmitResult({success: true, data}); | |
} catch (error) { | |
console.error('ошибка API:', error); | |
setSubmitResult({success: false, error: error.message}); | |
} | |
}; | |
return ( | |
<div className="form-container"> | |
<h2>регистрационная форма</h2> | |
<form onSubmit={handleSubmit}> | |
<FormInput | |
label="имя" | |
type="text" | |
name="name" | |
value={formData.name} | |
onChange={(value) => handleChange('name', value)} | |
/> | |
<FormInput | |
label="e-mail" | |
type="email" | |
name="email" | |
value={formData.email} | |
onChange={(value) => handleChange('email', value)} | |
/> | |
<button type="submit"> | |
отправить | |
</button> | |
</form> | |
<div className="form-data"> | |
<h3>введённые данные:</h3> | |
<p>имя: {formData.name || '—'}</p> | |
<p>email: {formData.email || '—'}</p> | |
</div> | |
{submitResult && ( | |
<div className="result"> | |
{submitResult.success ? ( | |
<> | |
<p className="success">успешно отправлено!</p> | |
<pre>{JSON.stringify(submitResult.data, null, 2)}</pre> | |
</> | |
) : ( | |
<p className="error"> | |
ошибка: {submitResult.error || 'не удалось отправить'} | |
</p> | |
)} | |
</div> | |
)} | |
</div> | |
); | |
} | |
export default FormContainer; | |
================================================================================================================ | |
src / forms / css / FormContainer.css: | |
.form-container { | |
max-width: 520px; | |
margin: 3rem auto; | |
padding: 2.5rem; | |
background: linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(240, 245, 255, 0.7)); | |
backdrop-filter: blur(10px); | |
border-radius: 16px; | |
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); | |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
border: 1px solid rgba(255, 255, 255, 0.2); | |
animation: fadeIn 0.5s ease-out; | |
} | |
.form-container h2 { | |
margin-bottom: 2rem; | |
font-size: 1.75rem; | |
font-weight: 600; | |
color: #1a1a1a; | |
text-align: center; | |
animation: slideIn 0.6s ease-out; | |
} | |
form { | |
display: flex; | |
flex-direction: column; | |
gap: 1.8rem; | |
} | |
.form-group { | |
display: flex; | |
flex-direction: column; | |
gap: 0.6rem; | |
position: relative; | |
} | |
.form-group label { | |
font-size: 1rem; | |
font-weight: 500; | |
color: #2c3e50; | |
transition: all 0.3s ease; | |
} | |
.form-group input, | |
.form-group select { | |
padding: 0.9rem; | |
font-size: 1rem; | |
border: none; | |
border-radius: 8px; | |
background: rgba(255, 255, 255, 0.8); | |
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.05); | |
transition: all 0.3s ease; | |
} | |
.form-group input:focus, | |
.form-group select:focus { | |
outline: none; | |
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.3), 0 4px 8px rgba(0, 0, 0, 0.1); | |
background: #fff; | |
transform: translateY(-2px); | |
} | |
.form-group input.error, | |
.form-group select.error { | |
box-shadow: 0 0 0 3px rgba(231, 76, 60, 0.3); | |
} | |
.form-group .error-message { | |
color: #e74c3c; | |
font-size: 0.85rem; | |
position: absolute; | |
bottom: -1.6rem; | |
animation: shake 0.3s ease; | |
} | |
button { | |
padding: 0.9rem; | |
font-size: 1rem; | |
font-weight: 600; | |
color: #fff; | |
background: linear-gradient(90deg, #3498db, #2980b9); | |
border: none; | |
border-radius: 8px; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
box-shadow: 0 4px 12px rgba(52, 152, 219, 0.3); | |
} | |
button:hover:not(:disabled) { | |
background: linear-gradient(90deg, #2980b9, #1f618d); | |
transform: translateY(-2px); | |
box-shadow: 0 6px 16px rgba(52, 152, 219, 0.4); | |
} | |
button:active:not(:disabled) { | |
transform: translateY(0); | |
box-shadow: 0 2px 8px rgba(52, 152, 219, 0.2); | |
} | |
button:disabled { | |
background: #bdc3c7; | |
cursor: not-allowed; | |
box-shadow: none; | |
} | |
.form-data { | |
margin-top: 2.5rem; | |
padding: 1.5rem; | |
background: linear-gradient(135deg, #ecf0f1, #dfe6e9); | |
border-radius: 12px; | |
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | |
animation: fadeIn 0.6s ease-out; | |
} | |
.form-data h3 { | |
margin-bottom: 1.2rem; | |
font-size: 1.3rem; | |
color: #2c3e50; | |
} | |
.form-data p { | |
margin: 0.6rem 0; | |
font-size: 1rem; | |
color: #34495e; | |
transition: all 0.3s ease; | |
} | |
.result { | |
margin-top: 2rem; | |
padding: 1.5rem; | |
border-radius: 12px; | |
background: rgba(255, 255, 255, 0.95); | |
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | |
animation: fadeIn 0.6s ease-out; | |
} | |
.result .success { | |
color: #2ecc71; | |
font-weight: 600; | |
display: flex; | |
align-items: center; | |
gap: 0.5rem; | |
} | |
.result .error { | |
color: #e74c3c; | |
font-weight: 600; | |
display: flex; | |
align-items: center; | |
gap: 0.5rem; | |
} | |
.result pre { | |
background: #2d3436; | |
color: #dfe6e9; | |
padding: 1.2rem; | |
border-radius: 8px; | |
font-size: 0.9rem; | |
line-height: 1.5; | |
overflow-x: auto; | |
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2); | |
} | |
@keyframes fadeIn { | |
from { | |
opacity: 0; | |
transform: translateY(10px); | |
} | |
to { | |
opacity: 1; | |
transform: translateY(0); | |
} | |
} | |
@keyframes slideIn { | |
from { | |
opacity: 0; | |
transform: translateY(-20px); | |
} | |
to { | |
opacity: 1; | |
transform: translateY(0); | |
} | |
} | |
@keyframes shake { | |
0%, 100% { | |
transform: translateX(0); | |
} | |
20%, 60% { | |
transform: translateX(-5px); | |
} | |
40%, 80% { | |
transform: translateX(5px); | |
} | |
} | |
@media (max-width: 480px) { | |
.form-container { | |
margin: 1.5rem; | |
padding: 1.5rem; | |
} | |
form { | |
gap: 1.2rem; | |
} | |
.form-group input, | |
.form-group select, | |
button { | |
font-size: 0.9rem; | |
padding: 0.7rem; | |
} | |
.form-container h2 { | |
font-size: 1.5rem; | |
} | |
.form-data, | |
.result { | |
padding: 1rem; | |
} | |
} | |
================================================================================================================ | |
src / App.jsx: | |
import React from 'react'; | |
import './App.css'; | |
import FormContainer from "./forms/FormContainer.jsx"; | |
export function App() { | |
return <FormContainer />; | |
} | |
export default App; | |
================================================================================================================ | |
================================================================================================================ | |
вариант с useContext(): https://gist.github.com/sunmeat/1426f6782180966fc21621360156e9ac |
if8fe98
commented
May 17, 2025
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment