React не рендирит изменения компонента при изменении props
задача такая -> с сервера подтягиваются данные автомобиля отображаются в таблице, при нажатии кнопки появляется модальное окно и автозаполняется форма. Данные автомобилей загружаются и хранятся в useState в виде обьекта.
const [shopCars, setShopCars] = useState(false);
if (data['success']){
if (data.cars.length > 0)
setShopCars(data.cars)
Далее этот массив передается компоненту в качестве пропса
<AdminTableCarsPage setShopCars={setShopCars} shopData={shopData} shopCars={shopCars} mainData={props.mainData} />
В этом компоненте автомобили отображаются в таблице. Также в данном компоненте имеется состояние
const [focusCarID, setFocusCarID] = useState(0);
и еще один компонент (модальное окно) в котором при изменении состояния focusCarID должна авто-заполняться форма
При нажатии на кнопку изменить данные состояние обновляется
<div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
<Link className="dropdown-item" data-bs-toggle="modal" data-bs-target="#EditCarShop" onClick={(event) => setFocusCarID(i)}>Изменить данные</Link>
</div>
после того как состояние обновилась оно передается компоненту № 2 (модальной форме) вместе с массивом всех автомобилей
<AdminModalEditCar focusCarID={focusCarID} setShopCars={props.setShopCars} shopCars={props.shopCars} sp_cars={props.mainData.sp_cars} id_shop={props.shopData.id} />
в компоненте № 2 (модальном окне) получаю данные автомобиля следующим способом
props.shopCars[props.focusCarID]
Проблема в следующем ->
- Компонент модального окна получает все пропсы но не обновляет форму при изменении props.focusCarID из за этого форма не автозаполняется. Плюс focusCarID обновляется с задержкой в один шаг, например, я выбираю первый автомобиль его данные модальное окно не видит, потом я выбираю второй автомобиль модальное окно видит данные первого, потом я выбираю третий автомобиль модальное окно видит данные второго и тд. Если ктото понял что не так помогите пожалуйста.
Код компонента где отрисовывается таблица с данными
import React, {useEffect, useState} from 'react';
import {Link} from "react-router-dom";
import AdminModalEditCar from "./AdminModalEditCar";
const AdminTableCarsPage = (props) => {
const [focusCarID, setFocusCarID] = useState(false);
return (
<div>
<table className="table table-hover">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Марка</th>
<th scope="col">Модель</th>
<th scope="col">Дата выпуска</th>
<th scope="col">Vin номер</th>
<th scope="col">Гос. номер</th>
<th scope="col">Состояния</th>
<th scope="col">Цена авто</th>
<th scope="col">Цена всех запчастей</th>
<th scope="col">Дата покупки</th>
<th scope="col">Действия</th>
</tr>
</thead>
<tbody>
{
Object.keys(props.shopCars).map((index, i) => {
return <tr key={i+1}>
<th scope="row">{i+1}</th>
<td>{props.shopCars[index].marka['name']}</td>
<td>{props.shopCars[index].model}</td>
<td>{props.shopCars[index].date_create}</td>
<td>{props.shopCars[index].vinnomer}</td>
<td>{props.shopCars[index].gosnomer}</td>
<td>{props.shopCars[index].status}</td>
<td>{props.shopCars[index].price} руб.</td>
<td>{props.shopCars[index].summ_all_parts} руб.</td>
<td>{props.shopCars[index].date_add}</td>
<td>
<div className="dropdown">
<button className="btn btn-дшпре dropdown-toggle" type="button"
id="dropdownMenuButton" data-bs-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
Действия
</button>
<div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
<Link className="dropdown-item" data-bs-toggle="modal" data-bs-target="#EditCarShop" onClick={(event) => setFocusCarID(props.shopCars[index].id)}>Изменить данные</Link>
<Link className="dropdown-item" data-bs-toggle="modal" data-bs-target={"#viewPartsCar_" + i}>Запчасти</Link>
<Link className="dropdown-item" data-bs-toggle="modal" data-bs-target={"#deleteCarShop_" + i}>Удалить</Link>
<Link className="dropdown-item" data-bs-toggle="modal" data-bs-target="#DeleteUserShop">Финансы</Link>
</div>
</div>
</td>
</tr>
})
}
</tbody>
</table>
<AdminModalEditCar setShopCars={props.setShopCars} editCarData={{}} focusCarID={focusCarID} sp_cars={props.mainData.sp_cars} id_shop={props.shopData.id} />
{/*<AdminModalCarPart sp_shops={props.mainData.sp_shops} sp_cars={props.mainData.sp_cars} sp_parts={props.mainData.sp_parts} carData={props.shopCars[index]}id_shop={props.shopData.id} />*/}
{/*<AdminModalDeleteCarShop setShopCars={props.setShopCars} id_shop={props.shopData.id} id_car={props.shopCars[index].id} />*/}
</div>
);
};
export default AdminTableCarsPage;
Код компонента модального окна в котором форма изменения данных
import React, {useEffect} from 'react';
import Modal from "../../../components/Modal/Modal";
import Select from "react-select"
import {useState} from "react";
import ImgAds from "../../../components/ImgFormPreview/ImgAds";
import sendForm from "../../../utility/SendForm";
import apiRequest from "../../../utility/ApiRequest";
import $ from 'jquery';
import {objectSize} from "../../../utility/Utility";
import LoadingIcon from "../../../components/LoadingIcon/LoadingIcon";
let indexLastImage = 0;
const AdminModalEditCar = (props) => {
const [displayImgAds, setdisplayImgAds] = useState([]);
const [sendAddNewCar, setSendAddNewCar] = useState(false);
const [dateshop, setDateShop] = useState("");
const [dateCreateCar, setDateCreateCar] = useState("");
const [adsAvtoMarka, setAdsAvtoMarka] = useState("");
const [issetImages, setIssetImages] = useState({});
const [defaultMarkaAvto, setDefaultMarkaAvto] = useState("");
const [editCarData, setEditCarData] = useState({});
const [loadDataCar, setloadDataCar] = useState(false);
useEffect(()=> {
console.log(props.focusCarID);
if (props.focusCarID){
setloadDataCar(false);
setEditCarData({});
apiRequest("getDataCar?", "id_shop=" + props.id_shop + "&id_car=" + props.focusCarID).then((data) => {
if(data['success']){
setloadDataCar(true);
if (data['dataCar'])
setEditCarData(data['dataCar']);
} else console.log(data['error']);
});
if (objectSize(editCarData)) {
setDateShop(editCarData.date_add);
setDateCreateCar(editCarData.date_create);
setAdsAvtoMarka(editCarData.marka['id']);
setIssetImages(editCarData.images);
setDefaultMarkaAvto(editCarData.marka['id']);
$('#editCarSelect option[value="' + editCarData.status + '"]').prop('selected', true);
}
}
}, [props.focusCarID]);
const addImgAds = (event) => {
let arrImg = [];
for(let i = 0; i < event.target.files.length; i++) {
indexLastImage++;
event.target.files[i]['rotate'] = 0;
arrImg.push({"file": event.target.files[i], "url" : URL.createObjectURL(event.target.files[i]), "index": indexLastImage});
}
setdisplayImgAds([...displayImgAds, ...arrImg]);
event.target.value = null;
}
const deleteImgAds = (idImg) => {
setdisplayImgAds(displayImgAds.filter(obj => {
return obj['index'] !== idImg;
}));
}
const deleteImageServer = (id_img, id_cars, id_shop) => {
apiRequest("deleteCarImg?", "id_shop=" + id_shop + "&id_cars=" + id_cars + "&id_img=" + id_img).then((data) => {
if (data['success']){
setIssetImages(issetImages.filter(i => i.indexOf(id_img) < 0));
} else
alert(data['error']);
});
}
const addNewCar = (event) => {
event.preventDefault();
if (!sendAddNewCar){
let files = [];
for (let i = 0; i < displayImgAds.length; i++)
files.push(displayImgAds[i]['file']);
setSendAddNewCar(true);
sendForm(event, (data) => {
if (data['success']){
if (data['cars'])
props.setShopCars(data['cars']);
}
setSendAddNewCar(false);
}, true, false, true, files);
}
}
return (
<Modal size="modal-xl" title="Изменение данных автомобиля" id="EditCarShop">
{
(loadDataCar)
? (objectSize(editCarData) === 0)
? <div className="alert alert-danger">Данные автомобиля не найдены.</div>
: <form onSubmit={(event) => addNewCar(event)} action="editCarShop" id={"formEditCarShop_" + props.id} method="POST"
className="needs-validation" noValidate>
<div className="form-group row">
<label htmlFor="inputPassword" className="col-sm-2 col-form-label">Марка</label>
<div className="col-sm-10">
<Select
options={props.sp_cars}
placeholder="Марка автомобиля"
isSearchable={true}
onChange={(event) => setAdsAvtoMarka(event.value)}
defaultValue = {
props.sp_cars.filter(option =>
option.value === parseInt(defaultMarkaAvto))
}
/>
<input name="marka" type="text" defaultValue={adsAvtoMarka} className="d-none" required />
<div className="valid-feedback">
<i className="bi bi-hand-thumbs-up-fill"></i> Заполнено
</div>
<div className="invalid-feedback">
Поле обязательно к заполнению
</div>
</div>
</div>
<input type="text" className="d-none" defaultValue={props.id_shop} name="id_shop" />
<input type="text" className="d-none" defaultValue={
(objectSize(props.editCarData))
? props.editCarData.id
: false
} name="id_car" />
<div className="form-group row mt-3">
<label htmlFor="inputPassword" className="col-sm-2 col-form-label">Модель</label>
<div className="col-sm-10">
<input type="text" className="form-control" name="model" defaultValue={
(objectSize(props.editCarData))
? props.editCarData.model
: false
} placeholder="Camry" required />
<div className="valid-feedback">
<i className="bi bi-hand-thumbs-up-fill"></i> Заполнено
</div>
<div className="invalid-feedback">
Поле обязательно к заполнению
</div>
</div>
</div>
<div className="form-group row mt-3">
<label htmlFor="inputPassword" className="col-sm-2 col-form-label">VIN номер</label>
<div className="col-sm-10">
<input type="text" className="form-control" name="vinnomer" placeholder="Z94CB41AACR123456" defaultValue={
(objectSize(props.editCarData))
? props.editCarData.vinnomer
: false
} required />
<div className="valid-feedback">
<i className="bi bi-hand-thumbs-up-fill"></i> Заполнено
</div>
<div className="invalid-feedback">
Поле обязательно к заполнению
</div>
</div>
</div>
<div className="form-group row mt-3">
<label htmlFor="inputPassword" className="col-sm-2 col-form-label">Гос. номер</label>
<div className="col-sm-10">
<input type="text" className="form-control" name="gosnomer" placeholder="А637АА93" defaultValue={
(objectSize(props.editCarData))
? props.editCarData.gosnomer
: false
} required />
<div className="valid-feedback">
<i className="bi bi-hand-thumbs-up-fill"></i> Заполнено
</div>
<div className="invalid-feedback">
Поле обязательно к заполнению
</div>
</div>
</div>
<div className="form-group row mt-3">
<label htmlFor="inputPassword" className="col-sm-2 col-form-label">Дата выпуска</label>
<div className="col-sm-auto">
<input type="date" className="form-control" placeholder="" defaultValue={
(objectSize(props.editCarData))
? props.editCarData.date_create
: false
} required onChange={(event) => setDateCreateCar(event.target.value)} />
<input type="text" className="form-control d-none" name="date_create" defaultValue={dateCreateCar} placeholder="" />
<div className="valid-feedback">
<i className="bi bi-hand-thumbs-up-fill"></i> Заполнено
</div>
<div className="invalid-feedback">
Поле обязательно к заполнению
</div>
</div>
</div>
<div className="form-group row mt-3">
<label htmlFor="inputPassword" className="col-sm-2 col-form-label">Состояние</label>
<div className="col-sm-10">
<select id="editCarSelect" className="form-select" name="state" required>
<option value="">Выберите из списка</option>
<option value="Битая">Битая</option>
<option value="Не битая">Не битая</option>
</select>
<div className="valid-feedback">
<i className="bi bi-hand-thumbs-up-fill"></i> Заполнено
</div>
<div className="invalid-feedback">
Поле обязательно к заполнению
</div>
</div>
</div>
<div className="form-group row mt-3">
<label htmlFor="inputPassword" className="col-sm-2 col-form-label">Цена</label>
<div className="col-auto">
<div className="input-group mb-2">
<input type="number" className="form-control" name="price"
placeholder="0" defaultValue={
(objectSize(props.editCarData))
? props.editCarData.price_sys
: false
} required />
<div className="input-group-prepend">
<div className="input-group-text">руб.</div>
</div>
<div className="valid-feedback">
<i className="bi bi-hand-thumbs-up-fill"></i> Заполнено
</div>
<div className="invalid-feedback">
Поле обязательно к заполнению
</div>
</div>
</div>
</div>
<div className="form-group row mt-3">
<label htmlFor="inputPassword" className="col-sm-2 col-form-label">Дата покупки</label>
<div className="col-sm-auto">
<input type="date" className="form-control" defaultValue={dateshop} required onChange={(event) => setDateShop(event.target.value)} />
<input type="text" className="form-control d-none" name="date" defaultValue={dateshop} placeholder="" />
<div className="valid-feedback">
<i className="bi bi-hand-thumbs-up-fill"></i> Заполнено
</div>
<div className="invalid-feedback">
Поле обязательно к заполнению
</div>
</div>
</div>
<div className="form-group row mt-3">
<label htmlFor="inputPassword" className="col-sm-2 col-form-label">Комментарии к покупке</label>
<div className="col-sm-10">
<textarea rows="5" name="comment" className="form-control" defaultValue={
(objectSize(props.editCarData))
? props.editCarData.description
: false
}>
</textarea>
<div className="valid-feedback">
<i className="bi bi-hand-thumbs-up-fill"></i> Заполнено
</div>
<div className="invalid-feedback">
Поле обязательно к заполнению
</div>
</div>
</div>
<hr />
<div className="mt-3 w-100">
<div className="d-flex flex-row d-inline flex-wrap" id="AdsImgContainer">
{
(objectSize(props.editCarData))
? (Object.keys(issetImages).length)
? Object.keys(issetImages).map((image, key) => {
return (
<ImgAds src={props.editCarData.images[image]} id_shop={props.id_shop} id_cars={props.editCarData.id} id_img={image.split("/")[props.editCarData.images[image].split("/").length - 1]} key={key} deleteImageServer={deleteImageServer} deleteImgServer={true} />
)
})
: false
: false
}
{
(displayImgAds.length)
? displayImgAds.map((image, key) => {
return (
<ImgAds src={image['url']} id_img={image.index} deleteImage={deleteImgAds} key={key} />
)
})
: false
}
</div>
<label className="btn btn-default d-inline">
<div style={styles.imagebutton}>
<i className="bi bi-camera"></i>
</div>
<input type="file" multiple="multiple" onChange={(event) => addImgAds(event)} hidden />
</label>
</div>
<div className="alert alert-danger d-none mt-3">
</div>
<div className="alert alert-success mt-3 d-none">
</div>
<input type="submit" value="Добавить автомобиль" className="btn btn-success mt-3"/>
</form>
: (loadDataCar)
? false
: <LoadingIcon />
}
</Modal>
);
};
const styles = {
imagebutton: {
paddingLeft: 30,
paddingRight: 30,
paddingTop: 20,
paddingBottom: 20,
fontSize: 40,
color: "#abd6fc",
borderColor: "#abd6fc",
borderStyle: "solid",
borderWidth: 1,
}
}
export default AdminModalEditCar;
Ответы (1 шт):
Подозреваю, что проблема в useEffect.
Вы там делаете setEditCarData(data['dataCar']); и чуть ниже проверяете objectSize(editCarData) (первый сеттер состояния еще не отработал) и сетите значения setDateShop(editCarData.date_add);, однако забываете, что установка значений, это асинхронная операция.
При первом вызвове у вас editCarData === {}. Уже чуть позже, данные приедут в нужную переменную(отсюда и пустота).
При втором вызове аналогично, но в editCarData лежат еще данные от предыдущего вызова
Вот так, скорее всего, должен выглядеть ваш useEffect
useEffect(()=> {
if (props.focusCarID){
setloadDataCar(false);
setEditCarData({});
apiRequest("getDataCar?", "id_shop=" + props.id_shop + "&id_car=" + props.focusCarID)
.then((data) => {
if(data['success']){
setloadDataCar(true);
if (objectSize(data['dataCar'])) {
setEditCarData(data['dataCar']);
setDateShop(data['dataCar'].date_add);
setDateCreateCar(data['dataCar'].date_create);
setAdsAvtoMarka(data['dataCar'].marka['id']);
setIssetImages(data['dataCar'].images);
setDefaultMarkaAvto(data['dataCar'].marka['id']);
$('#editCarSelect option[value="' + data['dataCar'].status + '"]').prop('selected', true);
}
} else console.log(data['error']);
});
}
}, [props.focusCarID]);
код jQuery не убирал - это отдельная история, которая к данному вопросу не относится.