How to add more objects to Array Object state using setState? - javascript

This is my initial data
const data = [
{ id: '1', name: '1' },
{ id: '2', name: '1' },
{ id: '3', name: '2' },
]
I want to loop over and:
Where it has name 1 add that object to stateOne
Where it has name 2 add that object to stateTwo
End goal both states needs to have Array of Objects inside:
stateOne needs to look like
[
{ id: '1', name: '1' },
{ id: '2', name: '1' }
]
stateTwo needs to look like
[
{ id: '3', name: '2' },
]
This is what i've tried:
const data = [
{ id: '1', name: '1' },
{ id: '2', name: '1' },
{ id: '3', name: '2' },
]
const Testing = () => {
const [stateOne, setStateOne] = useState([])
const [stateTwo, setStateTwo] = useState([])
useEffect(() => {
data.forEach((e) => {
if (e.name === '1') {
console.log('e', e)
setStateOne((prevSate) => ({ ...prevSate, e }))
}
// if (e.name === '2') {
// setStateTwo(e)
// }
})
}, [])
console.log('stateOne', stateOne)
}

I'd prefer sending data as a prop to that component
You can achieve what you need by
const data = [
{ id: '1', name: '1' },
{ id: '2', name: '1' },
{ id: '3', name: '2' },
]
export default function Testing() {
const [stateOne, setStateOne] = useState([])
const [stateTwo, setStateTwo] = useState([])
useEffect(() => {
setStateOne(data.filter(e => e.name === "1"))
setStateTwo(data.filter(e => e.name === "2"))
console.log('stateOne', stateOne)
console.log('stateTwo', stateTwo)
}, [])
}

setState functions as an assignment. Like you would normally assign a variable. That means if you want to add something to an array, you need to include that array in the assignment.
Something like this:
if (e.name === '1') {
console.log('e', e)
setStateOne([...stateOne, e])
}
if (e.name === '2') {
setStateTwo([...stateTwo, e])
}

If you don't want to use filter twice for whatever reason, You can create temporary array for each one and manipulate them then update each state respectively like so:
const [stateOne, setStateOne] = useState([]);
const [stateTwo, setStateTwo] = useState([]);
useEffect(() => {
const tempArr1 = [];
const tempArr2 = [];
data.forEach((item) => {
if (item.name === "1") {
tempArr1.push(item);
} else if (item.name === "2") {
tempArr2.push(item);
}
});
setStateOne(tempArr1);
setStateTwo(tempArr2);
}, []);
console.log(stateOne);
console.log(stateTwo);
The problem with what you're doing is you're updating the state each time you find a match which will cause a lot of unnecessary re-renders.

You've said that data comes from some API you're querying. If so, filter the data once you get it. You can do that in a couple of ways.
With two calls to filter:
const Testing = () => {
const [stateOne, setStateOne] = useState([]);
const [stateTwo, setStateTwo] = useState([]);
useEffect(() => {
let cancelled = false;
getTheData(data => {
if (cancelled) {
return;
}
setStateOne(data.filter(({name}) => name === "1"));
setStateTwo(data.filter(({name}) => name === "2"));
};
return () => {
// So we don't try to set setate on an unmounted component
cancelled = true;
};
}, []);
// ...use `dataOne` and `dataTwo` here...
};
Or if you don't want to make two passes through the data, a single loop:
const Testing = () => {
const [stateOne, setStateOne] = useState([]);
const [stateTwo, setStateTwo] = useState([]);
useEffect(() => {
let cancelled = false;
getTheData(data => {
if (cancelled) {
return;
}
const stateOne = [];
const stateTwo = [];
for (const entry of data) {
switch (entry.name) {
case "1":
stateOne.push(entry);
break;
case "2": // or default if you want all non-1s in `stateTwo`
stateTwo.push(entry);
break;
}
}
setStateOne(stateOne);
setStateTwo(stateTwo);
};
return () => {
// So we don't try to set setate on an unmounted component
cancelled = true;
};
}, []);
// ...use `dataOne` and `dataTwo` here...
};

const data = [
{ id: "1", name: "1" },
{ id: "2", name: "1" },
{ id: "3", name: "2" }
];
const App = () => {
const newdata = useState(data);
const [stateOne, setStateOne] = useState([]);
const [stateTwo, setStateTwo] = useState([]);
const Filter = () => {
let listOne = [];
let listTwo = [];
newdata[0].map((it) => {
if (it.name === "1"){
listOne.push(it);
}
else if(it.name === "2"){
listTwo.push(it)
}
});
setStateOne(listOne);
setStateTwo(listTwo);
};
useEffect(() => {
Filter();
}, []);
console.log("stateOne", stateOne)
console.log("stateTwo", stateTwo)
return (
// your code
)
};

Related

RTK query useQuery invalidateTags on infinity scroll

I have this function that basically helps me implement infinite scroll everywhere. Still, I faced a problem where when I invalidate a tag related to an endless scroll tag it doesn't update the needed portion because of the offset and limit parameters.
The way I provide tags:
providesTags: (item) => item?.result
? [...item.result.departures.map(({ ID }) => ({
type: 'Departures',
id: ID,
})),
{ type: 'Departures', id: 'LIST' },
]
: [{ type: 'TransitDepartures', id: 'LIST' }],
To Invalidate tags I use invalidatesTags
The function I described
export const isValidNotEmptyArray = (array) =>
!!(array && array?.length && array?.length > 0)
const useFetchQuery = (
useGetDataListQuery,
{ offset = 0, limit = 10, ...queryParameters },
filter = () => true,
) => {
const [localOffset, setLocalOffset] = useState(offset)
const [combinedData, setCombinedData] = useState([])
const [gotWiped, setGotWiped] = useState(0)
const queryResponse = useGetDataListQuery(
{
offset: localOffset,
limit,
...queryParameters,
},
)
const { data: fetchData = { result: [], total: 0 } } = queryResponse || {}
const total = useMemo(() => fetchData.total, [fetchData])
useEffect(() => {
const value = departure ? fetchData.result.departures : fetchData.result
if (isValidNotEmptyArray(value)) {
setGotWiped(0)
if (localOffset === 0 || !localOffset) {
setCombinedData(value)
} else {
setCombinedData((previousData) => [...previousData, ...value])
}
} else if (gotWiped === 0) {
setGotWiped(1)
}
}, [fetchData])
useEffect(() => {
if (gotWiped) {
setCombinedData([])
}
}, [gotWiped])
const refresh = () => {
setLocalOffset((prev) => (prev === 0 ? null : 0))
setCombinedData([])
}
const loadMore = () => {
if (combinedData.length < total) {
setLocalOffset(combinedData.length)
}
}
return {
data: useMemo(() => combinedData.filter(filter), [combinedData, filter]),
offset: localOffset,
total:
combinedData.length > combinedData.filter(filter).length
? combinedData.filter(filter).length
: total,
loadMore,
refresh,
isLoading: queryResponse?.isLoading,
isFetching: queryResponse?.isFetching,
}
}

check for null when destructuring data with useSelector

How can I check for null when destructuring my data:
const {
vehicles: {
data: { reminderVehicles },
},
} = useSelector((state) => state);
The below code gives me
null is not an object, evaluating vehicles.data.reminderVehicles
document.getElementById("VehicleRegistration").value = "${
(prepopVehicleReg && reminderVehicles[0]?.Registration) || ""
}";
You can always assign a default value to it.
const myData = {
one: 1,
two: 2,
nested: {
three: 1
}
};
const {one = null} = myData;
const {five = null} = myData;
const {nested: {three}} = myData;
const {nested: {six = 'Default'}} = myData;
console.log({
one,
three,
five,
six
});
In your case, it should be (assuming reminderVehicles is an array)
const {
vehicles: {
data: {
reminderVehicles
} = {
reminderVehicles: []
}
} = {}
} = useSelector((state) => state);
But this is not readable and looks very complicated

React - State is not updating when it is supposed to, why is react doing this? (not retaining)

Hi so I'm trying to grab some json from an api and then populate a table, pretty simple stuff.
What's happening is that I can see the "tableData" state being updated as each new row comes in, I'm also logging every time "tableData" is updated, yet maybe .5 seconds after its all done my "tableData" is empty again (check console screenshots)
const [bigChartData, setbigChartData] = React.useState("data1");
const [tableData, setTableData] = React.useState([]);
const setBgChartData = (name) => {
setbigChartData(name);
};
const getData = () => {
axios.get("URL")
.then(res => {
const data = res.data.items.forEach(item => {
setTableData(oldData => [...oldData, {
data: [
{ text: item.title },
{ text: "asd" + item.url },
{ text: "some links..." }
]
}]);
});
})
.catch(err => console.log(err));
setTimeout(function () {
console.log(tableData);
}, 3000);
}
useEffect(() => {
getData();
}, []);
useEffect(() => {
console.log("Table data updated:");
console.log(tableData);
}, [tableData]);
I think you should not iterate through each row inside getData() method instead try following code
const getData = () => {
axios.get("URL")
.then(res => {
const data = res.data.items.map(item => {
return{
data: [
{ text: item.title },
{ text: "asd" + item.url },
{ text: "some links..." }
]
};
});
setTableData(data)
}).catch(err => console.log(err));
}
or if you have already some data in tableData then
setTableData([...tableData, data])

useCallback function is not a function while using unit test Jest

This is my test code snippet but it throws an exception, TypeError: componentInstance.loadLoanApplication is not a function :
it('should render the SubmittedLoan', () => {
const loanData = {
data: {
id: 1,
};
const div = document.createElement('div');
const wrapper = mount(
<AppProviders>
<MemoryRouter initialEntries={['/review/153']}>
<SubmittedLoan
match={{ params: { loanId: 1, step: 1 } }}
history={{
location: { state: { from: 'register' } },
push() {},
}}
/>
</MemoryRouter>
</AppProviders>,
div,
);
const componentInstance = wrapper
.find(SubmittedLoan)
.children()
.first()
.children()
.first()
.instance();
const loanApplication = {
id: 1,
steps_data: [
{ slug: 'step_1', title: 'Step 1' },
{ slug: 'step_2', title: 'Step 2' },
],
status: ApiCaptiq.STATUS_SUBMITTED,
};
expect(wrapper.find(SubmittedLoan).length).toBe(1);
componentInstance.loadLoanApplication(1, 1);
componentInstance.onLoadLoanApplication(loanData);
componentInstance.onLoadFail();
componentInstance.setState({
formData: [{ item: 'value' }, { item2: 'value2' }],
activeStep: 1,
loanApplication,
});
componentInstance.handleSnackbarClose(new Event('click'), '');
componentInstance.setState({ activeStep: 3 });
});
Then my Component which uses memo is as follows :
export const SubmittedLoan = memo(() => {
const [loanApplication, setLoanApplication] = useState<LoanApplication | null>(null);
const [message, setMessage] = useState({
message: '',
open: false,
messageType: '',
});
const authContext = useContext(AuthContext);
const customerContext = useCustomerData();
const params = useParams();
const history = useHistory();
const classes = useStyles();
const { loanId } = params;
const onLoadFail = useCallback(() => {
setMessage({
message: 'Die verfügbaren Darlehensarten können nicht aufgelistet werden',
open: true,
messageType: 'error',
});
}, []);
const onLoadLoanApplication = useCallback(
(response: AxiosResponse) => {
setTemplateSettings(response, authContext);
if (
response.data.status === ApiCaptiq.STATUS_STARTING ||
response.data.status === ApiCaptiq.STATUS_IN_PROGRESS ||
response.data.status === ApiCaptiq.STATUS_PRE_WAITING
) {
history.push(`/view/${loanId}`);
} else {
setLoanApplication(response.data);
}
},
[loanId, authContext, history],
);
const loadLoanApplication = useCallback(
async (loan_id: number) => {
try {
const response = await request.get(`${ApiCaptiq.LOAN_APPLICATION_URL}${loan_id}/`);
const { fetchCustomerProfile } = customerContext;
await fetchCustomerProfile(response.data.customer_profile_id);
onLoadLoanApplication(response);
} catch (err) {
onLoadFail();
}
},
[customerContext, onLoadLoanApplication, onLoadFail],
);
...
What could be the possible reason for this
The functions you are defining inside the component, are not just available on the component instance. In fact, there is not way to call them. You can test only by mocking the fetch calls they are doing.
If you really need callable functions in your component (you should try to avoid these..), you could use this: https://reactjs.org/docs/hooks-reference.html#useimperativehandle
Perhaps better would be to extract this data loading logic elsewhere and test it separately.

Javascript - How to push data inside a map function?

I've got the following data structure stored in a useState hook.
const [data, setData] = useState([
{
id: uniqid(),
title: "",
content:[
id: uniqid(),
title: "",
]
},
{
id: uniqid(),
title: "",
content:[
id: uniqid(),
title: "",
],
}
])
I've got a button where the user can add something to the content array, and I'm calling handleReport as below -
const handleAddReport = uniqueID =>{
const object = {
id: uniqid(),
title:"",
}
const formData = [...data];
formData.map(section=>{
section.content.map(report=>{
if(report.id === uniqueID){
section.content.push(object);
};
});
});
setForm(formData);
}
However, this isn't changing the form data at all. I'm not exactly sure how I could get it to work, any help would be appreciated! Thanks
you are not returning anything from the map.
const handleAddReport = uniqueID =>{
const object = {
id: uniqid(),
title:"",
}
const formData = [...data];
const newData = formData.map(section=> {
if(section.id === uniqueID){
section.content.push(object);
}
return section;
});
setForm(newData);
}
But instead of comparing the unqiueId another approach would be to pass the index of your data array. So that we can avoid map .
const handleAddReport = dataIndex =>{
const object = {
id: uniqid(),
title:"",
}
// deep clone the data
const clonedData = JSON.parse(JSON.stringify(data));
// since the data is cloned you can mutate it directly
clonedData[dataIndex].content.push(object);
setForm(clonedData)
}
Try this way
const onAddReport = (uniqid: number) => {
const obj = {
id: 3,
title: ''
};
const formData = [...data];
const mappedData = formData.map((data) => data.id === uniqid ? ({ ...data, content: [...data.content, obj] }) : data)
setData(mappedData);
}

Categories