Unable to update nested state react - reactjs

I have a reusable drop down menu component and i render it twice with two different lists and it should update the state with the id of the first element.
the first drop down of the layout update the state without any issue but the second one does not(i switched the order and it always seems the first one updates the state the second doesn't).
please see code
dashbord
const initializeData = {
actionStatuses: [],
actionCategories: [],
actionGroups: [],
actionEvents: [],
actionEventsWithFilter: [],
selectedFilters: {actionStatusId: "", actionCategoryId:""},
};
const Dashboard = ({ selectedPracticeAndFy }) => {
const [data, setData] = useState(initializeData);
const getSelectedStatus = ({ key }) => {
const actionStatusId = key;
const selectedFilters = { ...data.selectedFilters, actionStatusId };
setData((prevState) => {
return { ...prevState, selectedFilters }
});
};
const getSelectedCategory = ({ key }) => {
const actionCategoryId = key;
const selectedFilters = { ...data.selectedFilters, actionCategoryId };
setData((prevState) => {
return { ...prevState, selectedFilters }
});
};
}
result filter:
const ResultFilter = ({actionStatuses, actionCategories, getSelectedStatus, getSelectedCategory}) => {
return (
<Grid
justify="flex-start"
container
>
<Grid item >
<Typography component="div" style={{padding:"3px 9px 0px 0px"}}>
<Box fontWeight="fontWeightBold" m={1}>
Result Filter:
</Box>
</Typography>
</Grid>
<Grid >
<DropdownList payload={actionCategories} onChange={getSelectedCategory} widthSize= {dropdownStyle.medium}/>
<DropdownList payload={actionStatuses} onChange={getSelectedStatus} widthSize= {dropdownStyle.medium}/>
</Grid>
</Grid>
);
}
DropdownList:
const DropdownList = ({ label, payload, onChange, widthSize, heightSize, withBorders, initialData }) => {
const { selectedData, setSelectedData, handelInputChange } = useForm(
payload
);
useEffect(() => {
if (Object.entries(selectedData).length === 0 && payload.length !== 0) {
setSelectedData(payload[0]);
}
}, [payload]);
useEffect(() => {
if (Object.entries(selectedData).length !== 0) {
onChange(selectedData);
}
}, [selectedData]);
return (
<div style={widthSize}>
<div className="ct-select-group ct-js-select-group" style={heightSize}>
<select
className="ct-select ct-js-select"
id={label}
value={JSON.stringify(selectedData)}
onChange={handelInputChange}
style={withBorders}
>
{label && <option value={JSON.stringify({key: "", value: ""})}>{label}</option>}
{payload.map((item, i) => (
<option key={i} value={JSON.stringify(item)} title={item.value}>
{item.value}
</option>
))}
</select>
</div>
</div>
);
};

It may be a stale closure issue, could you try the following:
const getSelectedStatus = ({ key }) => {
setData((data) => {
const actionStatusId = key;
const selectedFilters = {
...data.selectedFilters,
actionStatusId,
};
return { ...data, selectedFilters };
});
};
const getSelectedCategory = ({ key }) => {
setData((data) => {
const actionCategoryId = key;
const selectedFilters = {
...data.selectedFilters,
actionCategoryId,
};
return { ...data, selectedFilters };
});
};

Related

Loop in useEffect?

I'm trying to select a date and the data should be reloaded every time a specific date is selected. But with my code, it would loop.
Currently I'm using syncfusion's schedule component. Redux toolkit.
Here is my code.
const Schedules = () => {
const { facilities } = useSelector((store) => store.allFacilities);
const { fieldsScheduler } = useSelector((store) => store.allFields);
const { timeSlots, isLoadingReservation } = useSelector(
(store) => store.allReservation
);
const [calRef, setCalRef] = useState();
const [time, setTime] = useState("");
useEffect(() => {
if (calRef && time) {
console.log("TIME", time);
dispatch(getAllReservation(time)); <--- Loop
}
}, [time, calRef]);
useEffect(() => {
dispatch(getFacilities());
dispatch(getFields());
}, []);
const dispatch = useDispatch();
if (isLoadingReservation) {
return <Loading />;
}
const headerInfo = (props) => {
return (
<Grid container>
<Grid item xs={12}>
<h6>{props.subject}</h6>
</Grid>
</Grid>
);
};
const bodyInfo = (props) => {
return (
<Grid container>
<Grid item xs={12}>
<h6>{`Giá tiền cụ thể`}</h6>
</Grid>
</Grid>
);
};
const footerInfo = (props) => {
return (
<Grid container>
<Grid item xs={12}>
<h6>{`Footer here`}</h6>
</Grid>
</Grid>
);
};
const eventTemplate = (props) => {
return (
<>
<div>
<Typography variant="p">
{`${props.subject}: ${props.rentalFee}`}K
</Typography>
</div>
<div>
<Typography variant="p">{`${moment(props.startTime, "HHmm").format(
"HH:mm"
)} - ${moment(props.endTime, "HHmm").format("HH:mm")} `}</Typography>
</div>
</>
);
};
const onDataBinding = () => {
// var scheduleObj = document.querySelector(".e-schedule").ej2_instances[0];
// var currentViewDates = scheduleObj.getCurrentViewDates();
// dispatch(setCurrentDate(moment(startDate).format("YYYY-MM-DD[T]HH:mm:ss")));
// dispatch(
// getAllReservation(moment(startDate).format("YYYY-MM-DD[T]HH:mm:ss"))
// );
var currentViewDates = calRef.getCurrentViewDates();
var startDate = currentViewDates[0];
var endDate = currentViewDates[currentViewDates.length - 1];
console.log("Start date", startDate);
// setTime(moment(startDate).format("YYYY-MM-DD[T]HH:mm:ss"));
};
return (
<MDBox
width="100%"
height="100%"
minHeight="100vh"
borderRadius="lg"
shadow="lg"
bgColor="white"
sx={{ overflowX: "hidden" }}
>
<ScheduleComponent
cssClass="timeline-resource-grouping"
width="100%"
height="100%"
locale="vi"
readonly={true}
currentView="TimelineDay"
allowDragAndDrop={false}
dataBinding={onDataBinding}
// onChange={onDataBinding}
ref={(t) => setCalRef(t)}
// quickInfoTemplates={{
// header: headerInfo.bind(this),
// content: bodyInfo.bind(this),
// footer: footerInfo.bind(this),
// }}
//Data get all event in here. then mapping in ResourceDirective (field)
eventSettings={{
dataSource: timeSlots,
fields: {
subject: { name: "subject" },
id: "reservationId",
endTime: { name: "endTime" },
startTime: { name: "startTime" },
rentalFee: { name: "rentalFee", title: "Phí thuê sân" },
},
template: eventTemplate.bind(this),
}}
group={{ resources: ["Facilities", "Fields"] }}
>
<ResourcesDirective>
<ResourceDirective
field="facilityId"
title="Chọn cơ sở"
name="Facilities"
allowMultiple={false}
dataSource={facilities}
textField="facilityName"
idField="id"
></ResourceDirective>
<ResourceDirective
field="fieldId"
title="Sân"
name="Fields"
allowMultiple={true}
dataSource={fieldsScheduler}
textField="fieldName"
idField="id"
groupIDField="facilityId"
colorField="color"
></ResourceDirective>
</ResourcesDirective>
<ViewsDirective>
<ViewDirective option="TimelineDay" />
<ViewDirective option="Day" />
</ViewsDirective>
<Inject
services={[
Day,
Week,
TimelineViews,
TimelineMonth,
Agenda,
Resize,
DragAndDrop,
]}
/>
</ScheduleComponent>
</MDBox>
);
};
export default Schedules;
This is my slice.
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import { toast } from "react-toastify";
import customFetch from "utils/axios";
const initialState = {
isLoadingReservation: false,
timeSlots: [],
currentDate: "",
};
const authHeader = (thunkAPI) => {
return {
headers: {
Authorization: `Bearer ${thunkAPI.getState().user.user.accessToken}`,
},
};
};
export const getAllReservation = createAsyncThunk(
"allReservation/getAllReservation",
async (currentDay, thunkAPI) => {
console.log("Log:", currentDay);
try {
const response = await customFetch.post(
`/reservation-slots`,
{ date: currentDay },
authHeader(thunkAPI)
);
return response.data.timeSlots;
} catch (error) {
return thunkAPI.rejectWithValue(error.response.data);
}
}
);
const allReservationSlice = createSlice({
name: "allReservation",
initialState,
reducers: {
setCurrentDate: (state, action) => {
console.log("Payload", action.payload);
state.currentDate = action.payload;
},
},
extraReducers: {
[getAllReservation.pending]: (state, action) => {
state.isLoadingReservation = true;
},
[getAllReservation.fulfilled]: (state, action) => {
state.isLoadingReservation = false;
state.timeSlots = action.payload;
},
[getAllReservation.rejected]: (state, action) => {
state.isLoadingReservation = false;
toast.error(action.payload);
},
},
});
export const { setCurrentDate } = allReservationSlice.actions;
export default allReservationSlice.reducer;
But there is no way to do that. I used useEffect to update the UI again, but it still loop again.
I don't know if there is a way to solve my problem?
I sat for 3 days straight and the situation did not improve much.

Unable to use value of useRef in value of input in react native

I have a dropdown based on which different fields gets rendered. Initially I have initialized selected option of dropdown in useEffect hook, corresponding fields gets rendered and are working fine.
But when different option or even same option is chosen from dropdown fields gets rendered but when I type something in it it gets cleared. I have used useRef to store values for field at index i in fields array.
export default function CreateEntry({ navigation }) {
const { user, setLoading, setLoadingMsg } = useAuthContext();
const [name, setName] = useState("");
const [selectedCategory, setSelectedCategory] = useState("");
const [categoriesList, setCategoriesList] = useState([]);
const [primary, setPrimary] = useState("");
const [primaryValue, setPrimaryValue] = useState("");
const [fields, setFields] = useState([
{
name: "Email",
value: "",
type: "string",
encrypt: false,
},
{
name: "Password",
value: "",
type: "string",
encrypt: true,
},
]);
const refInputs = useRef([]);
useEffect(() => {
firestore()
.collection("categories")
.doc(user.uid)
.get()
.then((documentSnapshot) => {
setCategoriesList(documentSnapshot.data().category);
setSelectedCategory(documentSnapshot.data().category[0].name);
setFields(documentSnapshot.data().category[0].fields);
fields.map((field) => {
refInputs.current.push("");
});
})
.catch((error) => {
console.log(error);
});
}, []);
const addInput = () => {
refInputs.current.push("");
setFields([
...fields,
{ name: "First", value: "", type: "string", encrypt: false },
]);
};
const removeInput = (i) => {
refInputs.current[i] = "";
setFields((fields) =>
fields.map((field, index) => {
if (index === i) {
return null;
}
return field;
})
);
};
const setInputValue = (index, value) => {
refInputs.current[index] = value;
};
const inputs = [];
fields.forEach((field, i) => {
if (field !== null) {
inputs.push(
<View
key={i}
>
<TextInput
onChangeText={(value) => setInputValue(i, value)}
value={refInputs.current[i]}
/>
<TouchableOpacity
onPress={() => removeInput(i)}
>
<Ionicons name="close" style={[tw``]} size={22} color="#ddd" />
</TouchableOpacity>
</View>
);
}
});
const addEntry = () => {
//
};
return (
<ScrollView style={styles.container}>
<Dropdown
data={categoriesList}
value={selectedCategory}
onChange={(item) => {
refInputs.current = [];
setSelectedCategory(item.name);
setFields(item.fields);
item.fields.map((field) => {
refInputs.current.push("");
});
}}
/>
<Input
onChangeText={setName}
value={name}
placeholder="Name"
/>
{inputs}
<Pressable onPress={addInput}>
<Text}>+ Add a new input</Text>
</Pressable>
<MainButton onPress={addEntry}>Add</MainButton>
</ScrollView>
);
}

How to handle old state reducer react

I am creating a cart where the client has multiple checkboxes, I can update the cart when the checkbox is checked and update it when is unchecked, so far so good, but the problem is when the client wants to create another order, I think the reducer is bringing me the old order.
This is my reducer
if (action.type === ADD_ITEM_TO_CART_DETAILS) {
return {
...state,
cart: [...state.cart, action.payload]
}
}
This is my action
export function addItemDetail(value){
return {
type : ADD_ITEM_TO_CART_DETAILS,
payload: value
}
}
and this is the JS:
export default function DetailProduct() {
const dispatch = useDispatch();
const { id } = useParams();
const history = useHistory();
useEffect(() => {
dispatch(getDetail(id));
}, [dispatch, id]);
const detail = useSelector((state) => state.detail); // details of the products
const { options, setOptions } = useContext(OrderContext);
const BackToProducts = () => {
if (options.salsa.length) {
dispatch(addItemDetail(options));
} else {
history.push("/productos");
}
};
const seeCart = () => {
if (options.salsa.length) {
dispatch(addItemDetail(options));
} else {
history.push("/carrito");
}
};
const handleComments = (e) => {
setOptions((prev) => ({
...prev,
Comments: e.target.value,
}));
};
const handleSalsa = (e) => {
const { name, checked } = e.target;
if (options.salsa.length <= 2) {
e.target.checked = false;
} else if (checked === true) {
setOptions((prev) => ({
...prev,
salsa: [...prev.salsa, name],
picture_url: detail.picture_url,
id: uuidv4(),
price: detail.price,
title: detail.title,
}));
}
if (checked === false) {
setOptions((prev) => ({
...prev,
salsa: prev.salsa.filter((p) => p !== name),
}));
}
};
const handleToppings = async (e) => {
const { name, checked } = e.target;
if (checked === true) {
setOptions({ ...options, toppings: [...options.toppings, name] });
}
if (checked === false) {
setOptions((prev) => ({
...prev,
toppings: prev.toppings.filter((p) => p !== name),
}));
}
};
useEffect(() => {
// useEffect to update the total amount
const productPrice = options.price; // price of the single product
const toppingPrice = options.priceTopping; // price of the topping
const total = toppingPrice ? productPrice + toppingPrice : productPrice;
setOptions((prev) => ({ ...prev, unit_price: total })); // set total amount product plus toppings
}, [options.price, options.priceTopping, setOptions]);
useEffect(() => {
// useEffect to update total amount of the toppings
const numberOfToppings = options.toppings.length;
const totalPriceTopping = numberOfToppings !== 0 ? numberOfToppings * 119 : 0;
setOptions((prev) => ({ ...prev, priceTopping: totalPriceTopping }));
}, [options.toppings, setOptions]);
return (
<MainContainer>
{detail.picture_url ? <PhotoProduct src={`https://hit-pasta.herokuapp.com/${detail.picture_url}`} /> : <Loading />}
<Like
onClick={() => {
history.push("/productos");
}}
/>
<ContainerOption>
{detail &&
detail?.salsas?.map((p, index) => {
return (
<ContainerOptionChild key={index}>
<div>
<Drop />
<LabelProductName>{p.sauce}</LabelProductName>
</div>
<InputOptions type="checkbox" checked={options.salsa.index} key={index} name={p.sauce} value={p.sauce} onChange={handleSalsa} />
<Description>{p.description}</Description>
</ContainerOptionChild>
);
})}
</ContainerOption>
<MainBoxComments>
<h3>Comentarios</h3>
<BoxComentario type="text" value={options.Comments} onChange={handleComments} placeholder="Agrega instrucciones o comentarios a tu orden" />
</MainBoxComments>
<MainBoxBtns>
<Okay onClick={seeCart}>
OKAY <CartIcon />
</Okay>
<BtnArmarOtroHit onClick={BackToProducts}>ARMAR OTRO HIT</BtnArmarOtroHit>
</MainBoxBtns>{" "}
</MainContainer>
);
}

Next.js remember last path with old query after changing sorting options

I have strange issue with Next.js. In projet we have categories and sorting options.
We are using this as a two seperate compnents:
type ITagsScreenOwnProps = {
id?: string;
title?: string;
query?: { [key: string]: string | number | boolean };
};
export const TagsScreen = () => {
const router = useRouter();
const params = router.query as ITagsScreenOwnProps;
const dispatch = useDispatch();
const { t } = useTranslation();
const mediaListId = params.id ?? -1;
const mediaListSelector = (state: IAppState) => {
return state.media.mediaList[mediaListId] || {};
};
const mediaList = useSelector<IAppState, IMediaListModel>(mediaListSelector);
const hasMoreItems = mediaList?.TotalCount > mediaList?.Entities?.length;
const isOnFirstPage =
!mediaList?.Filter?.PageNumber || mediaList?.Filter?.PageNumber === 1;
const isOnNextPage =
mediaList?.Filter?.PageNumber && mediaList?.Filter?.PageNumber > 1;
useEffect(() => {
dispatch(
getMediaList({
MediaListId: mediaListId,
QueryParams: [params.query] ? params.query : undefined,
PageSize: 12,
PageNumber: 1,
})
);
}, [router.query, dispatch]);
const getMore = useCallback(() => {
if (mediaList.Filter?.PageNumber) {
dispatch(
getMediaList({
...mediaList.Filter,
MediaListId: mediaListId,
PageSize: 12,
PageNumber: mediaList.Filter.PageNumber + 1,
})
);
}
}, [mediaList.Filter?.PageNumber, dispatch]);
return (
<div className={styles.container}>
<div className={styles.buttonContainer}>
<MediaCategoryDrawer />
<MediaSortDrawer />
</div>
{mediaList.IsLoading && isOnFirstPage ? (
<div className={styles.loader}>
<LoaderSpinner width={75} height={75} />
</div>
) : (
<div className={styles.content}>
<GridComponent
cellPadding={28}
columns={3}
component={{
ComponentTypeCode: ComponentType.List,
CellType: CellType.Frame,
Orientation: Orientation.Grid,
MediaList: mediaList.Entities,
}}
/>
{hasMoreItems && (
<div className={styles.loader}>
{mediaList?.IsLoading && isOnNextPage ? (
<LoaderSpinner width={75} height={75} />
) : (
<MediaButton
icon={<ChevronDown />}
iconElevated
variant="transparent"
onClick={getMore}
>
{t("COMMON__BUTTON_MORE", "Show more")}
</MediaButton>
)}
</div>
)}
</div>
)}
</div>
);
};
export default TagsScreen;
So we have MediasortingDrawer and MediaCatgoriesDrawer
My problem is that, every time do change my sorting option it is using my initial link. So if I enter my screen with http://localhost:3000/category/asset?query=action_C77030b than even if i change action to drama my sorting will go use such link localhost:3000/category/asset?query=action_C77030b&sort=popularity.month
Not sure why this is happening and why Next.js is using old query.
My category drawer looks like this
const options = {
dropdownMatchSelectWidth: 350,
listHeight: 500,
dropdownAlign: { offset: [0, 1] },
className: `${styles.optionName}`,
};
export const MediaCategoryDrawer = () => {
const router = useRouter();
const params = router.query;
const dispatch = useDispatch();
const [optionValue, setOptionValue] = useState<string | undefined>("");
const pageNumber = 1;
const PAGE_SIZE = 20;
const categoriesToShow = pageNumber * PAGE_SIZE;
const { t } = useTranslation();
const mediaCategoriesSelector = (state: IAppState) => {
return state.media.mediaCategories;
};
const mediaCategories = useSelector<IAppState, IMediaCategoryListModel>(
mediaCategoriesSelector
);
useEffect(() => {
dispatch(getMediaCategories());
}, [dispatch]);
const onCategoryChange = useCallback(
(categoryId?: string) => {
const splitId = categoryId?.split("_")[0];
setOptionValue(splitId);
if (categoryId === "All categories") {
router.replace(router.asPath.split("?")[0]);
} else {
router.replace({
search: UrlHelper.joinQueries(params, {
query: categoryId,
}),
});
}
},
[optionValue]
);
return (
<div>
<Select
{...options}
placeholder={t(
"MEDIA_CATEGORY_DRAWER__ALL_GENRES_COLLECTIONS",
"All Genres & Collections"
)}
defaultValue={`${t("SELECT_CATEGORIES", "Categories: ")}${optionValue}`}
value={`${t("SELECT_CATEGORIES", "Categories: ")}${optionValue}`}
onChange={(e) => onCategoryChange(e)}
>
<Option key="All" value="All categories">
{t("ALL_CATEGORIES", "All categories")}
</Option>
{mediaCategories?.Entities?.slice(0, categoriesToShow - 1).map(
(category) => (
<Option key={category.CategoryName} value={category.CategoryId}>
{category.CategoryName}
</Option>
)
)}
</Select>
</div>
);
};
and sorting drawer looks like this
const IMediaSortOptions = [
{ name: "SELECT__OPTION_POPULAR" },
{ name: "SELECT__OPTION_RECENTLY_ADDED" },
{ name: "SELECT__OPTION_ALPHABETICAL" },
{ name: "SELECT__OPTION_YEAR" },
];
const options = {
dropdownMatchSelectWidth: 350,
listHeight: 500,
dropdownAlign: { offset: [0, 1] },
};
export const MediaSortDrawer = () => {
const { t, i18n } = useTranslation();
const router = useRouter();
const params = router.query;
const [optionValue, setOptionValue] = useState<string | undefined>("");
const langValue = i18n.language;
console.log("params: ", router);
const onSortChange = useCallback(
(sortId?: string) => {
let linkId = sortId;
setOptionValue(sortId);
switch (sortId) {
case SortTypes.ALPHABETICAL:
linkId = `localized.${langValue}.title`;
break;
case SortTypes.YEAR:
linkId = "productionYear";
break;
case SortTypes.POPULAR:
linkId = "popularity.month";
break;
case SortTypes.RECENTLY_ADDED:
linkId = "created";
break;
}
router.replace({
search: UrlHelper.joinQueries(params, {
sort: linkId,
}),
});
},
[langValue]
);
return (
<div>
<Select
{...options}
defaultValue={`${t("SELECT__SORT")}${optionValue}`}
value={`${t("SELECT__SORT")}${optionValue}`}
onChange={(e) => onSortChange(e)}
>
{IMediaSortOptions.map((sortOption) => (
<Option key={`${t(sortOption.name)}`} value={`${t(sortOption.name)}`}>
{t(sortOption.name)}
</Option>
))}
</Select>
</div>
);
};

Using reactPrime library in DataView components how update dynamic values (react hook)?

how I can update price value when update quantity value automatically ??
page design
interface ui
print values on the console:
print values on the console:
This sentence needs to be modified
{quantity[initQ] == 1 ? data.price : initP[initQ]}
i use setState to save multiple values
export default function Contaner({ setPressed, getPressed }) {
const [products, setProducts] = useState([]);
const [layout, setLayout] = useState('list');
let initQ = 1;
const [initP,setInitP] = useState({ [initQ]: 1 }) ;
const [quantity, setQuantity] = useState({ [initQ]: 1 });
function checkQuantity(e, data) {
if (e.value <= data.quantity) {
initQ = data.name;
setQuantity({ ...quantity, [data.name]: e.value});
setInitP( { ...quantity, [data.name]: data.price * e.value});
console.log(initP );
setCart(current => [...current, data.name]);
}
else {
showError();
}
}
const renderListItem = (data) => {
return (
<div style={{ display: "flex" }}>
<button className="button_color" onClick={() => removeItem(data)}>
<i className="pi pi-trash"></i>
</button>
<h6>{quantity[initQ] == 1 ? data.price : initP[initQ] }</h6>
<InputNumber id="stacked" showButtons min={1} value={quantity[initQ]}
onValueChange={(e) => checkQuantity(e, data)} />
<InputText disabled={true} value={"₪ " + data.price} />
<h6>{data.name}</h6>
</div>
);
}
const itemTemplate = (product, layout) => {
if (!product) {
return <></>;
}
if (layout === 'list') {
return renderListItem(product);
}
}
return(
<DataView value={products} layout={layout} itemTemplate={itemTemplate} rows={1} />
);
}

Resources