React Hook useEffect Error missing dependency - javascript

I’m very new to React and I’m trying to build an app, but I’m getting this error : React Hook useEffect has a missing dependency: ‘getRecipes’. Either include it or remove the dependency array. I cannot figure out how to fix it. Any help would be appreciated ?
useEffect( () => {
getRecipes();
}, [query]);
const getRecipes = async () => {
const response = await fetch(`https://api.edamam.com/search?q=${query}&app_id=${APP_ID}&app_key=${APP_KEY}`);
const data = await response.json();
setRecipes(data.hits);
console.log(data.hits);
}
const updateSearch = e => {
setSearch(e.target.value);
}
const getSearch = e => {
e.preventDefault();
setQuery(search)
}
return(
<div className="App">
<form onSubmit={getSearch}className="container">
<input className="mt-4 form-control" type="text" value={search} onChange={updateSearch}/>
<button className="mt-4 mb-4 btn btn-primary form-control" type="submit">Search</button>
</form>
<div className="recipes">
{recipes.map(recipe => (
<Recipe
key={recipe.label}
title={recipe.recipe.label} image={recipe.recipe.image}
ingredients={recipe.recipe.ingredients}calories={recipe.recipe.calories}
/>
))}
</div>
</div>
)
}

As your useEffect calls getRecipes(); React is indicating that getRecipes is a dependency on this useEffect Hook.
You could update with Effect with:
useEffect(() => {
getRecipes();
}, [query, getRecipes]);
However you will get
The 'getRecipes' function makes the dependencies of useEffect Hook (at line 18) change on every render. Move it inside the useEffect callback. Alternatively, wrap the 'getRecipes' definition into its own useCallback() Hook. (react-hooks/exhaustive-deps)
So you can update to:
useEffect(() => {
const getRecipes = async () => {
const response = await fetch(
`https://api.edamam.com/search?q=${query}&app_id=${APP_ID}&app_key=${APP_KEY}`
);
const data = await response.json();
setRecipes(data.hits);
console.log(data.hits);
};
getRecipes();
}, [query]);
Which indicates this effect will be called, when query is modified, which means getRecipes call the API with query.

Related

Getting API with useEffect, inside a handle change function

I'm using the YTS API and I need to change the link for the call, I have to use
?query_term= and add the text that the user is typing, for autocomplete. I'm using mantine components for the autocomplete. I tried putting the call inside the handlechange function, but this is not possible.
const [movieNames, setMovieNames] = useState([])
const onChangeHandler = (text) => {
useEffect(() => {
const loadMovieNames = async () => {
const response = await axios.get('https://yts.mx/api/v2/list_movies.json?query_term='+text);
let arrayOfMoviesNames = [];
response.data.data.movies.forEach(i => {
arrayOfMoviesNames.push(i.title)
});
setMovieNames(arrayOfMoviesNames)
}
loadMovieNames()
}, [])
}
.
<Autocomplete
placeholder="Search Movie"
limit={8}
data={movieNames}
onChange={e => onChangeHandler(e.target.value)}
/>
You MUST use hooks in the execution context of Function Component, you used the useEffect inside a function not in the execution context of Function Component.
const YourComponent = () => {
const [movieNames, setMovieNames] = useState([]);
const loadMovieNames = async (text) => {
const response = await axios.get(
'https://yts.mx/api/v2/list_movies.json?query_term=' + text
);
let arrayOfMoviesNames = [];
response.data.data.movies.forEach((i) => {
arrayOfMoviesNames.push(i.title);
});
setMovieNames(arrayOfMoviesNames);
};
return (
<Autocomplete
placeholder="Search Movie"
limit={8}
data={movieNames}
onChange={(value) => loadMovieNames(value)}
/>
);
};
It is also possible without useEffect, so without making it so complicated by using useEffect and onChangeHandler both, only use onChangeHandler function to update the movieNames and it will automatically update the DOM texts (I mean where ever you use)...
import React, { useState } from "react";
function MoviesPage(props) {
const [ movieNames, setMovieNames ] = useState([]);
const [ searchValue, setSearchValue ] = useState("");
const onChangeHandler = async (text) => {
const response = await axios.get(
'https://yts.mx/api/v2/list_movies.json?query_term=' + text
);
let arrayOfMoviesNames = [];
response.data.data.movies.forEach(i => {
arrayOfMoviesNames.push(i.title)
});
setMovieNames(arrayOfMoviesNames);
}
return (
<div>
<Autocomplete
placeholder="Search Movie"
limit={8}
data={movieNames}
onChange={(e) => onChangeHandler(e.target.value)}
/>
</div>
);
}
export default MoviesPage;
...and just to clarify, you can use useEffect in case of API if you want to initialize the page with the API data. You can use this hook if you don't have any onChange handlers. Another way you can approach is you can update a state hook (like searchData) on the change of the Search Bar, and lastly add the the searchData variable to the useEffect dependency array:
useEffect(() => {
// use the searchData variable to populate or update the page
// ...
},
[
searchData, // <-- talking about this line
]);
So, this was my solution. Hope this helps you mate!
useEffect is a hook, which executes on state change, So keep the useEffect funtion outside the onChangeHandler and add a new state for 'query param' and setQueryState(text) inside the onChangeHandler, and put the state param as dependency in useEffect, So whenever this state gets changed this will call the use effect function automatically.

React.JS: Form problem to make post caused from useEffect

I have created a form and I have noticed that when I submit data, they are not writing in the db (with error 400). So I have investigated and I have noticed that one api call that I make in useEffect is done about 5 time during the submit. (I have tried to comment this part and It works!)
I have a first part of form, in which with a select I make a choose, this value is used to make an api call (and there is the problem) to give back some data to use in the form.
return (
<AvForm model={isNew ? {} : userClientAuthorityEntity} onSubmit={saveEntity}>
<AvInput
id="client-application"
data-cy="application"
type="select"
className="form-control"
name="application"
onChange={handleChangeApp} // there i save the value applicationApp
required
value={applicationApp}
>
<option value="" key="0">
Select
</option>
{applicationListAPP ?
applicationListAPP.map(value => {
return (
<option value={value.appCod} key={value.appCod}>
{value.appDescription}
</option>
);
})
: null}
</AvInput>
</AvGroup>
<ShowRoleApp applicationRole={applicationApp} /> // so there I pass the value to make the api call
)
const ShowRoleApp = ({ applicationRole }) => {
const [profili, setProfili] = useState([]);
const [isLoading, setIsLoading] = useState(false);
if (!applicationRole) {
return <div />;
}
// I think that it the problem, because it recall GetProfili
useEffect(() => {
async function init() {
await GetProfili(applicationRole)
.then((res) => {
console.log('res ', res);
setProfili(res);
setIsLoading(true);
})
.catch((err) => console.log('err ', err));
}
init();
}, []);
return isLoading ? (
RenderProfili(profili, applicationRole)
) : (
<div className='d-flex justify-content-center'>
<div className='spinner-border text-primary' role='status'>
<span className='visually-hidden'></span>
</div>
</div>
);
};
const GetProfili = async (appCod) => {
const chiamata = 'myApi' + appCod.toString();
const res = await fetch(chiamata);
const result = res.clone().json();
return result;
};
const RenderProfili = (profili, applicationRole) => {
const ruoliOperatore = profili ? profili.filter(it => it.appCod.toString() === applicationRole.toString()) : null;
return (
<AvGroup>
<Label for="sce-profiloutentepa-pucCod">Profile (*)</Label>
// other code for the form...
So in your opinion how can i do to call the GetProfili without recall every time when I submit the form?
Thank you
You could define GetProfili as a custom hook an manage the useEffect call in it.
It will return the isLoading and profili instances.
Try to change your code like this.
GetProfili:
const GetProfili = (appCod) => {
const [isLoading, setIsLoading] = useState(true)
const [profili, setProfili] = useState([])
const loadProfili = async () => {
const chiamata = 'myApi' + appCod.toString();
const res = await fetch(chiamata);
setProfili(res.json())
setIsLoading(false)
}
useEffect(() => {
loadProfili()
}, [])
return { isLoading, profili };
};
ShowRoleApp:
const ShowRoleApp = ({ applicationRole }) => {
if (!applicationRole) {
return <div />;
}
const { isLoading, profili } = GetProfili(applicationRole)
return isLoading ? (
RenderProfili(profili, applicationRole)
) : (
<div className='d-flex justify-content-center'>
<div className='spinner-border text-primary' role='status'>
<span className='visually-hidden'></span>
</div>
</div>
);
};
I didn't really understand the question but I can say something that might help. The useEffect() hook gets called on every rerender of the component so if it updates 5 times its because some states inside the component get updated 5 times. Also states are updated in child components update the parent.

Why am I getting first response as an empty array in react?

Below I am trying to fetch data and use the onInputValue function in my other component called Search. It's working fine after first attempt, but I am getting an empty array in my initial button click
const App = () => {
const [results, setResults] = useState([]);
const onInputValue = async (input) => {
const { data } = await nasa.get('/search', {
params: {
q: input,
},
});
if(!results) {
return;
}
setResults(data.collection.items);
console.log(results);
};
return (
<div>
<Search onInputValue={onInputValue} />
</div>
);
};
import React, { useState} from 'react';
const Search = ({ onInputValue }) => {
const [input, setInput] = useState('');
return (
<div className='input-group mb-3'>
<input
type='text'
className='form-control'
placeholder='To infinity and beyond!'
onChange={(e) => setInput(e.target.value)}
/>
<div className='input-group-append'>
<button
onClick={() => {onInputValue(input)}}
className='btn btn-outline-secondary'
type='button'
>
<i className='fas fa-rocket'></i>
</button>
</div>
</div>
);
};
export default Search;
Below is the result I get.
Please advise
setResults is asynchronous if you want to check results you can use useEffect
const App = () => {
const [results, setResults] = useState([]);
useEffect(() => {
console.log(results);
}, [results])
const onInputValue = async (input) => {
const {data} = await nasa.get('/search', {
params: {
q: input,
},
});
if (!results) {
return;
}
setResults(data.collection.items);
};
return (
<div>
<Search onInputValue={onInputValue} />
</div>
);
};
This is due to asynchronous nature of setResults function - after calling it, the updated value of results will be available on the next component render - in your example you're logging in to console immediately after invoking setResults.
It could be because the setResults is batched and therefore move to the next line.
See useState batch updates.
Also
if(!results) { return; }
Is a bit suspect. ![] === false and !['someValue'] === false

Using React useEffect hook with rxjs mergeMap operator

I'm trying to implement a data stream that has to use inner observables, where I use one from mergeMap, concatMap etc.
e.g.:
const output$$ = input$$.pipe(
mergeMap(str => of(str).pipe(delay(10))),
share()
);
output$$.subscribe(console.log);
This works fine when logging into console.
But when I try to use it in React like below utilizing useEffect and useState hooks to update some text:
function App() {
const input$ = new Subject<string>();
const input$$ = input$.pipe(share());
const output$$ = input$$.pipe(
mergeMap(str => of(str).pipe(delay(10))),
share()
);
output$$.subscribe(console.log);
// This works
const [input, setInput] = useState("");
const [output, setOutput] = useState("");
useEffect(() => {
const subscription = input$$.subscribe(setInput);
return () => {
subscription.unsubscribe();
};
}, [input$$]);
useEffect(() => {
const subscription = output$$.subscribe(setOutput);
// This doesn't
return () => {
subscription.unsubscribe();
};
}, [output$$]);
return (
<div className="App">
<input
onChange={event => input$.next(event.target.value)}
value={input}
/>
<p>{output}</p>
</div>
);
}
it starts acting weird/unpredictable (e.g.: sometimes the text is updated in the middle of typing, sometimes it doesn't update at all).
Things I have noticed:
If the inner observable completes immediately/is a promise that
resolves immediately, it works fine.
If we print to console instead of useEffect, it works fine.
I believe this has to do something with the inner workings of useEffect and how it captures and notices outside changes, but cannot get it working.
Any help is much appreciated.
Minimal reproduction of the case:
https://codesandbox.io/s/hooks-and-observables-1-7ygd8
I'm not quite sure what you're trying to achieve, but I found a number of problems which hopefully the following code fixes:
function App() {
// Create these observables only once.
const [input$] = useState(() => new Subject<string>());
const [input$$] = useState(() => input$.pipe(share()));
const [output$$] = useState(() => input$$.pipe(
mergeMap(str => of(str).pipe(delay(10))),
share()
));
const [input, setInput] = useState("");
const [output, setOutput] = useState("");
// Create the subscription to input$$ on component mount, not on every render.
useEffect(() => {
const subscription = input$$.subscribe(setInput);
return () => {
subscription.unsubscribe();
};
}, []);
// Create the subscription to output$$ on component mount, not on every render.
useEffect(() => {
const subscription = output$$.subscribe(setOutput);
return () => {
subscription.unsubscribe();
};
}, []);
return (
<div className="App">
<input
onChange={event => input$.next(event.target.value)}
value={input}
/>
<p>{output}</p>
</div>
);
}
I had a similar task but the goal was to pipe and debounce the input test and execute ajax call.
The simple answer that you should init RxJS subject with arrow function in the react hook 'useState' in order to init subject once per init.
Then you should useEffect with empty array [] in order to create a pipe once on component init.
import React, { useEffect, useState } from "react";
import { ajax } from "rxjs/ajax";
import { debounceTime, delay, takeUntil } from "rxjs/operators";
import { Subject } from "rxjs/internal/Subject";
const App = () => {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const [filterChangedSubject] = useState(() => {
// Arrow function is used to init Singleton Subject. (in a scope of a current component)
return new Subject<string>();
});
useEffect(() => {
// Effect that will be initialized once on a react component init.
// Define your pipe here.
const subscription = filterChangedSubject
.pipe(debounceTime(200))
.subscribe((filter) => {
if (!filter) {
setLoading(false);
setItems([]);
return;
}
ajax(`https://swapi.dev/api/people?search=${filter}`)
.pipe(
// current running ajax is canceled on filter change.
takeUntil(filterChangedSubject)
)
.subscribe(
(results) => {
// Set items will cause render:
setItems(results.response.results);
},
() => {
setLoading(false);
},
() => {
setLoading(false);
}
);
});
return () => {
// On Component destroy. notify takeUntil to unsubscribe from current running ajax request
filterChangedSubject.next("");
// unsubscribe filter change listener
subscription.unsubscribe();
};
}, []);
const onFilterChange = (e) => {
// Notify subject about the filter change
filterChangedSubject.next(e.target.value);
};
return (
<div>
Cards
{loading && <div>Loading...</div>}
<input onChange={onFilterChange}></input>
{items && items.map((item, index) => <div key={index}>{item.name}</div>)}
</div>
);
};
export default App;

React hooks callback receives outdated state

Trying out react hooks on a simple search component. The idea is simple: user types symbols, every typed symbol initiates api query.
To achieve that I have useState and useCallback hooks like in the code below:
const Search = () => {
const [query, setQuery] = useState("");
const sendRequest = useCallback(() => {
console.log('sendRequest ', query);
}, [query]);
return (
<div>
<input
type="text"
value={query}
placeholder="Search"
onChange={e => {
console.log('onChange ', e.target.value);
setQuery(e.target.value);
sendRequest();
}}
/>
</div>
}
The result is that sendRequest method always gets a previous version of query.
onChange q
sendRequest
onChange qu
sendRequest q
onChange que
sendRequest qu
Why is that? I assume that this is not how the hooks are supposed to be used, but I can't figure that out from the documentation.
setState is asynchronous!
At the time you send sendRequest, the local state is not updated, because it is asynchronous and it needs some time to get set.
You should either give the string as a parameter into the function or useEffect and listen to changes of query.
Exchanging useCallback with useEffect and removing the call in onChange should work.
const Search = () => {
const [query, setQuery] = useState("");
useEffect(() => {
console.log('sendRequest ', query);
}, [query]);
return (
<div>
<input
type="text"
value={query}
placeholder="Search"
onChange={e => {
setQuery(e.target.value);
}}
/>
</div>
}
Use useEffect instead useCallback. useEffect fires your callback function when query changes.
useEffect(() => { console.log(query) }, [query])
hey bro you can try this implementation its works as you expect
const [query, setQuery] = useState("");
const sendRequest = e => {
setQuery(e);
console.log('sendRequest ', e);
};
return (
<div>
<input
type="text"
value={query}
placeholder="Search"
onChange={e => {
console.log('onChange ', e.target.value);
sendRequest(e.target.value);
}}
/>
</div>)

Categories