What is the best way to set a new filteredImages array with useState? - reactjs

What is the best way to set a new filteredImages array with useState?
I notice I’m doing return attachments.filter(attachment => { … twice, so wondered if I can do it once.
const [filteredImages, setFilteredImages] = useState(() => {
return attachments.filter(attachment => {
const {name, isImage} = attachment;
if (name) {
const newFileFormat = name.split('.').slice(-1)[0];
return isImage && newFileFormat !== 'tiff';
}
});
});
…
useEffect(() => {
setFilteredImages(() => {
return attachments.filter(attachment => {
const {name, isImage} = attachment;
if (name) {
const newFileFormat = name.split('.').slice(-1)[0];
return isImage && newFileFormat !== 'tiff';
}
});
});
}, [attachments, card.attachments]);
I passes an argument to setFilteredImages to set a new filteredImages array but it didn’t work.

Related

How to solve a situation when a component calls setState inside useEffect but the dependencies changes on every render?

I have this component:
const updateUrl = (url: string) => history.replaceState(null, '', url);
// TODO: Rename this one to account transactions ATT: #dmuneras
const AccountStatement: FC = () => {
const location = useLocation();
const navigate = useNavigate();
const { virtual_account_number: accountNumber, '*': transactionPath } =
useParams();
const [pagination, setPagination] = useState<PaginatorProps>();
const [goingToInvidualTransaction, setGoingToInvidualTransaction] =
useState<boolean>(false);
const SINGLE_TRANSACTION_PATH_PREFIX = 'transactions/';
// TODO: This one feels fragile, just respecting what I found, but, we could
// investigate if we can jsut rely on the normal routing. ATT. #dmuneras
const transactionId = transactionPath?.replace(
SINGLE_TRANSACTION_PATH_PREFIX,
''
);
const isFirst = useIsFirstRender();
useEffect(() => {
setGoingToInvidualTransaction(!!transactionId);
}, [isFirst]);
const {
state,
queryParams,
dispatch,
reset,
setCursorAfter,
setCursorBefore
} = useLocalState({
cursorAfter: transactionId,
includeCursor: !!transactionId
});
const {
filters,
queryParams: globalQueryParams,
setDateRange
} = useGlobalFilters();
useUpdateEffect(() => {
updateUrl(
`${location.pathname}?${prepareSearchParams(location.search, {
...queryParams,
...globalQueryParams
}).toString()}`
);
}, [transactionId, queryParams]);
useUpdateEffect(() => dispatch(reset()), [globalQueryParams]);
const account_number = accountNumber;
const requestParams = accountsStateToParams({
account_number,
...state,
...filters
});
const { data, isFetching, error, isSuccess } =
useFetchAccountStatementQuery(requestParams);
const virtualAccountTransactions = data && data.data ? data.data : [];
const nextPage = () => {
dispatch(setCursorAfter(data.meta.cursor_next));
};
const prevPage = () => {
dispatch(setCursorBefore(data.meta.cursor_prev));
};
const onRowClick = (_event: React.MouseEvent<HTMLElement>, rowData: any) => {
if (rowData.reference) {
if (rowData.id == transactionId) {
navigate('.');
} else {
const queryParams = prepareSearchParams('', {
reference: rowData.reference,
type: rowData.entry_type,
...globalQueryParams
});
navigate(
`${SINGLE_TRANSACTION_PATH_PREFIX}${rowData.id}?${queryParams}`
);
}
}
};
const checkIfDisabled = (rowData: TransactionData): boolean => {
return !rowData.reference;
};
useEffect(() => {
if (data?.meta) {
setPagination({
showPrev: data.meta.has_previous_page,
showNext: data.meta.has_next_page
});
}
}, [data?.meta]);
const showTransactionsTable: boolean =
Array.isArray(virtualAccountTransactions) && isSuccess && data?.data;
const onTransactionSourceLoaded = (
transactionSourceData: PayoutDetailData
) => {
const isIncludedInPage: boolean = virtualAccountTransactions.some(
(transaction: TransactionData) => {
if (transactionId) {
return transaction.id === parseInt(transactionId, 10);
}
return false;
}
);
if (!goingToInvidualTransaction || isIncludedInPage) {
return;
}
const fromDate = dayjs(transactionSourceData.timestamp);
const toDate = fromDate.clone().add(30, 'day');
setDateRange({
type: 'custom',
to: toDate.format(dateFormat),
from: fromDate.format(dateFormat)
});
setGoingToInvidualTransaction(false);
};
const fromDate = requestParams.created_after || dayjs().format('YYYY-MM-DD');
const toDate = requestParams.created_before || dayjs().format('YYYY-MM-DD');
const routes = [
{
index: true,
element: (
<BalanceWidget
virtualAccountNumber={account_number}
fromDate={fromDate}
toDate={toDate}
/>
)
},
{
path: `${SINGLE_TRANSACTION_PATH_PREFIX}:transaction_id`,
element: (
<TransactionDetails
onTransactionSourceLoaded={onTransactionSourceLoaded}
/>
)
}
];
return (........
I get this error: Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
The useEffect where the issue is, it is this one:
useEffect(() => {
if (data?.meta) {
setPagination({
showPrev: data.meta.has_previous_page,
showNext: data.meta.has_next_page
});
}
}, [data?.meta]);
Considering previous answers, would the solution be to make sure I return a new object each time? But I am not sure what would be the best approach. Any clues ?
did you want the useEffect to start every changes of 'data?.meta' ?
Without reading all the code, I believe the data.meta object changes on every render. There is a way to change the useEffect to narrow done its execution conditions:
useEffect(() => {
if (data?.meta) {
setPagination({
showPrev: data.meta.has_previous_page,
showNext: data.meta.has_next_page
});
}
}, [!data?.meta, data?.meta?.has_previous_page, data?.meta?.has_next_page]);
Please note the ! before data.?.meta which makes the hook test only for presence or absence of the object, since your code doesn't need more than that information.

React update existing object in array of objects

I have 1 input:
<input
type="text"
name={item.job_n}
defaultValue={item.azienda}
readOnly={isReadonly}
onChange={handleChange}
And onChange I'm adding the inputs value and name as an Object to an array of Objects in useState=([]), therefore my array looks like this: [{azienda:'', job_n:''}, {azienda:'', job_n:''}]
const [azienda, setAzienda] = useState([]);
const handleChange =(e)=>{
{
const azienda_name = e.target.value
const job_n = e.target.name
setAzienda((prevState) => [...prevState, {azienda: azienda_name, job_n: job_n}])
}
}
The code works fine and a new Object is added to my array each time an onchange event occurs. The issue I'm having is that if an object with job_n already exists in the array I'd need to update that object and not create a new object for each onchange.
I have changed the variables from italian to english so that everyone can understand them, and also changed some variables to follow the javascript naming conventions.
function handleChange(e) {
const businessName = e.target.value;
const jobName = e.target.name;
const jobIndex = business.findIndex(v => {
return v["jobName"] === jobName;
});
if (jobIndex === -1) {
setBusiness([...business, { businessName, jobName }]);
return;
}
const businessClone = [...business];
businessClone[jobIndex] = { businessName, jobName };
setBusiness(businessClone);
}
Change your code to,
setAzienda((prevState) => {
const jobExistIndex = prevState.findIndex(obj => obj.job_n === job_n)
const jobExist = prevState[jobExistIndex]
if (jobExist) {
const newState = [...prevState]
newState[jobExistIndex] = {...jobExist, job_n: job_n}
return newState
}
else {
const newState = [...prevState]
return [...newState, {azienda: azienda_name, job_n: job_n}]
}
const handleChange = e =>{
{
const azienda_name = e.target.value;
const job_n = e.target.name;
const existIndex = azienda.findIndex(p => p.job_n === job_n);
if(existIndex != -1)
{
azienda[existIndex].azienda_name = azienda_name
}
else
{
azienda.push({azienda_name : azienda_name, job_n: job_n})
}
setAzienda(azienda);
}

My useState hook is not updating itself and when i am trying to get data using filter, its not working

When I am trying to get data from an array using filter and find, it's not getting filtered also the _ids are the same when I cross-checked the array, also useState is also not updating
1. How should I filter one element from an array, Am I doing this right?
2. useState is not working, not updating data
I am getting every data from context (c1)
sd is returning array of single object, so to get one first index I am returning sd[0]
const ReadTemplate = (props) => {
const c1 = useContext(PostsContext);
const [first, myData] = useState({});
const first_load_func = () => {
const id = props.match.params.id;
const sd = c1.data.filter((c1) => id === c1._id);
const business_props = c1.business_data.filter((c1) => id === c1._id);
const startups_props = c1.startups_data.filter((c1) => id === c1._id);
const tech_props = c1.tech_data.filter((c1) => id === c1._id);
const sports_props = c1.sports_data.filter((c1) => id === c1._id);
if (sd) {
return sd[0];
} else if (business_props) {
return business_props[0];
} else if (startups_props) {
return startups_props[0];
} else if (tech_props) {
return tech_props[0];
} else if (sports_props) {
return sports_props[0];
} else {
return <MyAlert />;
}
};
const func = (data) => {
if (data) {
setTimeout(() => {
myData(data);
}, 1000);
console.log('ye first hai');
console.log(first._id);
console.log('ye data hai');
console.log(data);
} else {
console.log('No');
}
};
useEffect(() => {
first_load_func();
func(first_load_func());
}, [first]);
return (
<>
<PostDesign props={first} />
</>
);
};
export default ReadTemplate;
My guess from your code is that you should assign the filtered data when the component is rendered, not when first changes:
useEffect(() => {
func(first_load_func());
}, []);
It may be useful to convert ids toString() before comparing them:
const sd = c1.data.filter((c1) => id.toString() === c1._id.toString());

On each change check the objects if they have changes in React

So I'm trying to do something more advanced where I check for and set of old objects with new set of object arrays, and then put the ids that have change to the state.
So for example here is my code:
const [skillsSets, setSkillSets] = useState({...allSkills})
const [idOfChangedItem, setIdOfChangedItem] = useState([])
useEffect(() => {
skillsSets.map(skillSet = > {
if(checkForChange(skillSet, oldSkillObject) === true){
const newChanges = [...idOfChangedItems];
newChanges.push(skillSet.id);
}
})
}, [skillsSet])
Try to had the setState that you create:
const [skillsSets, setSkillSets] = useState({...allSkills})
const [idOfChangedItem, setIdOfChangedItem] = useState([])
useEffect(() => {
skillsSets.map(skillSet = > {
if(checkForChange(skillSet, oldSkillObject) === true){
const newChanges = [...idOfChangedItems];
newChanges.push(skillSet.id);
setIdOfChangedItem(newChanges);
}
})
}, [skillsSet])

Multiple filters with React Hook - Chaining function

Hello I have created a search bar with a multipl filter, it works but the functions are too dependent on each other. The problem here is that the functions are handling multiple cases.
would it be possible to lighten each function by chaining them and how ? I don't really get chaining method.
thanks
import React, { useState, useEffect } from "react";
import Search from "./Search";
import Anime from "./Anime";
import "./App.css";
const KIJAN_API_URL = "https://api.jikan.moe/v3/top/anime/1/upcoming";
const App = () => {
const [animes, setAnimes] = useState([]);
const [sortedAnimes, setSortedAnimes] = useState([]);
const [searchValue, setSearchValue] = useState("")
const [filterByType, setFilterByType] = useState("");
const [filterByYear, setFilterByYear] = useState("");
useEffect(() => {
fetch(KIJAN_API_URL)
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error("Something went wrong");
}
})
.then(jsonResponse => {
setAnimes(jsonResponse.top);
})
.catch(error => {
console.log(error);
});
}, []);
useEffect(() => {
const callFilterByType = result => {
if (filterByType === "") {
callFilterByYear(result);
console.log(result);
} else {
result = result.filter(anime => anime.type === filterByType);
callFilterByYear(result);
console.log(result);
}
};
const callFilterByYear = result => {
if (filterByYear === "") {
setSortedAnimes(result);
} else {
const regex = new RegExp(`${filterByYear}`, "gi");
result = result.filter(anime => regex.test(anime.start_date));
setSortedAnimes(result);
console.log(result);
}
};
if (searchValue === "") {
callFilterByType(animes);
} else {
const regex = new RegExp(`${searchValue}`, "gi");
console.log("search : ", searchValue);
const result = animes.filter(anime => regex.test(anime.title));
callFilterByType(result);
console.log(result);
}
}, [searchValue, animes, filterByType, filterByYear]);
return (
<div className="App">
<Search
searchValue={searchValue}
setSearchValue={setSearchValue}
filterByType={filterByType}
setFilterByType={setFilterByType}
filterByYear={filterByYear}
setFilterByYear={setFilterByYear}
/>
{sortedAnimes.length > 0 ? (
sortedAnimes.map((anime, index) => {
return <Anime key={index} anime={anime} />;
})
) : (
<span>Aucune correspondance</span>
)}
</div>
);
};
export default App;
SandBox Sample
You can do first round of simplification like this:
useEffect(() => {
let result = [...animes];
if(searchValue) {
const searchRegex = new RegExp(`${searchValue}`, "gi");
result = result.filter(anime => searchRegex.test(anime.title));
}
if(filterByType) {
result = result.filter(anime => anime.type === filterByType);
}
if(filterByYear) {
const yearRegex = new RegExp(`${filterByYear}`, "gi");
result = result.filter(anime => yearRegex.test(anime.start_date));
}
setSortedAnimes(result);
}, [searchValue, animes, filterByType, filterByYear]);
It can be reduced to more compact form, like:
useEffect(() => {
const searchRegex = searchValue && new RegExp(`${searchValue}`, "gi");
const yearRegex = filterByYear && new RegExp(`${filterByYear}`, "gi");
const result = animes.filter(anime =>
(!searchRegex || searchRegex.test(anime.title)) &&
(!filterByType || anime.type === filterByType)) &&
(!yearRegex || yearRegex.test(anime.start_date))
)
setSortedAnimes(result);
}, [searchValue, animes, filterByType, filterByYear]);
More idiomatic way would be use use momoisation hook. i.e. Remove sortedAnimes as state and
const sortedAnimes = useMemo(() => {
const searchRegex = searchValue && new RegExp(`${searchValue}`, "gi");
const yearRegex = filterByYear && new RegExp(`${filterByYear}`, "gi");
return animes.filter(anime =>
(!searchRegex || searchRegex.test(anime.title)) &&
(!filterByType || anime.type === filterByType)) &&
(!yearRegex || yearRegex.test(anime.start_date))
)
}, [searchValue, animes, filterByType, filterByYear]);
try this
if you are using filter method inside jsx then you try this method.
Let me brief it,
consider userInfo like an object containing fields like name, email, location etc. so, if you want your filter method to provide your search results based on these fields value then you can use something like this in jsx.
{userInfo.filter((user) => (
user.name.toLowerCase().includes(cloneSearchTerm)
||
user.email.toLowerCase().includes(cloneSearchTerm)
||
user.location.toLowerCase().includes(cloneSearchTerm)
)
).map((user, idx) => (
<div key={idx}>
<span>{user.name}</span>
<span>{user.email}</span>
<span>{user.location}</span>
</div>
))}

Resources