Filtering objects after API call - javascript

I am having trouble showing the results after filtering the data received form API call, my guess is it is trying to filter the data before it has received it, and that is why it isn't showing. I have only managed to get the data by setting filterAnswers() to setTimeout function, but it loops continuously until it breaks. any idea how I can filter only the correct_answers and save it to state?
const [quiz, setQuiz] = React.useState([])
const [correctAnswers, setCorrectAnswers] = React.useState(filterAnswers())
React.useEffect(() => {
fetch("https://opentdb.com/api.php?amount=5&difficulty=medium&type=multiple")
.then(res => res.json())
.then(data => setQuiz(data.results))
}, [])
console.log(quiz)
function filterAnswers() {
let filter = []
quiz.forEach((question) => {
filter.push(decode(question.correct_answer));
})
return filter
console.log(correctAnswers)
};

my guess is it is trying to filter the data before it has received it
Indeed, the code is explicitly doing exactly that:
const [correctAnswers, setCorrectAnswers] = React.useState(filterAnswers())
You could invoke your "filter" logic when receiving the data, instead of immediately upon loading the component. First, change the function to no longer depend on state:
function filterAnswers(questions) {
let filter = [];
questions.forEach((question) => {
filter.push(decode(question.correct_answer));
})
return filter;
};
Then invoke it when you receive your data:
fetch("https://opentdb.com/api.php?amount=5&difficulty=medium&type=multiple")
.then(res => res.json())
.then(data => {
setQuiz(data.results);
setCorrectAnswers(filterAnswers(data.results));
})
Then of course the default initial state for correctAnswers would just be an empty array, exactly like it is for quiz:
const [correctAnswers, setCorrectAnswers] = React.useState([])
As an aside, it's worth noting that you're somewhat duplicating state here. Unless this filter/decode process is very expensive, it might be better to do this directly when calculating or display data rather than store it as state. Since this "correct answers" data is derived from the "quiz" data, by keeping both in state separately you also will need to keep that data synchronized. Changes to one need to be made in the other.
That might not become a problem with the functionality you're currently building, but it's something to keep in mind.

Related

I try to display api data but nothing comes out, not even an error on React

I'm slowly understanding the services and fetch with React but when I try to show something, it shows me absolutely nothing. I put the code in case someone can help me. Maybe I have to look at how to work with JSON, I don't know.
let datosApi = [];
const recogerDatos = () => {
let json = "https://jsonplaceholder.typicode.com/albums";
let miApi = "http://localhost/dsw/api/";
fetch(json)
.then(data => data.json())
.then(info => {
console.log(info);
this.datosApi = info;
})
}
function Services() {
return (
<>
{datosApi.map(datos => (
<p>{datos.title}</p>
))}
</>
);
}
export default Services;
JSON data appears in https://jsonplaceholder.typicode.com/albums
I think your example is missing something or you've not done it.
Basically there's a few things wrong:
recogerDatos is never being called
datosApi is not declared, and even if it was, it's not stateful, thus won't cause a re-render of your items.
I've created a working sandbox here that shows it working, and the code is below:
const [result, setResult] = useState([]);
const recogerDatos = () => {
let json = "https://jsonplaceholder.typicode.com/albums";
fetch(json)
.then((data) => data.json())
.then((info) => {
console.log(info);
setResult(info);
});
};
useEffect(() => {
recogerDatos();
}, []);
return (
<div className="App">
{result.length > 0 && result.map((datos) => <p>{datos.title}</p>)}
</div>
);
The recogerDatos function is called on page load (see useEffect), and the result is updated when the fetch is successful. This causes a re-render and the items are shown.
You are displaying data from your list
let datosApi = [];
However it does not seem like you are populating your list with data from the API since the method recogerDatos() is never being called.
From your code it seems like you're missing some core React patterns like state management and the components lifecycle. Since React re-renders components a lot you want to store things like fetched data into state to avoid them constantly reset to their initial value. And when you want to fetch the data you usually don't want to do it at each re-render (that would cause A LOT of fetching), instead you usually want to trigger it based on different events, for example when component (that will be used to show this data) is mounted. Such things are usually using the components lifecycle methods / useEffect hooks to ensure that they happen at the right point in time. I recommend you to go into React documentation and study the basics a bit more so you can understand the main concepts when you're coding, but here's a quick sandbox with an example of how you could get the desired effect in React:
https://codesandbox.io/s/beautiful-frost-on9wmn?file=/src/App.js

is a good approach defining 2 useEffect Hooks, see my use of case bellow

I tried to find the same case, but everyone is in a different situation.
I've created 2 useEffects in my component, one for initializing the component (1 execution) and another to fetch data when some state (filter) changes. (many executions)
const [filter1, setFilter1] = useState({});
const [filter2, setFilter2] = useState({});
//initialize filters, only once
useEffect(() => {
//here i fetch all the data to fill the filter components.
setFilter1(data1);
setFilter2(data2);
}, []);
//everytime my filters change.
useEffect(() => {
//here i fetch data using the filter as parameters.
}, [filter1,filter2]);
The code above is my last approach and works fine. Every time I change the filters the useEffect fetch the data and load the table with that data.
I show below the old approach that I took, and the one I didn't like.
const loadData = useCallback((filter1,filter2) => {
//here i fetch data using the filter as parameters.
}, [filter1,filter2]);
useEffect(() => {
//here i fetch all the data to fill the filter components.
setFilter1(data1);
setFilter2(data2);
loadData(filter1, filter2);
},[]);
and I used the loadData function in every filter change handler like:
const onFilter1Change = (val) => {
setFilter1(val);
loadData(val, filter2)
};
The old approach above brought me some issues regarding how to use the hooks properly and that's why I find myself asking you React experts, how to improve my coding in the best way.
The new approach works fine for me, tho I wonder if it's right to use 2 useEffect in this case, or if there is a better way to do it.
Cheers
Where data1 & data2 comes from?
If it's a prop, and you are using a useEffect only to initialize them, you don't need a useEffect for that:
const [filter1, setFilter1] = useState(data1);
const [filter2, setFilter2] = useState(data2);
useEffect(() => {
//here i fetch data using the filter as parameters.
}, [filter1, filter2]);

I want to access my state variable from one component to other

I have a react query which writes the state variable- follower, and I want to access this variable in other component to find its .length can someone tell me how do I do it
const ModalFollower = ({profile}) => {
const [follower,setFollower] = useState([])
const {
data: followerName,
isLoading: followerLoading,
isFetching: followerFetching
} = useQuery(["invitations", profile?.id], () => {
getFollowers(profile?.id).then((response) => {
if (response) {
setFollower(response);
}
});
});
return(
{
!followerLoading && (
follower.map((e) => {
return(<>
<p>{e.requested_profile.Userlink}</p>
</>}
)
}
)
I want to access the length of follower in some other component
There is no need to copy data from react-query to local state, because react-query is a full-blown state manager for server state. As long as you use the same query key, you will get data from its cache. This is best abstracted away in custom hooks.
Please be aware that with the default values, you will get a "background refetch" if a new component mount, so you will see two network requests if you use it twice. That might look confusing at first, but it is intended, as it is not react-query's primary goal to reduce network requests, but to keep your data on the screen as up-to-date as possible. So when a new component mounts that uses a query, you'll get the stale data from the cache immediately, and then a background refetch will be done. This procedure is called stale-while-revalidate.
The best way to customize this behaviour is to set the staleTime property to tell react-query how long your resource is "valid". For that time, you will only get data from the cache if available. I've written about this topic in my blog here: React Query as a State Manager.
React Query also provides selectors, so if your second component is only interested in the length, this is what my code would look like:
const useInvitations = (profile, select) =>
useQuery(
["invitations", profile?.id],
() => getFollowers(profile?.id),
{
enabled: !!profile?.id
select
}
)
Note that I also added the enabled property because apparently, profile can be undefined and you likely wouldn't want to start fetching without that id.
Now we can call this in our main component:
const ModalFollower = ({profile}) => {
const { data } = useInvitations(profile)
}
and data will contain the result once the promise resolves.
In another component where we only want the length, we can do:
const { data } = useInvitations(profile, invitations => invitations.length)
and data will be of type number and you will only be subscribed to length changes. This works similar to redux selectors.

How to ignore previous AJAX data return?

I wonder what a good way is to ignore earlier but delayed AJAX data return, when a newer AJAX call should be taken care of instead?
For example, if the first data fetch that started at 12:01:33 is delayed and returns at 12:01;39, and the second data fetch that started at 12:01:36 actually came back first, at 12:01:38. So the data returned at 12:01:39 should not be honored and should not be displayed onto the webpage and "cover up" the result of the second data fetch.
In some case, we might disable the Search or Submit button so that the user cannot click on the button again, but what about if there is a text input box, and the user typed "React" and press Enter, but he changed his mind and added "Redux" to the search box and pressed Enter again, and results returned by the "React" search should then be ignored (for both of the cases if this first AJAX returns either before or after the second AJAX), and only results returned by "React Redux" should be taken care of, to populate into the search result area down below.
In my past jobs, it seems that nobody actually took care of things like this. I can think of one way, which is:
let fetchTimeStampToHonor;
function getData() {
let currentFetchTimestamp = Date.now();
fetchTimeStampToHonor = currentFetchTimestamp;
fetch("...")
.then(res => res.json())
.then(data => {
if (currentFetchTimestamp === fetchTimeStampToHonor) { // honor it
}
});
}
so that if there is a newer call to getData(), then fetchTimeStampToHonor will get updated to the newest currentFetchTimestamp. The function passed to then is a closure and is able to access the local currentFetchTimestamp, and can be compared with fetchTimeStampToHonor. But the code above involves a global variable, which I can hide if I use a function to return getData(), and this way, I can hide fetchTimeStampToHonor inside of local scope and make it private. (but I think if this is inside of a module, it is private to the module and isn't such a big problem). Or it might be able to be made into some state if it is in ReactJS.
But are there some other better solutions?
You could produce a local cache of results. Use the search term as the key, then only show the relevant results. The code below is far from optimised and I would NOT use it as is, but I hope it illustrates the point.
I've used react in the example as it provides an easy way to show stateful changes but this concept could be done with raw JS as well.
const App = () => {
const [searchTerm, setSearchTerm] = useState('')
const [dataCache, setDataCache] = useState({})
useEffect(() => {
fetch(`search?term=${searchTerm}`)
.then((res) => res.json())
.then((data) => {
setDataCache((prev) => ({
...prev,
[searchTerm]: data,
}))
})
}, [searchTerm])
return (
<>
<input type="search" onChange={(e) => setSearchTerm(e.target.value)} />
<Results data={dataCache[searchTerm]} />
</>
)
}

Issues with asynchronous nature of redux in React?

I'm pulling data into one of my parent components and then using various filter statements which are based on user choices from select boxes. I'm then calling an action which simply stores that filtered data based on the users search into global state so that my child components can access them.
One of my child components is supposed to render the results but what is happening is the results being rendered are lagging one action behind. I've encountered similar issues when using set state and my solution then was to use a callback but I'm not exactly sure how to go about dealing with this issue in this situation with redux.
The wordpress.get is just named import of axios config.
componentDidMount = async () => {
const response = await wordpress.get(
"*********************/api/wp/v2/variants?per_page=100"
);
this.props.fetchData(response);
const data = []
response.data.forEach(ele => {
data.push(ele)
})
this.props.sendFilteredView(data);
};
handleChange = () => {
this.preBuiltFiltering();
};
I've left out pre-built filtering because its long and excessive, all it does is run the filter based on the users choices and then dispatches the this.props.sendFilteredView action with the filtered data set as the argument. The action just returns the payload.
I then am rendering the results of the filter in a child component by accessing the global state (I also tried just passing it directly through props, same issue).
It’s an async function, you’re using a callback after the forEach with data.
So you need to wait forEach been completed.
Try to use await before forEach.
componentDidMount = async () => {
const response = await wordpress.get(
"*********************/api/wp/v2/variants?per_page=100"
);
this.props.fetchData(response);
const data = []
await response.data.forEach(ele => {
data.push(ele)
})
this.props.sendFilteredView(data);
};
handleChange = () => {
this.preBuiltFiltering();
};

Categories

Resources