I am trying to get the nested array from a input value of a checkbox.
How do I handle a nested array?
These are the values:
const othersOptions = [
{procedure:'ORAL PROPHYLAXIS',price: 1000},
{procedure:'TOOTH RESTORATION',price:1200},
{procedure:'TOOTH EXTRACTION',price:800}
];
This is how I get the values from checkbox. I am guessing that value={[item]} is procedure:'ORAL PROPHYLAXIS',price: 1000 if the ORAL PROPHYLAXIS checkbox is checked
<Form>
{othersOptions.map((item, index) => (
<div key={index} className="mb-3">
<Form.Check
value={[item]}
id={[item.procedure]}
type="checkbox"
label={`${item.procedure}`}
onClick={handleChangeCheckbox('Others')}
required
/>
</div>
))}
</Form>
When I console.log the value it shows that the value is [Object object] this is the value.
const handleChangeCheckbox = input => event => {
var value = event.target.value;
console.log(value, "this is the value")
var isChecked = event.target.checked;
setChecked(current =>
current.map(obj => {
if (obj.option === input) {
if(isChecked){
return {...obj, chosen: [{...obj.chosen, value}] };
}else{
var newArr = obj.chosen;
var index = newArr.indexOf(event.target.value);
newArr.splice(index, 1);
return {...obj, chosen: newArr};
}
}
return obj;
}),
);
console.log(checked);
}
and this is how I save the nested array:
const [checked, setChecked] = useState([
{ option: 'Others',
chosen: [],
]);
The reason why I need the procedure and price is so that I can save the values to MongoDB and get the values to another page which is a Create Receipt page. I want the following procedures price to automatically display in the Create Receipt page.Thank you for the help!
If anyone is wondering how I fixed it.
I stringfy the input values and parsed the e.target.values
import "./styles.css";
import { Form } from "react-bootstrap";
import { useState } from "react";
import React from "react";
const othersOptions = [
{ procedure: "ORAL PROPHYLAXIS", price: 1000 },
{ procedure: "TOOTH RESTORATION", price: 1200 },
{ procedure: "TOOTH EXTRACTION", price: 800 },
{ procedure: "DEEP SCALING", price: 10200 },
{ procedure: "PTS AND FISSURES SEALANT", price: 700 },
{ procedure: "FLOURIDE TREATMENT", price: 5500 },
{ procedure: "INTERMEDIATE RESTORATION", price: 7000 },
{ procedure: "ORTHODONTICS", price: 48000 }
];
export default function App() {
const [checked, setChecked] = useState([{ option: "Others", chosen: [] }]);
console.log(checked);
const handleChangeCheckbox = (input) => (event) => {
var value = JSON.parse(event.target.value);
var isChecked = event.target.checked;
console.log("value is:", value[0].procedure);
var tempArr = { procedure: value[0].procedure, price: value[0].price };
setChecked((current) =>
current.map((obj) => {
if (obj.option === input) {
if (isChecked) {
return { ...obj, chosen: [...obj.chosen, tempArr] };
} else {
var newArr = obj.chosen;
var index = newArr.indexOf(event.target.value);
newArr.splice(index, 1); // 2nd parameter means remove one item only
return { ...obj, chosen: newArr };
}
}
return obj;
})
);
};
return (
<Form>
{othersOptions.map((item, index) => (
<div key={index} className="mb-3">
<Form.Check
value={JSON.stringify([item])}
id={[item]}
type="checkbox"
label={`${item.procedure}`}
onClick={handleChangeCheckbox("Others")}
required
/>
</div>
))}
</Form>
);
}
RUN THE CODE HERE
Related
When testing the API I have noticed that it is getting called around 6 times, there is no for/foreach loop that would make it run several times in the code so am unsure as to why this may be happening.
The API runs every time a user goes onto the Landing Screen.
Any advice would be appreciated
exports.getFilteredOpportunities = async (req, res) => {
let mongoQuery = generateMongoQuery(req);
try {
const opps = await Opportunity.find(mongoQuery)
.select(removeItems)
.sort({
createdAt: -1
});
res.status(200).json({
opportunities: opps,
});
} catch (error) {
res.status(500).json({
status: "error",
message: error,
});
}
};
GenerateMongoQuery function
const generateMongoQuery = (req) => {
let query = {};
// search param used searching through Title field, ie. search=food
if (req.query.search && req.query.search.length > 0) {
query.$or = [{}, {}];
query.$or[0].Title = query.$or[1].Description = new RegExp(
`${req.query.search}`,
"i"
);
}
// location param, i.e location=Glasgow,Manchester
if (req.query.location && req.query.location.length > 0) {
query.location = {
$in: req.query.location.split(",")
};
}
// timeslot param, i.e timeslot=Evening,Morning
if (req.query.timeslot && req.query.timeslot.length > 0) {
query.timeslot = {
$in: req.query.timeslot.split(",")
};
}
// category param, returning corresponding id i.e category=
if (req.query.category && req.query.category.length > 0) {
query.category = {
$in: req.query.category.split(",")
};
}
// Dont load expired opportunities
query.eventDate = {
$gte: new Date().toDateString()
};
// Only return non-cancelled opportunities
query.isCancelled = false;
return query;
};
The landing Screen
import { useState, useEffect } from "react";
import Opportunities from "../components/molecules/Opportunities";
import Filters from "../components/organisms/Filters";
import { IOpportunity } from "../Models/IOpportunity";
import OpportunitiesClient from "../Api/opportunitiesClient";
import Utils from "../Utils/Utils";
import FiltersClient from "../Api/filtersClient";
import { IFilters } from "../Models/IFilters";
import { ISelectedFilters } from "../Models/ISelectedFilters";
import Header from "../components/atoms/Header";
import Footer from "../components/atoms/Footer";
export default function LandingScreen(props) {
const [opportunities, setOpportunities] = useState<IOpportunity[]>([]);
const [filters, setFilters] = useState<IFilters[]>([]);
const [checkedFilters, setCheckedFilters] = useState<
ISelectedFilters | undefined
>({
Location: [],
Category: [],
Timeslot: [],
});
const [isLoading, setLoading] = useState<boolean>(true);
const [allResultsLoaded, setAllResultsLoaded] = useState<boolean>(false);
let pageToGet = 1;
const [scrollPosition, setScrollPosition] = useState(0);
const [totalOpps, setTotalOpps] = useState(0);
useEffect(() => {
getOpportunities();
getFilters();
}, []);
useEffect(() => {
setTotalOpps(opportunities.length);
}, [opportunities]);
const handleScroll = () => {
setScrollPosition(window.pageYOffset);
let scrollHeight = 0;
if ((props.scrollHeight === null) !== undefined) {
scrollHeight = +props.scrollHeight;
} else {
scrollHeight = document.scrollingElement.scrollHeight;
}
if (
window.innerHeight + document.documentElement.scrollTop >=
scrollHeight
) {
if (allResultsLoaded === false) {
setLoading(true);
}
setTimeout(() => {
pageToGet += 1;
getOpportunities();
}, 600);
}
window.removeEventListener("scroll", handleScroll);
};
window.addEventListener("scroll", handleScroll, { passive: true });
const setItemChecked = (filtername: string, filterType: string) => {
// reset page and results
pageToGet = 1;
setAllResultsLoaded(false);
setCheckedFilters(
Utils.setItemChecked(filtername, filterType, checkedFilters)
);
};
const resetFilters = () => {
// reset page and results
pageToGet = 1;
setAllResultsLoaded(false);
checkedFilters.Location = [];
checkedFilters.Category = [];
checkedFilters.Timeslot = [];
getOpportunities();
};
const getFilters = () => {
FiltersClient.getAll().then((filters) => {
setFilters(filters);
});
};
const getOpportunities = () => {
console.log(opportunities);
OpportunitiesClient.getWithParams(
Utils.getAxiosParams(checkedFilters)
).then((res) => {
definePage(res);
if (opportunities.length === res["opportunities"].length)
setAllResultsLoaded(true);
setLoading(false);
});
};
const definePage = (res) => {
if (pageToGet === 1) {
setOpportunities(res["opportunities"].slice(0, 15));
} else {
setOpportunities(
opportunities.concat(
res["opportunities"].slice(
opportunities.length,
opportunities.length + 15
)
)
);
}
};
return (
<div>
<Header page="landing" includePostButton={true} />
<div
data-testid="test-div1"
className="grid md:grid-cols-[150px_auto] sm:grid-cols-1 md:grid-flow-col gap-4 mx-4 mb-5 my-10"
>
<div></div>
<div className="card-container-title">
{totalOpps} Results{" "}
{checkedFilters.Location.length > 0 ? "(Filtered)" : ""}
</div>
</div>
<div
data-testid="test-div3"
className="grid md:grid-cols-[150px_auto] sm:grid-cols-1 md:grid-flow-col gap-4 mx-4"
>
<div>
<Filters
filters={filters}
getChecked={setItemChecked}
urlCall={getOpportunities}
resetFilters={resetFilters}
checkedFilters={checkedFilters}
/>
</div>
<div
data-testid="test-div4"
className={isLoading ? "opacity-50" : ""}
>
<div className="col-span-2">
<Opportunities
items={opportunities}
isLoading={isLoading}
allResultsLoaded={allResultsLoaded}
/>
</div>
</div>
</div>
<Footer />
</div>
);
}
I have a layout component that needs onAppBarInputChange prop. The onAppBarInputChange prop expected a function that take the input value from the layout component, and filter the todos based on that input value.
How do I pass the props from the todos page to the layout component?
todos.jsx
import {useState} from 'react'
import Layout from './layout'
const Todos = () => {
const [query, setQuery] = useState('')
const todos = [
{
id: 0,
text: 'make some projects'
},
{
id: 1,
text: 'fix some bugs'
},
{
id: 2,
text: 'cook food at home'
}
]
const searchedTodos = todos.filter(todo => todo.toLowerCase().includes(query.toLowerCase()))
return (
<ul>
{searchedTodos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
)
}
Todos.getLayout = function getLayout(page) {
return (
{/* how to set the query like this? */}
<Layout onAppBarInputChage={() => setQuery(e.targe.value)}>
{page}
</Layout>
)
}
export default Todos;
layout.jsx
const Layout = ({children, onAppBarInputChange}) => {
return (
<div>
<header>
<div>Todo page</div>
<input onChange={onAppBarInputChange} />
</header>
<main>{children}</main>
<footer>
some footer here
</footer>
</div>
)
}
export default Layout
Note: I had read the documentation from the next.js website, about how to add layout in next.js, however they don't show any examples on how to pass the props to the layout component
How about passing the input value through Context?
By adopting Context every component can observe the input value easily.
context/app.jsx
const AppContext = createContext(null);
const AppContextProvider = ({ children }) => {
const [query, setQuery] = useState("");
const value = {
query,
setQuery,
};
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
};
const useAppContext = () => useContext(AppContext);
export { AppContext, AppContextProvider, useAppContext };
pages/_app.jsx
function App({ Component, pageProps }) {
return (
<AppContextProvider>
{Component.getLayout(<Component {...pageProps} />)}
</AppContextProvider>
);
}
component/layout.jsx
const Layout = ({ children }) => {
const { setQuery } = useAppContext();
const onAppBarInputChange = (e) => setQuery(e.target.value);
...(snip)...
todos.jsx
const Todos = () => {
const { query } = useAppContext();
...(snip)...
};
Todos.getLayout = function getLayout(page) {
return <Layout>{page}</Layout>;
};
On my application i'm using Reduxjs/toolkit for state management and TypeScript for type safety.
My backend were wrote in Node.js with MongoDB.
I have implemented pagination for one slice/component, and i know that is not the best solution and i want to improve it and make reusable for other slices.
Could you help me with that? Give me some hints?
Below is my current pagination implementation:
// CategorySlice
interface InitialState {
categories: ICategory[];
isFetching: boolean;
errorMessage: string | null;
// to refactor
currentPage: number;
itemsPerPage: number;
totalResults: number;
}
const initialState: InitialState = {
categories: [],
isFetching: false,
errorMessage: '',
// to refactor
currentPage: 1,
itemsPerPage: 9,
totalResults: 0,
};
export const fetchCategories = createAsyncThunk<
{ data: ICategory[]; totalResults: number },
number
>('category/fetchCategories', async (currentPage, { rejectWithValue }) => {
try {
const accessToken = getToken();
if (!accessToken) rejectWithValue('Invalid token');
const config = {
headers: { Authorization: `Bearer ${accessToken}` },
};
const response: IApiResponse<ICategoryToConvert[]> = await api.get(
`/categories?page=${currentPage}&limit=9&isPrivate[ne]=true`,
config
);
const data = response.data.data;
const convertedData = data.map(e => {
return {
id: e._id,
name: e.name,
image: e.image,
};
});
return {
totalResults: response.data.totalResults,
data: convertedData,
};
} catch (error) {
removeToken();
return rejectWithValue(error);
}
});
export const categorySlice = createSlice({
name: 'category',
initialState,
reducers: {
setNextPage(state, { payload }) {
state.currentPage = payload;
},
},
extraReducers: builder => {
builder.addCase(fetchCategories.pending, state => {
state.isFetching = true;
state.errorMessage = null;
});
builder.addCase(fetchCategories.fulfilled, (state, action) => {
state.categories = action.payload.data;
state.isFetching = false;
state.totalResults = action.payload.totalResults;
});
builder.addCase(fetchCategories.rejected, state => {
state.isFetching = false;
state.errorMessage = 'Problem with fetching categories 🐱👤';
});
},
});
// Category Page
const CategoryPage = () => {
const dispatch = useAppDispatch();
const { currentPage } = useAppSelector(state => state.category);
useEffect(() => {
dispatch(fetchCategories(currentPage));
}, [dispatch, currentPage]);
return (
<ContainerWrapper>
<CategoryList />
</ContainerWrapper>
);
};
export default CategoryPage;
Inside CategoryPage
I'm passing those properties from state selector.
<Pagination
currentPage={currentPage}
itemsPerPage={itemsPerPage}
paginate={(n: number) => dispatch(categoryActions.setNextPage(n))}
totalItems={totalResults}
/>
And finally PaginationComponent
interface IProps {
itemsPerPage: number;
totalItems: number;
paginate: (numb: number) => void;
currentPage: number;
}
const Pagination = ({ itemsPerPage, totalItems, paginate, currentPage }: IProps) => {
const numberOfPages = [];
for (let i = 1; i <= Math.ceil(totalItems / itemsPerPage); i++) {
numberOfPages.push(i);
}
return (
<nav className={styles['pagination']}>
<ul className={styles['pagination__list']}>
{numberOfPages.map(number => {
return (
<li
key={number}
className={`${styles['pagination__item']} ${
currentPage === number && styles['pagination__item--active']
}`}
onClick={() => paginate(number)}
>
<div className={styles['pagination__link']}>{number}</div>
</li>
);
})}
</ul>
</nav>
);
};
export default Pagination;
I have a stateful component that calls a CEP promise, to fetch data from post offices. This data is fetched when the Zip input is fulfilled with 9 chars - 8 number and an '-' - and return an object with desired information.
Heres the function:
const handleZipCode = useCallback(
async ({ target }: ChangeEvent<HTMLInputElement>) => {
const { value } = target;
try {
if (value.length === 9 && isDigit(value[8])) {
const zip = await cep(value);
if (zip?.city) {
setZipData(zip);
} else
addressFormRef.current?.setErrors({
user_zip_code: 'CEP not found',
});
}
} catch (e) {
addressFormRef.current?.setErrors({
user_zip_code: e.message ?? 'CEP not found',
});
}
},
[isDigit]
);
Then, on the return I have some fields, example:
<fieldset>
<legend>Address</legend>
<Input
mask=''
name='user_address'
placeholder='Rua um dois três'
defaultValue={zipData.street}
/>
</fieldset>
Here's the Input component:
const Input: React.FC<InputProps> = ({ name, ...rest }) => {
const { fieldName, defaultValue, registerField, error } = useField(name);
const inputRef = useRef(null);
useEffect(() => {
registerField({
name: fieldName,
ref: inputRef.current,
path: 'value',
// eslint-disable-next-line
setValue(ref: any, value: string) {
ref.setInputValue(value);
},
// eslint-disable-next-line
clearValue(ref: any) {
ref.setInputValue('');
},
});
}, [fieldName, registerField]);
return (
<Container>
<ReactInputMask ref={inputRef} defaultValue={defaultValue} {...rest} />
{error && <Error>{error}</Error>}
</Container>
);
};
However the zipData seems to update, but the default value is not fulfilled. What I'm doing wrong?
The default value will not change, as unform is an uncontrolled form library, the defaultValue will be set on the first render of the page and then will not change anymore.
To fix your problem you can do something like:
// on your handleZipCode function
formRef.current.setData({
zipData: {
street: zipResult.street,
},
});
editStatusObj.categories values comes from database like (1,15,23) in one column, if there is only one value available then it shows checkbox checked, but how can I show multiple checked using multiple values in angular?
edit-status.component.html
<div>
<label *ngFor="let statusCategoryObj of statusCategoryObj">
<mat-checkbox value="{{statusCategoryObj.categorytitle}}" [checked]="statusCategoryObj.id == editStatusObj.categories" name="categories" (change)="onCheckboxChange(statusCategoryObj,$event)">
{{statusCategoryObj.categorytitle}}</mat-checkbox>
</label>
</div>
edit-status.component.ts
editStatusObj : Status = new Status();
statusCategoryObj : Category[];
ngOnInit() {
const id = +this.route.snapshot.paramMap.get('id');
this.statusService.editStatusDetailsById({'id': id}).subscribe(
(data) => {
if(data.status == 28){
editStatusObj.id = data.payload[0].id;
editStatusObj.categories = data.payload[0].categories;
}
}
)
this.statusService.getCategoryInStatus(em).subscribe(
(data) => {
if(data.status == 23){
statusCategoryObj.id = data.payload.id;
}
}
);
}
app.js(node side code)
app.post('/editStatusDetailsById',function(req,res){
connection.query('SELECT id,categories from status WHERE id = ?',[req.body.id], function (error, results, fields) {
if (error) throw error;
res.send({
'status':'28',
'success': 'true',
'payload': results,
'message':'one language is edited'
});
});
});
status.ts
export class Status {
constructor(public id : number = null,
public languages : string = "") { }
}
statuscategory.ts
export class StatusCategory {
constructor(public id : number = null,
public categorytitle : string = "",
){}
I assumed editStatusObj as an Object Array.
DEMO
HTML:
<div>
<label *ngFor="let statusCategoryObj of statusCategoryObj;let i = index">
<mat-checkbox value="{{statusCategoryObj.categorytitle}}" [checked]="checkedCategories(statusCategoryObj.id)" name="categories" (change)="onCheckboxChange(statusCategoryObj,$event)">
{{statusCategoryObj.categorytitle}}</mat-checkbox>
</label>
</div>
TS:
categories: Array<any> = [];
statusCategoryObj: Array<any> = [
{ category: 1, id: 1, categorytitle: 'cat 1' },
{ category: 2, id: 2, categorytitle: 'cat 2' },
{ category: 3, id: 3, categorytitle: 'cat 3' },
];
editStatusObj: Array<any> = [
{ categories: '1,3,5', id: 1, categorytitle: 'cat 1' },
]
ngOnInit() {
const id = +this.route.snapshot.paramMap.get('id');
this.statusService.editStatusDetailsById({'id': id}).subscribe(
(data) => {
if(data.status == 28){
this.editStatusObj.id = data.payload[0].id;
this.editStatusObj.categories = data.payload[0].categories;
this.allCategories = this.editStatusObj.categories.split(',');
}
}
)
}
checkedCategories(id){
for(var i = 0 ; i < this.allCategories.length; i++){
if(id == this.allCategories[i]){
return true;
}
}
return false;
}