Skip to content

Instantly share code, notes, and snippets.

@sunmeat
Last active May 17, 2025 13:21
Show Gist options
  • Save sunmeat/8012b9bc740e5d551d4caca6041fb8e5 to your computer and use it in GitHub Desktop.
Save sunmeat/8012b9bc740e5d551d4caca6041fb8e5 to your computer and use it in GitHub Desktop.
interaction between a parent form and child input components react
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
Copy link

if8fe98 commented May 17, 2025


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment