Get input value with react hooks to search on oMdb api - reactjs

I want to do a movie search with the oMdb api using React Hooks.
The result is not as expected. I seem to break some React Hooks rule that I don't understand.
Here is the code.
HOOK TO SEARCH
The Hook inside of a store.
(If I use searchMovies('star wars') in a console.log I can see the result of star wars movies and series.)
import React, { useState, useEffect } from "react";
const useSearchMovies = (searchValue) => {
const API_KEY = "731e41f";
const URL = `http://www.omdbapi.com/?&apikey=${API_KEY}&s=${searchValue}`
// Manejador del estado
const [searchMovies, setSearchMovies] = useState([])
//Llamar y escuchar a la api
useEffect(() => {
fetch(URL)
.then(response => response.json())
.then(data => setSearchMovies(data.Search))
.catch((error) => {
console.Console.toString('Error', error)
})
}, []);
return searchMovies;
};
THE INPUT ON A SANDBOX
Here i have the input to search with a console log to see the result.
import React, { useState } from "react";
import searchMovies from "../store/hooks/useSearchMovies";
const Sandbox = () => {
const [search, setSearch] = useState('')
const onChangeHandler = e =>{
setSearch(e.target.value)
console.log('Search result', searchMovies(search))
}
const handleInput =()=> {
console.log('valor del input', search)
}
return (
<div>
<h1>Sandbox</h1>
<div>
<input type="text" value={search} onChange={onChangeHandler}/>
<button onClick={handleInput()}>search</button>
</div>
</div>
)
}
export default Sandbox;

Issue
You are breaking the rules of hooks by conditionally calling your hook in a nested function, i.e. a callback handler.
import searchMovies from "../store/hooks/useSearchMovies";
...
const onChangeHandler = e => {
setSearch(e.target.value);
console.log('Search result', searchMovies(search)); // <-- calling hook in callback
}
Rules of Hooks
Only call hooks at the top level - Don’t call Hooks inside loops,
conditions, or nested functions.
Solution
If I understand your code and your use case you want to fetch/search only when the search button is clicked. For this I suggest a refactor of your useSearchMovies hook to instead return a search function with the appropriate parameters enclosed.
Example:
const useSearchMovies = () => {
const API_KEY = "XXXXXXX";
const searchMovies = (searchValue) => {
const URL = `https://www.omdbapi.com/?apikey=${API_KEY}&s=${searchValue}`;
return fetch(URL)
.then((response) => response.json())
.then((data) => data.Search)
.catch((error) => {
console.error("Error", error);
throw error;
});
};
return { searchMovies };
};
Usage:
import React, { useState } from "react";
import useSearchMovies from "../store/hooks/useSearchMovies";
const Sandbox = () => {
const [search, setSearch] = useState('');
const [movies, setMovies] = useState([]);
const { searchMovies } = useSearchMovies();
const onChangeHandler = e => {
setSearch(e.target.value)
};
const handleInput = async () => {
console.log('valor del input', search);
try {
const movies = await searchMovies(search);
setMovies(movies);
} catch (error) {
// handle error/set any error state/etc...
}
}
return (
<div>
<h1>Sandbox</h1>
<div>
<input type="text" value={search} onChange={onChangeHandler}/>
<button onClick={handleInput}>search</button>
</div>
<ul>
{movies.map(({ Title }) => (
<li key={Title}>{Title}</li>
))}
</ul>
</div>
);
};
export default Sandbox;

Related

How to display data from Mongodb in React using Axios

I'm facing difficulty displaying data in React - Here is my code:
import Axios from 'axios';
import { useNavigate } from 'react-router';
export default function ProductCatalog() {
let navigate = useNavigate();
function addProduct() {
navigate('/adding')
}
const [products, setProducts] = useState([{}])
useEffect(() => {
const axiosProd = async () => {
const response = await Axios('http://localhost:3001/getProducts');
setProducts(response.data)
};
axiosProd();
}, []);
const useProducts = products.map((product)=>{
return <div>
<h1>{product.name}</h1>
</div>
})
return(
<>
<button className = "button" onClick={addProduct}>Add New Product</button>
<br></br>
{useProducts}
</>
)
}
I know data is coming in as JSON Objects as when i follow the link of http://localhost:3001/getProducts, I see my data. What am i doing wrong?
You should make a function then outside of the function call the use effect.
To do a get request using axios use axios.get(api)
For example:
// Get All Shoes
const getShoes = () => {
axios.get('/shoes')
.then(res => setShoes(res.data))
.catch(err => console.log(err));
}
Then
useEffect(() => {
getShoes();
}, [])

How to pass a value from an input to a submit button?

I'm currently working on a project to implement a website to check the weather forecast.
I'm trying to get the value from the input field and when I click the submit button, this value should be set to cityName. What do I have to change in order to make this work?
import { useState, useEffect } from "react"
export function WeatherInfo() {
const token: string = '7ebe7c2a03cd48c090a193437'
async function getCurrentWeather(cityName: string): Promise<any> {
const response = await fetch(`http://api.weatherapi.com/v1/current.json?key=${token}&q=${cityName}`)
const data = await response.json()
console.log(data)
return data
}
const [cityName, setCityName]: any = useState('')
const [cityWeather, setCityWeather] = useState({})
const [value, setValue] = useState('')
const handleChange = (event: any) => {
setValue(event.target.value)
}
const handleSubmit = (event: any) => {
event.preventDefault()
setCityName(value)
}
useEffect(() => {
async function fetchData() {
const cityWeather = await getCurrentWeather(cityName)
}
fetchData()
})
return (
<div >
<form onSubmit={handleSubmit}>
<input onChange={handleChange} placeholder="Type here" />
<button>Search</button>
</form>
</div>
);
}
You should add a dependency array to your effect hook so that it triggers whenever cityName changes.
Updating the cityWeather state should only be done via the setCityWeather function.
useEffect(() => {
if (cityName) { // only fetch when you've got a value
getCurrentWeather(cityName).then(setCityWeather);
}
}, [cityName]);
You should also try to use as few any types as possible, preferably none
// define stand-alone functions outside your components
// eg weather-api.ts
const token = "your-api-key";
export interface CurrentWeather {
temp_c: number;
feelslike_c: number;
// etc
}
export async function getCurrentWeather(
cityName: string
): Promise<CurrentWeather> {
// safely encode URL query params
const params = new URLSearchParams({
key: token,
q: cityName,
});
const response = await fetch(
`http://api.weatherapi.com/v1/current.json?${params}`
);
// don't forget to check for errors
if (!response.ok) {
throw response;
}
return response.json(); // will be cast to the `CurrentWeather` type
}
import { useState, useEffect, FormEventHandler } from "react";
import { getCurrentWeather, CurrentWeather } from "./weather-api";
export function WeatherInfo() {
const [cityName, setCityName] = useState("");
const [cityWeather, setCityWeather] = useState<CurrentWeather>(); // default undefined
const [value, setValue] = useState("");
useEffect(() => {
getCurrentWeather(cityName).then(setCityWeather).catch(console.error);
}, [cityName]);
const handleSubmit: FormEventHandler<HTMLFormElement> = (event) => {
event.preventDefault();
setCityName(value);
};
return (
<div>
{cityWeather && (
<p>
The current temperature in {cityName} is {cityWeather.temp_c} °C
</p>
)}
<form onSubmit={handleSubmit}>
<input
onChange={(e) => setValue(e.target.value)}
placeholder="Type here"
/>
<button>Search</button>
</form>
</div>
);
}

How do I update useEffect hook when clicked on a button? (how to write the button submit method?)

I am working on an API app that will return a list of jobs when user enter the job description or location. Initially, the page will return all jobs and display them to the screen at the first render (default useEffect). Now I want when the user clicks on the button, page will render a list of job based on user's inputs. How do I do that on my onSubmit function in order to update the value of useEffect hooks?
import React, { useState, useEffect} from 'react';
import SearchForm from './componenets/Form.js';
import JobLists from './componenets/JobLists'
import axios from 'axios'
function App() {
const [posts, setPosts] = useState([]) //posts store a list of jobs
const [description, setDescription] = useState('') //description of the job (user's input)
const [location, setLocation] = useState('') //location of the job (user's input)
//description input handle
const handleDescriptionChange = (e) => {
setDescription(e.target.value);
}
//location input handle
const handleLocationChange = (e) => {
setLocation(e.target.value);
}
//submit button handle
const onSubmit = e => {
e.preventDefault();
//once the user enter input and clicks on button, update the the useEffect hooks
}
//get data from github job API (fetching by description and location)
const url = `https://cors-anywhere.herokuapp.com/https://jobs.github.com/positions.json?description=${description}&location=${location}`
useEffect(() => {
axios.get(url)
.then(res =>{
console.log(res)
setPosts(res.data)
})
.catch(err =>{
console.log(err)
})
}, [])
return (
<div>
<SearchForm
description={description}
handleDescriptionChange={handleDescriptionChange}
location={location}
handleLocationChange={handleLocationChange}
onSubmit={onSubmit} />
{
posts.map((job) => <JobLists job={job} key={job.id} />) //map through each job
}
</div>
)
}
export default App;
https://codesandbox.io/s/react-form-example-gm9o6
import React, { useState, useEffect } from 'react'
import SearchForm from './componenets/Form.js'
import JobLists from './componenets/JobLists'
import axios from 'axios'
const App = () => {
const [posts, setPosts] = useState([]) //posts store a list of jobs
const [description, setDescription] = useState('') //description of the job (user's input)
const [location, setLocation] = useState('') //location of the job (user's input)
const url = `https://cors-anywhere.herokuapp.com/https://jobs.github.com/positions.json?description=${description}&location=${location}`
const getPosts = async () => {
await axios
.get(url)
.then((res) => {
console.log(res)
setPosts(res.data)
})
.catch((err) => {
console.log(err)
})
}
//get data from github job API (fetching by description and location)
useEffect(() => {
getPosts()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
//description input handle
const handleDescriptionChange = (e) => {
setDescription(e.target.value)
}
//location input handle
const handleLocationChange = (e) => {
setLocation(e.target.value)
}
//submit button handle
const onSubmit = (e) => {
e.preventDefault()
//once the user enter input and clicks on button, update the the useEffect hooks
getPosts()
}
return (
<div>
<SearchForm
description={description}
handleDescriptionChange={handleDescriptionChange}
location={location}
handleLocationChange={handleLocationChange}
onSubmit={onSubmit}
/>
{
!!posts?.length &&
posts.map((job) => <JobLists key={job.id} job={job} />) //map through each job
}
</div>
)
}
export default App

Api marvel response don't show react

I try to training in react and want to make a form who call the api marvel when submitted with the current input and display the name + description of the character search.
The Api call is ok but when i submit the form nothing show any advice?
import React, { Component, useEffect, useState } from 'react'
import axios from 'axios'
const SearchEngine = React.forwardRef((props, ref) => {
const [asked, setAsked] = useState([]);
const [characterInfos, setCharacterInfos] = useState([]);
const [searchTerm, setSearchTerm] = useState("");
const [loading, setLoading] = useState(true);
const [inputs, setInputs] = useState('');
const handleChange = (event) => {
setInputs(event.target.value);
console.log(inputs);
}
const getCharacters = (inputs) => {
setSearchTerm(inputs)
axios
.get(`https://gateway.marvel.com:443/v1/public/characters?name=${searchTerm}&apikey=XXX`)
.then(response => {
console.log(searchTerm)
console.log(response)
setCharacterInfos(response.data.data.results[0]);
setLoading(false);
console.log(response.data.data.results[0].name)
response.data.data.results.map((item) => {
return characterInfos.push(item.name)
})
localStorage.setItem(characterInfos, JSON.stringify(response.data))
if (!localStorage.getItem('marvelStorageDate')) {
localStorage.setItem('marvelStorageDate', Date.now());
}
})
.catch(error => {
console.log(error);
})
}
return (
<div className="search-container">
<h1>Character Infos</h1>
<form onSubmit={getCharacters}>
<input
type="text"
placeholder="Search"
value={inputs}
onChange={handleChange}
/>
<input type="submit" value="Envoyer" />
</form>
<ul>
<li>{characterInfos.name}</li>
</ul>
</div>
)
})
export default React.memo(SearchEngine)
Thanks for your help. Any to advice to show a list of all the character and make a search filter who work with minimum 3 characters?
getCharacters is fired with form submit event as param. You are assuming that is getting inputs from the state wrongly:
const getCharacters = event => {
event.preventDefault() // Prevent browser making undesired form native requests
// setSearchTerm(inputs); // Not sure what are you trying here but, again, inputs is a form submit event
axios
.get( // use searchValue as query string in the url
`https://gateway.marvel.com:443/v1/public/characters?name=${searchValue}&apikey=XXX`
)
.then(response => {
console.log(searchTerm);
console.log(response);
setCharacterInfos(response.data.data.results[0]);
setLoading(false);
console.log(response.data.data.results[0].name);
response.data.data.results.map(item => {
return characterInfos.push(item.name);
});
localStorage.setItem(characterInfos, JSON.stringify(response.data));
if (!localStorage.getItem("marvelStorageDate")) {
localStorage.setItem("marvelStorageDate", Date.now());
}
})
.catch(error => {
console.log(error);
});
};

Putting fetch function in a separate component

I'm trying to take out the fetchImages function from the following component and put it inside a new component:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import UnsplashImage from './UnsplashImage';
const Collage = () => {
const [images, setImages] = useState([]);
const [loaded, setIsLoaded] = useState(false);
const fetchImages = (count = 10) => {
const apiRoot = 'https://api.unsplash.com';
const accessKey =
'<API KEY>';
axios
.get(`${apiRoot}/photos/random?client_id=${accessKey}&count=${count}`)
.then(res => {
console.log(res);
setImages([...images, ...res.data]);
setIsLoaded(true);
});
};
useEffect(() => {
fetchImages();
}, []);
return (
<div className="image-grid">
{loaded
? images.map(image => (
<UnsplashImage
url={image.urls.regular}
key={image.id}
alt={image.description}
/>
))
: ''}
</div>
);
};
export default Collage;
For this, I created a new component called api.js, removed the entire fetchImage function from the above component and put it in to api.js like this:
api.js
const fetchImages = (count = 10) => {
const apiRoot = 'https://api.unsplash.com';
const accessKey =
'<API KEY>';
axios
.get(`${apiRoot}/photos/random?client_id=${accessKey}&count=${count}`)
.then(res => {
console.log(res);
setImages([...images, ...res.data]);
setIsLoaded(true);
});
};
export default fetchImages;
Next I took setIsLoaded(true); from api.js and paste it inside Collage component like this:
useEffect(() => {
fetchImages();
setIsLoaded(true);
}, []);
Now I can import fetchImages in to Collage component.
However, I don't know what should I do with this line inside the fetchImages function? This needs to go to Collage component, but res.data is not defined inside Collage component.
setImages([...images, ...res.data]);
How should I handle it?
There is many way to do that, but in your case.
You should use
const fetchImages = (afterComplete, count = 10) => {
const apiRoot = 'https://api.unsplash.com';
const accessKey = '<API KEY>';
axios
.get(`${apiRoot}/photos/random?client_id=${accessKey}&count=${count}`)
.then(res => {
console.log(res);
afterComplete(res.data);
});
};
export default fetchImages;
And in your Collage component:
const afterComplete = (resData) =>{
setImages([...images, ...resData]);
setIsLoaded(true);
}
useEffect(() => {
fetchImages(afterComplete);
}, []);
What you can do is create a custom hook ( sort of like a HOC)... Since I don't have an unsplash API key I'll give you an example with a different API but the idea is the same:
Here is your custom hook:
import { useState, useEffect } from 'react';
export const useFetch = url => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const fetchUser = async () => {
const response = await fetch(url);
const data = await response.json();
const [user] = data.results;
setData(user);
setLoading(false);
};
useEffect(() => {
fetchUser();
}, []);
return { data, loading };
};
Here is how you can use it in your component:
import { useFetch } from './api';
const App = () => {
const { data, loading } = useFetch('https://api.randomuser.me/');
return (
<div className="App">
{loading ? (
<div>Loading...</div>
) : (
<>
<div className="name">
{data.name.first} {data.name.last}
</div>
<img className="cropper" src={data.picture.large} alt="avatar" />
</>
)}
</div>
);
};
Here is a live demo: https://codesandbox.io/s/3ymnlq59xm

Resources