How to setState for react useState hook, array of objects? - reactjs

Here, using an array I have mapped the input boxes, and the value which has entered in that array should be stored in an usestate hook and on click of submit button it should console the array of objects. How to acheive this? Thank You.
const AddCheckPoint = ({ master }) => {
const [addChecklist, setAddChecklist] = useState([])
const [addChecklistValues, setAddChecklistValues] = useState({})
const submit = () => {
setShow(false)
console.log(JSON.stringify(addChecklist))
}
const getFormDetails = (val, id) => {
setAddChecklistValues(prevState => ({
...prevState,
ch_id: id,
value: val
}));
setAddChecklist(prevArray => [...prevArray, addChecklistValues])
}
return (
<>
<Row><Col><b>SLno</b></Col><Col><b>Checkpoint</b></Col><Col><b>Value</b></Col></Row>
{master && master.map((r, i) => {
return (<Form.Group key={i} className="mb-3" controlId={i}>
<Row><Col><Form.Label>{r.slno}</Form.Label></Col><Col><Form.Label>{r.checkPoint}</Form.Label></Col><Col><Form.Control type="text" placeholder="Enter Username" onChange={e => getFormDetails(e.target.value, r._id)} /></Col></Row>
</Form.Group>)
})}
<Button variant="primary" onClick={submit}>Submit</Button>
</>
);
}
export default AddCheckPoint
And the prop "master" looks like the follows
[
{
"_id": "61028558b45073399077becd",
"slno": "A1",
"checkPoint": "Position of adaptor CAT",
"cellName": "PC - 1",
"createdAt": "2021-07-29T10:39:20.902Z",
"updatedAt": "2021-07-29T10:39:20.902Z",
"__v": 0
},
{
"_id": "61028567b45073399077becf",
"slno": "A2",
"checkPoint": "Flush height of Adaptor CAT position",
"cellName": "PC - 1",
"createdAt": "2021-07-29T10:39:35.752Z",
"updatedAt": "2021-07-29T10:39:35.752Z",
"__v": 0
}
]

Just add simple by create a new value and pass it into setAddChecklistValues and setAddChecklist
const getFormDetails = (val, id) => {
const newValue = {
ch_id: id,
value: val,
};
setAddChecklistValues(newValue);
setAddChecklist((prevArray) => [...prevArray, newValue]);
};

Here as you are using onchange u must use debouncing technique to get correct the array of the objects. Refer Following url:
https://www.telerik.com/blogs/debouncing-and-throttling-in-javascript or you can replace below function in your code :
const getFormDetails = (val, id) => {
setAddChecklistValues((prevState) => ({
...prevState,
ch_id: id,
value: val
}));
setAddChecklist((prevArray) => {
const k = prevArray.filter((e) => {
return e.ch_id === addChecklistValues.ch_id;
});
if (k.length > 0) {
prevArray.map((e) => {
if (e.ch_id === addChecklistValues.ch_id) {
e.value = addChecklistValues.value;
}
return e;
});
return prevArray;
}
return [...prevArray, addChecklistValues];
});
};

Related

useCallback not updating array with object

I am trying to update array with objects using useCallback but instead of updating object it is adding or appending one too.
const items = [
{
id: 101,
name: 'Mae Jemison',
},
{
id: 201,
name: 'Ellen Ochoa',
},
];
const [headings, setHeadings] = useState(items);
const handleHeadingTextChange = useCallback(
(value, id) => {
let items2 = headings;
items2 = items2.map((item, key) => {
if (items2[key].id == id) {
items2[key].name = value
}
return items2
});
setHeadings((prevState) => [...prevState, items2]) // adding, not updating
//setHeadings((prevState) => [...prevState, ...items2]) // try 2 : still adding
},
[setHeadings],
);
<input type="text" id="101" value="" onChange={handleHeadingTextChange} />
So, on change expected output is
items = [
{
id: 101,
name: 'Johaan',
},
{
id: 201,
name: 'Ellen Ochoa',
},
];
But Instead I am getting
items = [
{
id: 101,
name: 'Johaan',
},
{
id: 201,
name: 'Ellen Ochoa',
},
[{
id: 101,
name: 'Johaan',
},
{
id: 201,
name: 'Ellen Ochoa',
}]
];
I am not sure how to set value in setHeading function so that it only update the value and not appending one too. Is there way to make it update only?
problems
.map iterates over the entire array. that doesn't make sense if you are trying to just update a single item.
onChange takes an event handler, but your handler accepts (value, id)
the value property of your <input> should be set from the object's name property
solution
function MyComponent() {
const [headings, setHeadings] = useState(items);
const updateName = key => event => {
setHeadings(prevState => {
return [
// elements before the key to update
...prevState.slice(0, key),
// the element to update
{ ...prevState[key], name: event.currentTarget.value },
// elements after the key
...prevState.slice(key + 1),
]
})
}
return headings.map((h, key) =>
<input key={key} id={h.id} value={h.name} onChange={updateName(key)} />
)
}
improvement
Imagine having to write that function each time you have a component with array or object state. Extract it to a generic function and use it where necessary -
// generic functions
function arrUpdate(arr, key, func) {
return [...arr.slice(0, key), func(arr[key]), ...arr.slice(key + 1)]
}
function objUpdate(obj, key, func) {
return {...obj, [key]: func(obj[key])}
}
// simplified component
function MyComponent() {
const [headings, setHeadings] = useState(items);
const updateName = key => event => {
setHeadings(prevState =>
// update array at key
updateArr(prevState, key, elem =>
// update elem's name property
updateObj(elem, "name", prevName =>
// new element name
event.currentTarget.value
)
)
)
}
return headings.map((h, key) =>
<input key={key} id={h.id} value={h.name} onChange={updateName(key)} />
)
}
multiple arrow functions?
Curried functions make writing your event handlers easier, but maybe you've never used them before. Here's what the uncurried version would look like -
function MyComponent() {
const [headings, setHeadings] = useState(items);
const updateName = (key, event) => {
setHeadings(prevState =>
// update array at key
updateArr(prevState, key, elem =>
// update elem name property
updateObj(elem, "name", prevName =>
// new element name
event.currentTarget.value
)
)
)
}
return headings.map((h, key) =>
<input key={key} id={h.id} value={h.name} onChange={e => updateName(key, e)} />
)
}

REACT- Displaying and filtering specific data

I want to display by default only data where the status are Pending and Not started. For now, all data are displayed in my Table with
these status: Good,Pending, Not started (see the picture).
But I also want to have the possibility to see the Good status either by creating next to the Apply button a toggle switch : Show good menus, ( I've made a function Toggle.jsx), which will offer the possibility to see all status included Good.
I really don't know how to do that, here what I have now :
export default function MenuDisplay() {
const { menuId } = useParams();
const [selected, setSelected] = useState({});
const [hidden, setHidden] = useState({});
const [menus, setMenus] = useState([]);
useEffect(() => {
axios.post(url,{menuId:parseInt(menuId)})
.then(res => {
console.log(res)
setMenus(res.data.menus)
})
.catch(err => {
console.log(err)
})
}, [menuId]);
// If any row is selected, the button should be in the Apply state
// else it should be in the Cancel state
const buttonMode = Object.values(selected).some((isSelected) => isSelected)
? "apply"
: "cancel";
const rowSelectHandler = (id) => (checked) => {
setSelected((selected) => ({
...selected,
[id]: checked
}));
};
const handleClick = () => {
if (buttonMode === "apply") {
// Hide currently selected items
const currentlySelected = {};
Object.entries(selected).forEach(([id, isSelected]) => {
if (isSelected) {
currentlySelected[id] = isSelected;
}
});
setHidden({ ...hidden, ...currentlySelected });
// Clear all selection
const newSelected = {};
Object.keys(selected).forEach((id) => {
newSelected[id] = false;
});
setSelected(newSelected);
} else {
// Select all currently hidden items
const currentlyHidden = {};
Object.entries(hidden).forEach(([id, isHidden]) => {
if (isHidden) {
currentlyHidden[id] = isHidden;
}
});
setSelected({ ...selected, ...currentlyHidden });
// Clear all hidden items
const newHidden = {};
Object.keys(hidden).forEach((id) => {
newHidden[id] = false;
});
setHidden(newHidden);
}
};
const matchData = (
menus.filter(({ _id }) => {
return !hidden[_id];
});
const getRowProps = (row) => {
return {
style: {
backgroundColor: selected[row.values.id] ? "lightgrey" : "white"
}
};
};
const data = [
{
Header: "id",
accessor: (row) => row._id
},
{
Header: "Name",
accessor: (row) => (
<Link to={{ pathname: `/menu/${menuId}/${row._id}` }}>{row.name}</Link>
)
},
{
Header: "Description",
//check current row is in hidden rows or not
accessor: (row) => row.description
},
{
Header: "Status",
accessor: (row) => row.status
},
{
Header: "Dishes",
//check current row is in hidden rows or not
accessor: (row) => row.dishes,
id: "dishes",
Cell: ({ value }) => value && Object.values(value[0]).join(", ")
},
{
Header: "Show",
accessor: (row) => (
<Toggle
value={selected[row._id]}
onChange={rowSelectHandler(row._id)}
/>
)
}
];
const initialState = {
sortBy: [
{ desc: false, id: "id" },
{ desc: false, id: "description" }
],
hiddenColumns: ["dishes", "id"]
};
return (
<div>
<button type="button" onClick={handleClick}>
{buttonMode === "cancel" ? "Cancel" : "Apply"}
</button>
<Table
data={matchData}
columns={data}
initialState={initialState}
withCellBorder
withRowBorder
withSorting
withPagination
rowProps={getRowProps}
/>
</div>
);
}
Here my json from my api for menuId:1:
[
{
"menuId": 1,
"_id": "123ml66",
"name": "Pea Soup",
"description": "Creamy pea soup topped with melted cheese and sourdough croutons.",
"dishes": [
{
"meat": "N/A",
"vegetables": "pea"
}
],
"taste": "Good",
"comments": "3/4",
"price": "Low",
"availability": 0,
"trust": 1,
"status": "Pending",
"apply": 1
},
//...other data
]
Here my CodeSandbox
Here a picture to get the idea:
Here's the second solution I proposed in the comment:
// Setting up toggle button state
const [showGood, setShowGood] = useState(false);
const [menus, setMenus] = useState([]);
// Simulate fetch data from API
useEffect(() => {
async function fetchData() {
// After fetching data with axios or fetch api
// We process the data
const goodMenus = dataFromAPI.filter((i) => i.taste === "Good");
const restOfMenus = dataFromAPI.filter((i) => i.taste !== "Good");
// Combine two arrays into one using spread operator
// Put the good ones to the front of the array
setMenus([...goodMenus, ...restOfMenus]);
}
fetchData();
}, []);
return (
<div>
// Create a checkbox (you can change it to a toggle button)
<input type="checkbox" onChange={() => setShowGood(!showGood)} />
// Conditionally pass in menu data based on the value of toggle button "showGood"
<Table
data={showGood ? menus : menus.filter((i) => i.taste !== "Good")}
/>
</div>
);
On ternary operator and filter function:
showGood ? menus : menus.filter((i) => i.taste !== "Good")
If button is checked, then showGood's value is true, and all data is passed down to the table, but the good ones will be displayed first, since we have processed it right after the data is fetched, otherwise, the menus that doesn't have good status is shown to the UI.
See sandbox for the simple demo.

Patch multiple id's with one request with React Query

I have a very basic prototype of app that allows to book a seat. User selects the seat/seats, clicks book, patch request with available: false is sent to the fake api (json-server) with React Query, library invalidates the request and immediately shows response from the server.
Database structure looks like this:
{
"hallA": [
{
"id": 1,
"seat": 1,
"available": false
},
{
"id": 2,
"seat": 2,
"available": true
},
{
"id": 3,
"seat": 3,
"available": false
}
]
}
and the logic for selecting, booking seats looks like this:
const App = () => {
const { data, isLoading } = useGetHallLayout("hallA");
const [selected, setSelected] = useState<
{ id: number; seat: number; available: boolean }[]
>([]);
const handleSelect = useCallback(
(seat: { id: number; seat: number; available: boolean }) => {
const itemIdx = selected.findIndex((element) => element.id === seat.id);
if (itemIdx === -1) {
setSelected((prevState) => [
...prevState,
{ id: seat.id, seat: seat.seat, available: !seat.available },
]);
} else {
setSelected((prevState) =>
prevState.filter((element) => element.id !== seat.id)
);
}
},
[selected]
);
const takeSeat = useTakeSeat({
onSuccess: () => {
useGetHallLayout.invalidate();
},
});
const sendRequest = useCallback(() => {
selected.forEach((item) =>
takeSeat.mutateAsync({ id: item.id, hall: "hallA" })
);
setSelected([]);
}, [selected, takeSeat]);
return (
<>
{!isLoading && (
<ConcertHall
layout={data}
onSeatSelect={handleSelect}
activeSeats={selected}
/>
)}
<button disabled={isLoading} onClick={sendRequest}>
Take selected
</button>
</>
);
};
Queries look like this:
export const useGetHallLayout = (hall: string) => {
const { data, isLoading } = useQuery(["hall"], () =>
axios.get(`http://localhost:3000/${hall}`).then((res) => res.data)
);
return { data, isLoading };
};
export const useTakeSeat = (options?: UseMutationOptions<unknown, any, any>) =>
useMutation(
(data: { hall: string; id: number }) =>
axios.patch(`http://localhost:3000/${data.hall}/${data.id}`, {
available: false,
}),
{
...options,
}
);
useGetHallLayout.invalidate = () => {
return queryClient.invalidateQueries("hall");
};
The problem of the above code is that I perform very expensive operation of updating each id in a for each loop (to available: false) and query invalidates it after each change not once all of them are updated.
The question is: is there any better way to do this taking into account the limitations of json-server? Any batch update instead of sending request to each and every id seperately? Maybe some changes in a logic?
Thanks in advance
You can certainly make one mutation that fires of multiple requests, and returns the result with Promise.all or Promise.allSettled. Something like:
useMutation((seats) => {
return Promise.allSettled(seats.map((seat) => axios.patch(...))
})
then, you would have one "lifecycle" (loading / error / success) for all queries together, and onSuccess will only be called once.
Another gotcha I'm seeing is that you'd really want the hall string to be part of the query key:
- useQuery(["hall"], () =>
+ useQuery(["hall", hall], () =>

react error "Expected an assignment or function call and instead saw an expression" under the map iteration

I am trying to move keys and values in propsValue to Users using immer.
Furthermore, there are 2 buttons.
"convert them" moves propsValue into a new Array.
"print all" prints all key and value of the new Array.
In my theory, it should be worked perfectly and keys and values in propsValue were moved into
users.
However, it prints "Expected an assignment or function call ..." when the button clicked.
please share your kind advice to solve this problem.
import React from 'react';
import { useImmer } from "use-immer";
const UserImmer = () => {
const propsValue = [
{
value: "Name", key:"first_name"
},
{
value: "last Name", key:"last_name"
},
{
value: "E mail", key:"e_mail"
},
{
value: "Address", key: "address"
}
]
const [user, setUser] = useImmer({
name: '',
key: '',
})
const [users, setUsers] = useImmer([])
const onbuttonChange = () => {
//iteration begins
propsValue.map(data => (
//moves propsValue key, value into user
setUser(draftState => {
draftState.name = data.name,
draftState.key = data.key
}),
//push new array key and value into Users
setUsers(draftState => {
draftState.push(user);
}),
//initialize the array
setUser(draftState => {
draftState.name = "",
draftState.key = ""
})
) )
}
const onClickUser = () => {
users.map(temp => console.log(temp.name, " : " , temp.key));
}
return (
<div>
<button onClick={onClickUser}>print All</button>
<button onClick={onbuttonChange}>Convert them</button>
</div>
)
}
export default UserImmer;
found a solution. modified some comma and semi-colon
propsValue.map(data => (
//moves propsValue key, value into user
setUser(draft => {
draft.name = data.name;
draft.key = data.key;
}),
setUsers(draft => {
draft.push(user);
}),
setUser(draft => {
draft.name ="";
draft.key = "";
})
) )

Update item in state onClick ReactJS

So, I have class Comopnent :
state = {
tokens: [
{
name: "first",
value: 3
},
{
name: "second",
value: 2
},
{
name: "third",
value: 4
}
]
}
handleClick = (name, id) => {
const newState = this.state.tokens.map((token => {
console.log(token.name)
}))
}
render() {
const token = this.state.tokens;
const tokenList = token.map(t => {
return (
<div value={t.name} onClick={() => this.handleClick(t.name, t.value)}>
<img src=""/>
</div>
)
})
What i need to do - after click - to subtract 1 from value clicked token.
So - ex. after click on "First" token i want his value equal 2.
So far I've done just as much as the above.
I do not know how to go about it, i am new in ReactJS, so thanks for help in advance!
You'll have to find in your state in tokens array the object which has the same name as the argument passed in the onclick handler. Then you will have to change it's value - decrement it (value--) but you have to be aware that you can't mutate the state.
handleClick = name => () => {
const { tokens } = this.state;
const clickedToken = tokens.find(token => token.name === name);
clickedToken.value--;
const clickedTokenIndex = tokens.indexOf(clickedToken);
const newTokens = [
...tokens.slice(0, clickedTokenIndex),
clickedToken,
...tokens.slice(clickedTokenIndex + 1)
];
this.setState({ tokens: newTokens });
};
Codesandbox link: https://codesandbox.io/s/92yz34x97w
First, some things are wrong with your code.
1- You have an array of tokens, then you're mapping the list, but you don't have a key to index, this will cause weird behaviors, I improve your tokens list with keys now.
2.- You can handle the click and change the state of the tokens list, this will trigger a reload of the component.
state = {
tokens: [
{
name: "first",
value: 3,
id: 1
},
{
name: "second",
value: 2,
id: 2
},
{
name: "third",
value: 4,
id: 3
}
]
}
handleClick = (name, id) => {
const { tokens} = this.state;
const newState = tokens.map((token => {
if(token.id === id) {
token.value--;
}
return token;
}))
}
render() {
const token = this.state.tokens;
const tokenList = token.map(t => {
return (
<div key={t.key} value={t.name} onClick={() => this.handleClick(t.name, t.value, t.key)}>
<img src=""/>
</div>
)
})

Resources