I am trying to fetch data from an API from an input. After providing the input and calling the data, I am getting two fetch call from same data. here is the code of input:
const [countryName, setCountryName] = useState<string>("");
const handleInputChange = (event: {
target: { value: React.SetStateAction<string> };
}) => {
setCountryName(event.target.value);
};
const onSubmit = () => {
navigate(`/country/${countryName}`);
};
<TextField
variant="standard"
placeholder="Enter country Name"
value={countryName}
onChange={handleInputChange}
/>
Here I am using the input to fetch the data:
const { name } = useParams();
const [loading, setLoading] = useState<boolean>(false);
const [country, setCountry] = useState<InitCountry>();
useEffect(() => {
const getCountry = async () => {
setLoading(true);
const response = await fetch(
`https://restcountries.com/v3.1/name/${name}`
);
const data = await response.json();
console.log(data);
setCountry(data.length > 1 ? data[2] : data[0]);
setLoading(false);
};
getCountry();
}, [name]);
What am I doing wrong here?
Your setup seems good, but the root cause is possibly from React.StrictMode
Strict mode canβt automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions: ...
This effect is only in development which means it won't happen in your production, but if you don't want intentionally double-invoking, you can remove it from your code.
Related
i've been solving this problem without any progress for the pas 2 hours or so, here is code:
export const useFetchAll = () => {
const [searchResult, setSearchResult] = useState([]);
const [loading, setLoading] = useState(false);
const [searchItem, setSearchItem] = useState("");
const [listToDisplay, setListToDisplay] = useState([]);
// const debouncedSearch = useDebounce(searchItem, 300);
const handleChange = (e) => {
setSearchItem(e.target.value);
if (searchItem === "") {
setListToDisplay([]);
} else {
setListToDisplay(
searchResult.filter((item) => {
return item.name.toLowerCase().includes(searchItem.toLowerCase());
})
);
}
console.log(searchItem);
};
useEffect(() => {
const searchRepo = async () => {
setLoading(true);
const { data } = await axios.get("https://api.github.com/repositories");
setSearchResult(data);
setLoading(false);
};
if (searchItem) searchRepo();
}, [searchItem]);
the problem is that when i enter characters in input and set state to event.target.value it doesn't pick up last character. here is an image:
enter image description here
BTW this is a custom hook, i return the onchange function here:
const HomePage = () => {
const { searchResult, loading, searchItem, handleChange, listToDisplay } =
useFetchAll();
and then pass it as a prop to a component like so:
<Stack spacing={2}>
<Search searchItem={searchItem} handleChange={handleChange} />
</Stack>
</Container>
any help? thanks in advance.
You are handling the searchItem and searchResult state variables as if their state change was synchronous (via setSearchItem and setSearchResult) but it isn't! React state setters are asynchronous.
The useEffect callback has a dependency on the searchItem state variable. Now every time the user types something, the state will change, that change will trigger a re-rendering of the Component and after that render finishes, the side-effect (the useEffect callback) will be executed due to the Components' lifecycle.
In our case, we don't want to initiate the fetch request on the next render, but right at the moment that the user enters something on the search input field, that is when the handleChange gets triggered.
In order to make the code work as expected, we need some a more structural refactoring.
You can get rid of the useEffect and handle the flow through the handleChange method:
export const useFetchAll = () => {
const [ loading, setLoading ] = useState( false );
const [ searchItem, setSearchItem ] = useState( "" );
const [ listToDisplay, setListToDisplay ] = useState( [] );
const handleChange = async ( e ) => {
const { value } = e.target;
// Return early if the input is an empty string:
setSearchItem( value );
if ( value === "" ) {
return setListToDisplay( [] );
}
setLoading( true );
const { data } = await axios.get( "https://api.github.com/repositories" );
setLoading( false );
const valueLowercase = value.toLowerCase(); // Tiny optimization so that we don't run the toLowerCase operation on each iteration of the filter process below
setListToDisplay(
data.filter(({ name }) => name.toLowerCase().includes(valueLowercase))
);
};
return {
searchItem,
handleChange,
loading,
listToDisplay,
};
};
function used for updating state value is asynchronous that why your state variable is showing previous value and not the updated value.
I have made some change you can try running the below code .
const [searchResult, setSearchResult] = useState([]);
const [loading, setLoading] = useState(false);
const [searchItem, setSearchItem] = useState("");
const [listToDisplay, setListToDisplay] = useState([]);
// const debouncedSearch = useDebounce(searchItem, 300);
const handleChange = (e) => {
setSearchItem(e.target.value); // this sets value asyncronously
console.log("e.target.value :" + e.target.value); // event.target.value does not omitting last character
console.log("searchItem :" + searchItem); // if we check the value then it is not set. it will update asyncronously
};
const setList = async () => {
if (searchItem === "") {
setListToDisplay([]);
} else {
setListToDisplay(
searchResult.filter((item) => {
return item.name.toLowerCase().includes(searchItem.toLowerCase());
})
);
}
};
const searchRepo = async () => {
const { data } = await axios.get("https://api.github.com/repositories");
setSearchResult(data);
setLoading(false);
};
// this useeffect execute its call back when searchItem changes a
useEffect(() => {
setList(); // called here to use previous value stored in 'searchResult' and display something ( uncomment it if you want to display only updated value )
if (searchItem) searchRepo();
}, [searchItem]);
// this useeffect execute when axios set fetched data in 'searchResult'
useEffect(() => {
setList();
}, [searchResult]);
// this useeffect execute when data is updated in 'listToDisplay'
useEffect(() => {
console.log("filtered Data") // final 'listToDisplay' will be availble here
console.log(listToDisplay)
}, [listToDisplay]);
I am making an api call to the steam review api with this link: "api link"
I have used another link with my code and was able to get responses and even display the data on my screen, so I have no faulty code. I am currently using this to try and get the result content: comment.reviews.review
This is my complete code:
function Home() {
const [comments, setComments] = useState([]);
useEffect(() => {
fetchComments();
}, []);
useEffect(() => {
console.log(comments);
}, [comments]);
const fetchComments = async () => {
const response = await axios(
"https://store.steampowered.com/appreviews/1389990?json=1&language=english"
);
setComments(response.data);
};
var limitComments = comments.slice(0, 3);
return (
{limitComments &&
limitComments.map((comment) => (
<p>{comment.reviews.review}</p>
))}
);
}
export default Home;
What is wrong with request? I have tried using different keys like comment.author.reviews.review.
I have a function that filters through some state and renders out the result for a search request.
const handleSearch = (value: string) => {
const searchResultData = users.filter((userId) => user.id.startsWith(value));
setSearchResult(searchResultData);
};
I am trying to work with lodash.throttle library to cause a delay before the request is sent. So we don't have a request go out every time a user types.
const handleSearch = useCallback(throttle((value: string) => {
const searchResultData = users.filter((userId) => user.id.startsWith(value));
setSearchResult(searchResultData);
}, 2500), []);
This works in delaying input as expected but for some reason, the user.filter method doesn't run, and so the state isn't updated with the search result. I believe the problem might be from the useCallback hook, but the throttle function is dependent on it to run. Any ideas on how I can work around this problem?
If your throttled/debounced handler uses props or state, like this:
const { fetcherFunctionFromProps } = props;
const eventHandler = async () => {
const resp = await fetcherFunctionFromProps();
};
const debouncedEventHandler = useMemo(
() => throttle(eventHandler, 300)
), [fetcherFunctionFromProps]);
And it doesn't work,
you can refactor it to the following:
const { fetcherFunctionFromProps } = props;
const eventHandler = async (fetcher) => {
const resp = await fetcher();
};
const debouncedEventHandler = useMemo(() => throttle(eventHandler, 300), []);
...
<Component onClick={() => debouncedEventHandler(fetcherFunctionFromProps)}>
I would like to filter data based on pressing multiple checkbox buttons. Currently only the most recently pressed button works and shows the output instead of also showing outputs from other buttons which are pressed as well.
The state of checkbox buttons works correctly i.e. when clicked it is true, when unclicked it is false - however I am not sure how to connect it with my find function which fetches the data.
const JobsList = (props) => {
const pageNumber = props.pageNumber || 1;
const [jobs, setJobs] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [page, setPage] = useState(pageNumber);
const [pages, setPages] = useState(1);
useEffect(() => {
const fetchJobs = async () => {
try {
retrieveJobs();
retrievePages();
pages = retrievePages();
setJobs(jobs);
setLoading(false);
} catch (error) {
console.log(error);
setLoading(false);
setError("Some error occured");
}
};
fetchJobs();
}, [page]);
const retrievePages = () => {
JobDataService.getPage(pages)
.then((response) => {
setPages(response.data.totalPages);
})
.catch((e) => {
console.log(e);
});
};
const Checkbox = ({ type = "checkbox", name, checked = false, onChange }) => {
return (
<input
type={type}
name={name}
checked={checked}
onChange={onChange}
className="btn--position"
/>
);
};
//plain object as state
const [checkedItems, setCheckedItems] = useState({}); //plain object as state
const filteredItems = [];
const handleChange = (event) => {
// updating an object instead of a Map
setCheckedItems({
...checkedItems,
[event.target.name]: event.target.checked,
filteredItems.
});
console.log("from HANDLECHANGE: ", checkedItems)
// console.log(checkedItems[event.target.checked])
// find(event.target.name)
};
useEffect(() => {
console.log("checkedItems from UseEffect: ", checkedItems);
// console.log(checkedItems)
// find(checkedItems)
}, [checkedItems]);
const checkboxes = [
{
name: "π€΅ββοΈ Finance",
key: "financeKey",
label: "financeLabel",
},
{
name: "π©βπ¨ Marketing",
key: "marketingKey",
label: "marketingLabel",
},
{
name: "π¨βπΌ Sales",
key: "salesKey",
label: "salesLabel",
},
{
name: "π₯· Operations",
key: "operationsKey",
label: "financeLabel",
},
{
name: "π¨βπ» Software Engineering",
key: "softwareEngineeringKey",
label: "softwareEngineeringLabel",
},
];
const retrieveJobs = () => {
JobDataService.getAll(page)
.then((response) => {
console.log(response.data);
setJobs(response.data.jobs);
})
.catch((e) => {
console.log(e);
});
};
const refreshList = () => {
retrieveJobs();
};
const find = (query, by) => {
JobDataService.find(query, by)
.then((response) => {
console.log(response.data);
setJobs(response.data.jobs);
// setPage(response.data.total_results)
setPages(response.data.totalPages);
})
.catch((e) => {
console.log(e);
});
};
return (
<div className="hero-container">
<div>
<div className="allButtons-div">
<div className="buttons-div">
<div>
<label>
{checkedItems[""]}
{/* Checked item name : {checkedItems["check-box-1"]}{" "} */}
</label>
{checkboxes.map((item) => (
<label key={item.key}>
{item.name}
<Checkbox
name={item.name}
checked={checkedItems[item.name]}
onChange={handleChange}
/>
</label>
))}
</div>
</div>
</div>
</div>
</div>
);
The function below fetches data from the MongoDB Realm database
const find = (query, by) => {
JobDataService.find(query, by)
.then((response) => {
setJobs(response.data.jobs);
setPages(response.data.totalPages);
})
.catch((e) => {
console.log(e);
});
};
To answer your question, our find() function should be a lot like your retrieveJobs() and retrievePages() functions - they interact with the data layer of your app. That said, if all we're trying to do is filter the data we already have (let's say that retrieveJobs() and retrievePages() fetches all of the jobs and pages you'll need), then we don't need refetch the data based on what's checked in your UI - we simply need to use JavaScript to filter the results by using things you should already be familiar with like map(), sort(), reduce(), filter(), etc.
To go further, this code has a lot of problems. We're using state probably a little more than we should, we're setting state in multiple places redundantly, we're using useEffect() calls that don't do much, the list goes on. I've been there - trying to do things in a "React" way can sometimes result in the opposite effect, where you're lost in endless useState() and useEffect() calls and trying to figure out where to call what event handler and why. I've gone through and made some fairly obvious changes to your code to hopefully get you on the right track to understanding what's going on a little bit better going forward, but I highly recommend going through the React docs and reading this post by Dan Abramov until you understand it (I had to read and re-read a couple paragraphs in that article over and over before it clicked, but I think it will go a long way for you).
Here's the code, it likely still has a lot of problems but best of luck moving forward!
// Since this is a constant set of data, you don't need to include it in your component; remember
// that React components are just regular old functions, so having this constant array value in your
// component means that it's being created anew every render. Let's move it above the component.
const checkboxes = [
{
name: 'π€΅ββοΈ Finance',
key: 'financeKey',
label: 'financeLabel',
},
{
name: 'π©βπ¨ Marketing',
key: 'marketingKey',
label: 'marketingLabel',
},
{
name: 'π¨βπΌ Sales',
key: 'salesKey',
label: 'salesLabel',
},
{
name: 'π₯· Operations',
key: 'operationsKey',
label: 'financeLabel',
},
{
name: 'π¨βπ» Software Engineering',
key: 'softwareEngineeringKey',
label: 'softwareEngineeringLabel',
},
];
// the same principle applies with this smaller component. It doesn't use
// state or props from JobsList, so we should move the component outside of
// your JobsList component to make sure it's not created over and over again
// on each render; let's move it outside of JobsList
const Checkbox = ({ type = 'checkbox', name, checked = false, onChange }) => {
return (
<input
type={type}
name={name}
checked={checked}
onChange={onChange}
className="btn--position"
/>
);
};
// Since these functions seem to interact with the data layer of your app (depending on how JobDataService works of course),
// why don't we try making them functions that return a value from the data layer? Also, it looks like we're using async/await
// syntax in our useEffect call, why don't we try that here?
const retrievePages = async (pages) => {
try {
const response = await JobDataService.getPage(pages);
return response;
} catch (e) {
console.log(e);
}
};
// as an aside, I'm not sure of the difference between pages and page, but we'll keep this the same for now
const retrieveJobs = async (page) => {
try {
const response = await JobDataService.getAll(page);
return response;
} catch (e) {
console.log(e);
}
};
// to hopefully kind of answer your question, this find() function is a lot like the retrieveJobs and retrievePages functions above:
// it just interacts with your data layer - let's try and make it an async function and pull it out of the component so it can return
// results we need. As I explained above, though, if we grabbed all of our jobs and all of our pages already and just need to filter
// the data, why do we need to make a network call for that? Surely we can just use JS functions like filter(), map(), sort(), and reduce()
// to filter the results into the structures that our app needs
const find = async (query, by) => {
try {
const response = await JobDataService.find(query, by);
return response;
} catch (e) {
console.log(e);
}
};
const JobsList = (props) => {
const pageNumber = props.pageNumber || 1;
const [jobs, setJobs] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
// if pageNumber is passed as a prop, why do we need to store it in state? Presumably the parent component
// of <JobsList /> will handle keeping track of pageNumber, which is why we pass data as props. Let's comment
// out this useState call
// const [page, setPage] = useState(pageNumber);
const [pages, setPages] = useState(1);
useEffect(() => {
const fetchJobs = async () => {
try {
const jobsData = await retrieveJobs(props.page);
const pageData = await retrievePages(pages);
setJobs(jobsData);
setPages(pageData);
// why do we call retrievePages() twice? also, you've decided to store pages in state, so we'll want to use setPages
// for this instead of a normal assignment. let's comment out this assignment
// pages = retrievePages();
setLoading(false);
} catch (error) {
console.log(error);
setLoading(false);
setError('Some error occured');
}
};
fetchJobs();
}, [props.page, pages]);
const [checkedItems, setCheckedItems] = useState({});
// this is where we could do things like filter based on the checked items instead of making another network call; we have all of our data,
// we just need to do stuff with it (this is contrived but hopfully you get the idea) - every time React re-renders the JobsList component based on a new set of state or props (think something gets checked or unchecked),
// we'll just filter the data we've already fetched based on that new reality
const filteredJobs = jobs.filter((job) => job.id === checkedItems[job.id]);
const filteredPages = pages.filter((page) => page.id === checkedItems[page.id]);
const handleChange = (event) => {
// updating an object instead of a Map
setCheckedItems({
...checkedItems,
[event.target.name]: event.target.checked,
// not sure what this is, perhaps a typo; let's comment it out
// filteredItems.
});
// this find call needs two arguments, no? let's comment it out for now
// find(event.target.name)
};
// not sure of the purpose behind this second useEffect call, let's comment it out
// useEffect(() => {
// console.log("checkedItems from UseEffect: ", checkedItems);
// // console.log(checkedItems)
// // find(checkedItems)
// }, [checkedItems]);
// we'll ignore this for now as well and comment it out, we should probably be refreshing our data based on state or prop updates
// const refreshList = () => {
// retrieveJobs();
// };
return (
<div className="hero-container">
<div>
<div className="allButtons-div">
<div className="buttons-div">
<div>
<label>
{checkedItems['']}
{/* Checked item name : {checkedItems["check-box-1"]}{" "} */}
</label>
{checkboxes.map((item) => (
<label key={item.key}>
{item.name}
<Checkbox
name={item.name}
checked={checkedItems[item.name]}
onChange={handleChange}
/>
</label>
))}
</div>
</div>
</div>
</div>
</div>
);
};
I'm newbie in React but I'm developing an app which loads some data from the server when user open the app. App.js render this AllEvents.js component:
const AllEvents = function ({ id, go, fetchedUser }) {
const [popout, setPopout] = useState(<ScreenSpinner className="preloader" size="large" />)
const [events, setEvents] = useState([])
const [searchQuery, setSearchQuery] = useState('')
const [pageNumber, setPageNumber] = useState(1)
useEvents(setEvents, setPopout) // get events on the main page
useSearchedEvents(setEvents, setPopout, searchQuery, pageNumber)
// for ajax pagination
const handleSearch = (searchQuery) => {
setSearchQuery(searchQuery)
setPageNumber(1)
}
return(
<Panel id={id}>
<PanelHeader>Events around you</PanelHeader>
<FixedLayout vertical="top">
<Search onChange={handleSearch} />
</FixedLayout>
{popout}
{
<List id="event-list">
{
events.length > 0
?
events.map((event, i) => <EventListItem key={event.id} id={event.id} title={event.title} />)
:
<InfoMessages type="no-events" />
}
</List>
}
</Panel>
)
}
export default AllEvents
useEvents() is a custom hook in EventServerHooks.js file. EventServerHooks is designed for incapsulating different ajax requests. (Like a helper file to make AllEvents.js cleaner) Here it is:
function useEvents(setEvents, setPopout) {
useEffect(() => {
axios.get("https://server.ru/events")
.then(
(response) => {
console.log(response)
console.log(new Date())
setEvents(response.data.data)
setPopout(null)
},
(error) => {
console.log('Error while getting events: ' + error)
}
)
}, [])
return null
}
function useSearchedEvents(setEvents, setPopout, searchQuery, pageNumber) {
useEffect(() => {
setPopout(<ScreenSpinner className="preloader" size="large" />)
let cancel
axios({
method: 'GET',
url: "https://server.ru/events",
params: {q: searchQuery, page: pageNumber},
cancelToken: new axios.CancelToken(c => cancel = c)
}).then(
(response) => {
setEvents(response.data)
setPopout(null)
},
(error) => {
console.log('Error while getting events: ' + error)
}
).catch(
e => {
if (axios.isCancel(e)) return
}
)
return () => cancel()
}, [searchQuery, pageNumber])
return null
}
export { useEvents, useSearchedEvents }
And here is the small component InfoMessages from the first code listing, which display message "No results" if events array is empty:
const InfoMessages = props => {
switch (props.type) {
case 'no-events':
{console.log(new Date())}
return <Div className="no-events">No results :(</Div>
default:
return ''
}
}
export default InfoMessages
So my problem is that events periodically loads and periodically don't after app opened. As you can see in the code I put console log in useEvents() and in InfoMessages so when it's displayed it looks like this:
logs if events are displayed, and the app itself
And if it's not displayed it looks like this: logs if events are not displayed, and the app itself
I must note that data from the server is loaded perfectly in both cases, so I have totally no idea why it behaves differently with the same code. What am I missing?
Do not pass a hook to a custom hook: custom hooks are supposed to be decoupled from a specific component and possibly reused. In addition, your custom hooks return always null and that's wrong. But your code is pretty easy to fix.
In your main component you can fetch data with a custom hook and also get the loading state like this, for example:
function Events () {
const [events, loadingEvents] = useEvents([])
return loadingEvents ? <EventsSpinner /> : <div>{events.map(e => <Event key={e.id} title={e.title} />}</div>
}
In your custom hook you should return the internal state. For example:
function useEvents(initialState) {
const [events, setEvents] = useState(initialState)
const [loading, setLoading] = useState(true)
useEffect(function() {
axios.get("https://server.ru/events")
.then(
(res) => {
setEvents(res.data)
setLoading(false)
}
)
}, [])
return [events, loading]
}
In this example, the custom hook returns an array because we need two values, but you could also return an object with two key/value pairs. Or a simple variable (for example only the events array, if you didn't want the loading state), then use it like this:
const events = useEvents([])
This is another example that you can use, creating a custom hook that performs the task of fetching the information
export const useFetch = (_url) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(true);
useEffect(function() {
setLoading('procesando...');
setData(null);
setError(null);
const source = axios.CancelToken.source();
setTimeout( () => {
axios.get( _url,{cancelToken: source.token})
.then(
(res) => {
setLoading(false);
console.log(res.data);
//setData(res);
res.data && setData(res.data);
// res.content && setData(res.content);
})
.catch(err =>{
setLoading(false);
setError('si un error ocurre...');
})
},1000)
return ()=>{
source.cancel();
}
}, [_url])