Invalid hook call React while fetching data from an API - javascript

passing country full name at onClick event.
here is error
import React,{useState,useEffect} from 'react'; //
import axios from 'axios';
export const Country = (name) => {
const [country, setCountry] = useState([]);
const requestCountry = (name) => {
axios(`https://restcountries.eu/rest/v2/name/${name}?fullText=true`)
.then((res) =>
// Success handling
setCountry(res.data))
.catch((error) => {
// Error handling
console.error(error.message);
});
}
requestCountry(name)
}
Here is Source of Code Click here to see code

Hooks can be only used inside a Functional Component and not a normal function.
Seems like you are trying to call a Functional Component like normal function with an argument like below.
onClick={() => Country(data.name)}
Instead what you might want to do is, show a list of buttons with country names and then when one of the button is clicked, call a handler function which is the axios API call and then show the response country details or do whatever that you want with those detail data.
To do that, you need to save those responded country detail into a React state.
If country detail exists, show the details. If not, show the list.
So, I forked your codesandbox and edit it like this.
https://codesandbox.io/s/country-data-i479e?file=/src/App.js

Well from the error I can see that you have put the Country call inside a event handler onClick.
The truth is that hooks can not be called inside event listeners. If you need to change the state inside a listener you can do that but you will need to call useState outside of a listener and then call setState wherever you need.
That is because React uses order in which you call hooks to remember how execute your component in subsequent calls.
const [state, setState] = useState();
const onClick = () => {
setState(...);
} ;

As the previous answers have mentioned, you can use hooks only at functional level and not inside a handler.
You just need to move your hook a level up and pass it to your function.
Also, as you're not returning anything from the Country function, there's no need to import "React".
I have modified the code: https://codesandbox.io/s/quiet-night-7cdvi
Check console (Added a useEffect in Country.js just for logging, you can remove it).

Some changed done in your code. Here is the link to view code. https://codesandbox.io/s/practical-dust-lk0x7?file=/src/country.js

Related

How to test the reaction to a component event in Svelte?

In Svelte, I have a parent component which listens to a component event dispatched by a child component.
I know how to use component.$on to check that the dispatched event does the right thing within the component which is dispatching, like so.
But I can't figure out how to check that the component which receives the dispatch does the right thing in response.
Here's a basic example:
Child.svelte
<script>
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
function handleSubmit(event) {
dispatch('results', 'some results')
}
</script>
<form on:submit|preventDefault={ handleSubmit }>
<button type='submit'>Submit</button>
</form>
Parent.svelte
<script>
import Child from './Child.svelte'
let showResults = false
function handleResults(event) {
showResults = true
}
</script>
<Child on:results={ handleResults } />
{ #if showResults }
<p id='results'>Some results.</p>
{ /if }
The idea is to eventually write a test using #testing-library/svelte like:
import { render } from '#testing-library/svelte'
import Parent from './Parent.svelte'
test('shows results when it receives them', () => {
const rendered = render(Parent)
// ***
// Simulate the `results` event from the child component?
// ***
// Check that the results appear.
})
If the parent were reacting to a DOM event, I would use fireEvent.
But I don't know how I would get a hold of the <Child> component in this case, and even if I could I'm guessing that Svelte is using a different mechanism for component events.
(Just to test it out, I used createEvent to fire a custom results event on one of the DOM elements rendered by <Child> but it didn't seem to do anything.)
Anyone have any ideas? Thanks!
If you're already planning on using #testing-library/svelte, I think the easiest way is not to try to manually trigger the Child component's results event, but to use Testing Library to grab the form/submit elements and trigger the submit event (using fireEvent a SubmitEvent on the <form> or their #testing-library/user-event library, or even a vanilla dispatchEvent). Svelte would then dispatch the custom results event that Parent is listening on.
Something like:
test('shows results when it receives them', async () => {
// Arrange
const rendered = render(Parent)
const submitButton = rendered.getByRole('button', {
name: /submit/i
});
const user = userEvent.setup();
// Act
await user.click(submitButton);
// Assert
const results = rendered.queryByText(/some results\./i);
expect(results).not.toBe(null);
});
Hope this is what you had in mind.
Edit:
For mocking Child.svelte, something like this in a __mocks__/Child.svelte should work:
<script>
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
function handleSubmit(event) {
dispatch("results", "some results");
}
</script>
<form on:submit|preventDefault={handleSubmit}>
<button type="submit">Test</button>
</form>
Which is the exact same implementation as the actual module (I gave the button a different label just to make it clear it's the mocked version when querying it), but the idea is that this would never need to change and is only used to dispatch a results event. Then you'd just need to tell Jest or whatever you're using that you're mocking it (jest.mock("./Child.svelte");), change the getByRole query to match the new name (or just leave the mock with the original name), then it should just work.
Whether you think that's worth it or not is up to you. I've generally had success testing the UI as a whole rather than mocking sub-components, but I guess it comes down to preference. Yes, you might have to change the test if the Child component changes, but only if you change the label of the button or change the user interaction mechanism.
You don't need to know about the details of the components, you don't even need to know that it's split into a separate Child component, all the test would care about is a general idea of the structure of the UI—that there's a button called "Submit" and that clicking on it should show an additional <p> tag.

How to give react components dynamic ids, when React runs code twice?

It's a known React behavior that code runs twice.
However, I'm creating a form builder in which I need to be able to give each form input a dynamic Id and use that Id for a lot of other purposes later. Here's a simple code of an input:
const Text = ({placeholder}) => {
const [id, setId] = useState(Math.random());
eventEmitter.on('global-event', () => {
var field = document.querySelector(`#${id}`); // here, id is changed
});
}
But since Math.random() is a side-effect, it's called twice and I can't create dynamic ids for my form fields.
The reason I'm using document.querySelector can be read here.
My question is, how can I create consistent dynamic ids for my inputs?
It seems you think that useState(Math.random()); is the side-effect causing you issue, but only functions passed to useState are double-invoked.
I think the issue you have is that the eventEmitter.on call is the unintentional side-effect since the function component body is also double invoked.
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:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies <-- this
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer <-- not this
To remedy this I believe you should place the eventEmitter.on logic into an useEffect hook with a dependency on the id state. You should also probably use id values that are guaranteed a lot more uniqueness. Don't forget to return a cleanup function from the effect to remove any active event "listeners", either when id updates, or when the component unmounts. This is to help clear out any resource leaks (memory, sockets, etc...).
Example:
import { v4 as uuidV4 } from 'uuid';
const Text = ({placeholder}) => {
const [id, setId] = useState(uuidV4());
useEffect(() => {
const handler = () => {
let field = document.querySelector(`#${id}`);
};
eventEmitter.on('global-event', handler);
return () => {
eventEmitter.removeListener('global-event', handler);
};
}, [id]);
...
}

React useState hook is not working when submitting form

I am new to React and I have some doubt regarding useState hook.I was recently working on an API based recipe react app .The problem I am facing is when I submit something in search form a state change should happen but the state is not changing but if I resubmit the form the state changes.
import React,{useState,useEffect} from "react";
import Form from "./componnents/form";
import RecipeBlock from "./componnents/recipeblock"
import './App.css';
function App() {
const API_id=process.env.REACT_APP_MY_API_ID;
const API_key=process.env.REACT_APP_MY_API_KEY;
const [query,setQuery]=useState("chicken");
const path=`https://api.edamam.com/search?q=${query}&app_id=${API_id}&app_key=${API_key}`
const [recipe,setRecipe]=useState([]);
useEffect(() => {
console.log("use effect is running")
getRecipe(query);
}, []);
function search(queryString){
setQuery(queryString);
getRecipe();
}
async function getRecipe(){
const response=await fetch(path);
const data=await response.json();
setRecipe(data.hits);
console.log(data.hits);
}
queryString in search() function holds the value of form input,Every time I submit the form this value is coming correctly but setQuery(queryString) is not changing the query value or state and if I resubmit the form then it change the state.
The code you provided something doesn't make sense.
useEffect(() => {
console.log("use effect is running")
getRecipe(query);
}, []);
Your getRecipe doesn't take a variable. But from what I am understanding whenever you search you want to set the Query then get the recipe from that Query.
With the useEffect you can pass in a parameters to check if they changed before running a function. So update the setQuery then when the component reloads it will fire the useEffect if query has changed. Here is the code to explain:
useEffect(() => {
console.log("use effect is running")
getRecipe(query); <-- this doesn't make sense on your code
}, [query]);
function search(queryString){
setQuery(queryString);
}
By doing this when the state updates it causes the component to re-render and therefore if query has changed it will call your getRecipe function.
The main issue in your code is that you are running getRecipe() directly after setQuery(queryString). setQuery(queryString) is asynchronous and will queue a state change. When you then run getRecipe() directly after, the state will still hold the old value of query (and path) and therefore does not fetch the new data correctly.
One solution would be to call getRecipe() within a useEffect() dependent on path.
useEffect(() => {
getRecipe();
}, [path]);
function search(queryString){
setQuery(queryString);
// getRecipe() <- removed
}
With [path] given as dependencies for useEffect(), getRecipe() will be called automatically whenever path changes. So we don't have to call it manually from search() and therefore can remove getRecipe() from the function body. This also makes the current useEffect() (without [path] dependency) redundant, so it can be removed.
Another solution would be to provide the new query value through the getRecipe() parameters, removing the dependency upon the state.
function search(queryString){
setQuery(queryString);
getRecipe(queryString);
}
async function getRecipe(query) {
const path = `https://api.edamam.com/search?q=${query}&app_id=${API_id}&app_key=${API_key}`;
const response = await fetch(path); // <- is no longer dependent upon the state
const data = await response.json();
setRecipe(data.hits);
}
This does require moving the path definition inside getRecipe().

Do functions get the latest state value in React?

I have a function inside of my functional component that uses a value saved in state. However, when it is called, it has the original value in state, not the updated value. When I look at my component in Chrome React Dev Tools, I see that the updated value is stored in state. Aren't functions supposed to get the latest state value in React? I didn't think I'd have to wrap my functions in a useEffect every time some value in state they depend on changes. Why is this happening?
const Editor = (props) => {
const [template, setTemplate] = useState(null);
const [openDialog, setOpenDialog] = useState(false);
useEffect(() => {
if (props.templateId) {
getTemplate(props.templateId));
}
},[]);
const getTemplate = (templateId) => {
{...make API to get template...}
.then((response) => {
if (response.template) setTemplate(response.template);
});
}
/* THIS FUNCTION SAYS TEMPLATE IS ALWAYS NULL */
const sendClick = async () => {
if (template) {
await updateTemplate();
} else {
await initializeTemplate();
}
setOpenDialog(true);
};
}
UPDATE: I figured out the issue. The sendClick function is being used inside an object that I have in state. When that object is created, it creates a version of the sendClick function based on the state at that time. I realized I needed to refactor my code so that the function is not stored within my object in state so that the function will always have the latest state values.
Please correct the code there its setTemplate(template)); not getTemplate(template));
I'm guessing that you have that right in the source code... if Yes then,
You have got into a trap that all developers new to React fall into.
This code is betraying you ...
useEffect(() => {
if (props.template) {
setTemplate(template)); // Mentioned as getTemplate(template));
}
},[]); // Here is where you make the mistake
The second argument you pass to the useEffect is called as Dependencies. Meaning if your useEffect is dependent on any state or any variable or function, Ii should be pass as the second argument inside the []. By now you should have got the answer.
Clearly, your useEffect is dependent on template. You should pass that inside the [].
So the code will be : -
useEffect(() => {
if (props.template) {
setTemplate(template)); // Mentioned as getTemplate(template));
}
},[template]);
Now React will automatically run the function every time the value of template changes therefore, updates template.
For more information about useEffect ...
Refer React Documentation
Refer the useEffect API

React - Populating First Select Option with Redux State

I have component League.js where there are 4 boxes containing League team details.
The team details are coming from this API => https://www.api-football.com/demo/api/v2/teams/league/${id}
When I click on each of the league boxes I am populating the Dropdown in my component Details.js
I won't get the team_id of the first team in the dropdown anytime I click on each of the League boxes in League.js in order to have the right calculation in a component called Stat.js. To do this I make a call request to this endpoint => https://www.api-football.com/demo/api/v2/statistics/${league}/${team}
So I need to pass league_id and team_id as a parameter, I can get both values correctly but I am doing something wrong on how to pass the first team_id of the team for each league.
These are the steps, I put only the relevant code. I am creating firstTeamStats state that I am dispatching
In my actions => index.js
export const RECEIVE_FIRST_TEAM_STATS = "RECEIVE_FIRST_TEAM_STATS";
export const receivedFirstTeamStats = json => ({
type: RECEIVE_FIRST_TEAM_STATS,
json: json
});
.get(`https://www.api-football.com/demo/api/v2/teams/league/${id}`)
.then(res => {
let teams = res.data.api.teams;
dispatch(receivedFirstTeamStats(teams[0].team_id));
})
in my reducer => index.js
case RECEIVE_FIRST_TEAM_STATS:
return {
...state,
firstTeamStats: action.json,
isTeamsDetailLoading: false
};
in my League.js component
import {getTeamsStats} from "../actions";
const mapStateToProps = state => ({
firstTeamStats: state.firstTeamStats
});
const mapDispatchToProps = {
getStats: getTeamsStats,
};
const onClick = (evt, id, firstTeamStats) => {
evt.preventDefault();
getDetail(id); \\ get the team names in the Detail.js component
getStats(id, firstTeamStats); \\ get the state value for the first team in the dropdown in Detail.js component
};
<a href={`#${item.league_id}`} onClick={(e) => onClick(e, item.league_id, firstTeamStats)}
Right now firstTeamStats in the onclick method above returns correctly the first team state value but of the existing league and not in the one where I click which is what I want.
However I can get this correctly in the Details.js Component, I have put {firstTeamStats} there in the demo where I have reproduced my case => https://codesandbox.io/s/romantic-solomon-6e0sb (Use CORS Unblock Chrome extension to see )
So the question is, how can i pass {firstTeamStats} correctly in League.js in the method onclick to the request getStats(id, firstTeamStats) where {firstTeamStats} is the state of my first team_id of the league?
Whenever you are making an API call, it is an asynchronous event. You should always await, before using its response in the next line.
So inside getDetail(), you are making an API call. But you haven't declared this method as async. so you are not waiting for it to complete and in the next line you are calling getStats() method with an old data. Thats why your app will always be a step behind, than your DOM events.
One way would be to use async|await and return the API response
from the function, instead of using dispatch.
You can also invoke the getTeamsStats() inside the getTeamsDetailById() after the api response is fetched, instead of invoking it inside OnClick()
event.
const onClick = (evt, id, firstTeamStats) => {
evt.preventDefault();
getDetail(id); \\ this is an async api call. you should have used await here.
getStats(id, firstTeamStats); \\ It will execute before the new stats are fetched
};
In Stats.js, you should not be strictly validating these fields to show Statistics... these are only numbers.. can also be 0 at times. so please remove these checks.
if (
teamsStatsWinHome &&
teamsStatsWinAway &&
teamsStatsDrawHome &&
teamsStatsDrawAway &&
teamsStatsLoseHome &&
teamsStatsLoseAway
) {
I have a working instance of your application... check below.
https://codesandbox.io/s/charming-dewdney-zke2t
Let me know, if you need anything.

Categories

Resources