Empty Object on React useEffect - javascript

In my project I have the component ExportSearchResultCSV. Inside this component the nested component CSVLink exports a CSV File.
const ExportSearchResultCSV = ({ ...props }) => {
const { results, filters, parseResults, justify = 'justify-end', fileName = "schede_sicurezza" } = props;
const [newResults, setNewResults] = useState();
const [newFilters, setNewFilters] = useState();
const [data, setData] = useState([]);
const [isLoading, setIsLoading] = useState(true)
const [headers, setHeaders] = useState([])
const prepareResults = () => {
let newResults = [];
if (results.length > 1) {
results.map(item => {
newResults.push(parseResults(item));
}); return newResults;
}
}
const createData = () => {
let final = [];
newResults && newResults?.map((result, index) => {
let _item = {};
newFilters.forEach(filter => {
_item[filter.filter] = result[filter.filter];
});
final.push(_item);
});
return final;
}
console.log(createData())
const createHeaders = () => {
let headers = [];
newFilters && newFilters.forEach(item => {
headers.push({ label: item.header, key: item.filter })
});
return headers;
}
React.useEffect(() => {
setNewFilters(filters);
setNewResults(prepareResults());
setData(createData());
setHeaders(createHeaders());
}, [results, filters])
return (
<div className={`flex ${justify} h-10`} title={"Esporta come CSV"}>
{results.length > 0 &&
<CSVLink data={createData()}
headers={headers}
filename={fileName}
separator={";"}
onClick={async () => {
await setNewFilters(filters);
await setNewResults(prepareResults());
await setData(createData());
await setHeaders(createHeaders());
}}>
<RoundButton icon={<FaFileCsv size={23} />} onClick={() => { }} />
</CSVLink>}
</div >
)
}
export default ExportSearchResultCSV;
The problem I am facing is the CSV file which is empty. When I log createData() function the result is initially and empty object and then it gets filled with the data. The CSV is properly exported when I edit this component and the page is refreshed. I tried passing createData() instead of data to the onClick event but it didn't fix the problem. Why is createData() returning an empty object first? What am I missing?

You call console.log(createData()) in your functional component upon the very first render. And I assume, upon the very first render, newFilters is not containing anything yet, because you initialize it like so const [newFilters, setNewFilters] = useState();.
That is why your first result of createData() is an empty object(?). When you execute the onClick(), you also call await setNewFilters(filters); which fills newFilters and createData() can work with something.
You might be missunderstanding useEffect(). Passing something to React.useEffect() like you do
React.useEffect(() => {
setNewFilters(filters);
setNewResults(prepareResults());
setData(createData());
setHeaders(createHeaders());
}, [results, filters]) <-- look here
means that useEffect() is only called, when results or filters change. Thus, it gets no executed upon initial render.

Related

Requiring a Hook to update on state change

So this is a component I have in React JS
const EnterSetData = ({ user_card_set }) => {
const [cardlist, setCardlist] = useState({ data: [] });
let setlist= useGetSetDB();
//Save and reset form
const sendSets = () => {
const dupes = findDuplicates(setlist, cardlist);
if (dupes.length > 0) {
saveSets(setlist)
setCardlist({ data: [] });
} else {
handleDupes(dupes);
}
};
useEffect(() => {
setCardlist(card_set);
}, [card_set]);
return (
<>
{cardlist.data.map((card, key) => (
<a> .......</a>))}
<button
type="button"
onClick={sendSets}
>
Save Cards
</button>
</div>
The main issue is while data is saved, there is a major bug. useGetSetDB() is used to fetch the entire set list from an API. After saving, I use setCardlist to update the component state. It is here that I notice that setlist does not change. The new entries are not there and this causes findDuplicates to fail. My DB checks for duplicates, and thus console.logs will show that the inserts failed. How do I force useGetSetDB to update on every state change.This is useGetSetDB
const useGetSetDB = () => {
const[state,setState] = useState([]);
const fetchData = async () => {
const data = await fetch("/sets");
const res = await data.json();
return res;
};
useEffect(()=>{
fetchData().then(response => {setState(response)})
},[])
return state
}
export default useGetSetDB;
You can pass cardlist to useGetSetDB like below
const [cardlist, setCardlist] = useState({ data: [] });
let setlist = useGetSetDB(cardlist); //whenever cardlist gets updated, we trigger side-effect again
You also need to modify useGetSetDB to align with state change
const useGetSetDB = (cardlist) => {
const[state,setState] = useState([]);
const fetchData = async () => {
const data = await fetch("/sets");
const res = await data.json();
return res;
};
useEffect(()=>{
fetchData().then(response => {setState(response)})
}, [cardlist]) //add `cardlist` to dependency list
return state
}
export default useGetSetDB;

find method returns undefined

I make find method on users array but selectedUserId = undefined
How can I execute this method necessarily after the async function so that the selectedUserId is not undefined?
function App() {
const [selectedUserId, setSelectedUserId] = useState(null)
const [users,setUsers] = useState([])
const [loading, setLoading] = useState(false)
console.log(users)
useAsyncEffect(async () => {
setLoading(true)
const res = await fetch('https://jsonplaceholder.typicode.com/users')
const data = await res.json()
setUsers(data)
setLoading(false)
}, [])
const selectedUser = users.find(u => u.id === selectedUserId)
console.log(selectedUser)
const onUserClick = (userId) => {
setSelectedUserId(userId)
}
if(loading) {
return <img src={preloader} alt="preloader"/>
}
return (
<div>
{ !selectedUser ? <ListUsers users={users} onUserClick={onUserClick} /> : <Profile user=
{selectedUser} />
}
</div>
)
}
useEffect() hook will be called after rendering / component update.
so, on First time the users value will be empty array.
so, the selectedUser value will be undefined.
Wrap it in another useEffect will work.

how to display my list from store at initialization with easy-peasy?

I want to get a list of churches from the store at initialization but i can't. The log get my initial array and the new one but doesn't display. Log below:
log
here is my model:
const churchModel = {
items: [],
// ACTIONS
setAllChurches: action((state, payload) => {
state.items = payload;
}),
getInitialChurches: thunk(async (actions) => {
const { data } = await axios.post(
'http://localhost:3000/api/geo/closeto?latlong=2.3522219 48.856614&distance=10000'
);
let array = [];
const resData = data.map(async (index) => {
const res = await axios.get(`http://localhost:3000/api/institutions/all?idInstitution=${index.idInstitution}`);
array.push(res.data[0]);
});
actions.setAllChurches(array);
})
}
and my component:
const ChurchList = () => {
const classes = useStyles();
const setInitialChurches = useStoreActions(action => action.churches.getInitialChurches);
const churches = useStoreState(state => state.churches.items);
const [activeItem, setActiveItem] = React.useState(null);
useEffect(() => {
setInitialChurches()
}, []);
return (
<div className={classes.root} style={{marginTop: '20px',}}>
{ churches.map( (church) => (
<ChurchItem
key={ church.idInstitution }
church={ church }
setActiveItem={setActiveItem}
activeItem={activeItem}
/>)
), console.log(churches)}
</div>
)
};
export default ChurchList;
I tried a useEffect but nothing true. Could you help me please ?
that is not a good location to put console.log in, either put it outside the component render, inside the map or on a useEffect.
You can achieve it by using useEffect and passing churches on the array.
useEffect(() => {
// this will log everytime churches changes / initialized churches
console.log(churches);
}, [churches]);

Multiple rendering problem and how can I use the useEffect here

Filtering data by using this function, if I am calling this function in useEffect than its pushes to search results and not working well.
const AdvanceSearch = (props) => {
const [region, setRegion] = useState("");
const [searchStuhl, setSearchStuhl] = useState("");
const filterData = (async ()=> {
const filtereddata = await props.data.filter((item) => {
return (
item.region.toLowerCase().includes(region.toLowerCase())
&& item.stuhl.toLowerCase().includes(searchStuhl.toLowerCase())
)}
) await props.history.push({
pathname: '/searchResults/',
state:
{
data:filtereddata
}
})
})
//If the props. history.push is pass here instead of the above function then its sending the empty array and not the filtered data
const handleSubmit = async (e) => {
e.preventDefault()
await filterData();
}
when you are changing the navigation URL with some data and there is multiple rendering then the following problem would be there.
Check your route configuration for the path. is it configured to hold the changed path: in this scenario, you get fluctuated UI or we can say multiple renders
yes you can use useEffect hooks to change the path and set the data here is the peace of code. here whenever your props.data will be changed filteredData will run and it will return the value when data will be available.
const filteredData = useCallback(() => {
if(props.data){
const filteredData = props.data.filter((item) => (
item.region.toLowerCase().includes(region.toLowerCase())
&&item.stuhl.toLowerCase().includes(searchStuhl.toLowerCase())
));
return filteredData
}
},
[props && props.data]);
useEffect(()=> {
const data = filteredData();
if(data){
props.history.push({
pathname:'/search-results',
state:{data}
});
}
},[filteredData])
Try to remove async / await from the function. You don't need them to filter an array.

Item list fetched with useEffect hook renders only after it is filtered with useEffect

My problem is that I am trying to show data (item list) on the first render using useEffect, with useState hooks, and after it is rendered I want to filter these mapped items.
At the moment when I get to items page, I get zero items rendered on-page. And they are rendered only after I filter them (either by search filter or by choosing the desired color from the select dropdown).
Below I show my code:
...
var [searchTerm, setSearchTerm] = useState<any>("");
var [searchResults, setSearchResults] = useState<any>([]);
const handleInput = (e: any) => {
console.log(e.target.value);
setSearchTerm(e.target.value);
};
var [pickedColor, setPickedColor] = useState<any>("");
var [colorPickResults, setColorPickResults] = useState<any>([]);
const handleColorChange = (e: any) => {
console.log(e.target.value);
setPickedColor(e.target.value);
};
var [items, setItems] = useState<any>([]);
useEffect(() => {
var message: string = "";
var fetchItems = () => {
axios
.get("/api/products")
.then((res: any) => {
console.log("res :", res);
setItems(res.data.data[0].products);
console.log("res.data.data[0].products :", res.data.data[0].products);
if (res.status === 200) {
message = "Products successfuly fetched.";
console.log(message);
} else {
message = "Something went wrong. Please try again later.";
console.log(message);
}
})
.catch((err: any) => console.log(err));
};
fetchItems();
const resultsFromSearch = items.filter((item: any) =>
item.title.toLowerCase().includes(searchTerm)
);
setSearchResults(resultsFromSearch);
const resultsFromPickedColor = resultsFromSearch.filter((item: any) =>
item.color.toLowerCase().includes(pickedColor)
);
setColorPickResults(resultsFromPickedColor);
}, [searchTerm, pickedColor]);
var filteredItems = colorPickResults;
var itemList = filteredItems.map((item: any) => {
return (
<div key={item.id} style={{ width: "370px", padding: "1rem" }}>
...
I get successful message in the console, and item is successfully fetched as I mentioned above.
What I have tried:
1.Adding "items" to: [searchTerm, pickedColor, items] array, but then useEffect creates infinite loop when fetching items, and (probably) re-renders component at each fetch.
What can I change to get desired functionality mentioned at start of my post?
Should I add some other hooks like useContext,useMemo? Or maybe I can somehow separate fetching from filtering?
calling async function and trying access data it will return somewhere later will never work as you expect
fetchItems();
const resultsFromSearch = items.filter((item: any) =>
item.title.toLowerCase().includes(searchTerm)
);
You should either add that code to .then inside fetchItems or probably have separate useEffect:
useEffect(() => {
const resultsFromSearch = items.filter((item: any) =>
item.title.toLowerCase().includes(searchTerm)
);
setSearchResults(resultsFromSearch);
}, [items])
To me second option is more declarative and more flexible, but it's up to you

Categories

Resources