I'm using match to pull an ID that will search an API for that ID and pull data from it. This was working until I started moving my files around and now I'm not sure how to pass match into my fetch function
HeroDetail.services
import React from 'react'
export const fetchHeroDetail = async ({match}) => {
const data = await fetch(`https://api.opendota.com/api/heroStats`)
const item = await data.json()
const heroId = match.params.id
console.log(match.params.id)
const hero = item.find(element => element.id === Number(heroId))
console.log(hero)
return await hero
};
HeroDetail Component
import React, {useState, useEffect} from "react"
import "../App.css"
import {fetchHeroDetail} from './services/HeroDetail.services'
const setHeroDetail = async setHero => {
const hero = await fetchHeroDetail()
setHero(hero)
}
function HeroDetail() {
const [hero, setHero] = useState({})
useEffect(() => {
setHeroDetail(setHero)
},[])
return(
<div>
<h1>{hero.localized_name} </h1>
<h2>{hero.move_speed}</h2>
<h2>{hero.base_health}</h2>
</div>
)
}
export default HeroDetail
You should just do this:
function HeroDetail() {
const [hero, setHero] = useState({})
useEffect(async () => {
if (!hero) {
const data = await fetchHeroDetail()
setHero(data)
}
})
...
}
Related
I am new to JavaScript and React and am building a weather app that allows the user to save locations to their profile and retrieve the forecast data relevant to that specific location.
I need to display a list of buttons that display the only the names of the locations saved by the user logged in.
Currently, I'm fetching locations, current profile, and then mapping through the locations that was returned by the fetch to match up the location id's to the foreign keys saved to the profile. Right now, all of my fetches return empty arrays and objects. However, if I edit the code and save it, my React app re-renders correctly and the console prints the correct data. When I refresh the page I'm back to empty arrays and objects.
Here's my ProfilesProvider:
import React, { useState, createContext } from "react";
export const ProfileContext = createContext();
export const ProfileProvider = (props) => {
const [profiles, setProfiles] = useState([]);
const [currentProfile, setCurrentProfile] = useState({});
const getProfiles = () => {
return fetch("http://localhost:8088/profiles")
.then((res) => res.json())
.then((theProfiles) => setProfiles(theProfiles))
.then(console.log(profiles));
};
const getCurrentProfile = () => {
let id = localStorage.getItem("weathernet_user");
return fetch(`http://localhost:8088/profiles/${id}`)
.then((res) => res.json())
.then((theProfile) => {
return setCurrentProfile(theProfile);
})
.then(console.log(currentProfile));
};
Heres my LocationsProvider:
import React, { useState, createContext } from "react";
export const LocationContext = createContext();
export const LocationProvider = (props) => {
const apiURL = "http://localhost:8088";
const locationsURL = apiURL + "/locations";
const [locations, setLocations] = useState([]);
let id = localStorage.getItem("weathernet_user");
const getLocations = () => {
return fetch("http://localhost:8088/locations")
.then((res) => res.json())
.then(setLocations);
};
const addLocation = (locationObj) => {
return fetch("http://localhost:8088/locations", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(locationObj),
}).then(getLocations());
};
And here's the module where I'm implementing the code:
import React, { useContext, useEffect, useState } from "react";
import { LocationContext } from "./LocationsProvider";
import { ProfileContext } from "../profiles/ProfilesProvider";
import { useHistory } from "react-router-dom";
import "./Locations.css";
export const LocationList = () => {
const { locations, getLocations, deleteLocation } =
useContext(LocationContext);
const { profiles, getProfiles, currentProfile, getCurrentProfile } =
useContext(ProfileContext);
const [profile, setCurrentProfile] = useState({});
const [city, setCity] = useState("");
const [result, setResult] = useState({});
const [isHidden, setIsHidden] = useState(true);
const [buttonList, setButtonList] = useState([]);
const history = useHistory();
useEffect(() => {
Promise.all([
getLocations(),
getProfiles(),
getCurrentProfile(),
setCurrentProfile(),
]).then(() => {
console.log(currentProfile);
setButtonList(locationResults);
console.log(buttonList);
console.log(locationResults);
});
}, []);
const locationResults = (currentProfile.savedCityId || []).map((cityId) => {
return locations.find((location) => location.id === cityId);
});
I didn't include the lower half of the module because it's where im returning a form and I didn't want to clutter up the question too much
I'm learning React and am having trouble with a value defined in a custom context provider. I access the value in a component under the provider with a custom hook but it's reported as being undefined. I've gone through the questions on SO and have verified my syntax with the lesson in my book but can't find the problem.
This is my custom provider and custom hook:
import React, { createContext, useState, useEffect, useContext } from 'react';
const ApiContext = createContext();
export const useApi = () => useContext(ApiContext);
export const ApiProvider = ({ children }) => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState();
const [baseImageUrl, setBaseImageUrl] = useState();
const apiKey = 'api_key=SECRET';
const baseUrl = 'https://api.themoviedb.org/3';
const objToParams = (obj) => {
let params = '';
if(obj) {
const keys = Object.keys(obj);
for(let key of keys) {
params += `&${key}=${encodeURIComponent(obj[key])}`;
}
}
return params;
}
const api = {
get: async (path, params) => {
const resp = await fetch(baseUrl + path + '?' + apiKey + objToParams(params));
return await resp.json();
}
}
useEffect( () => {
try {
setLoading(true);
const config = api.get('/configuration');
console.log(config);
config.images && setBaseImageUrl(config.images.secure_base_url);
}
catch (error) {
console.error(error);
setError(error);
}
finally {
setLoading(false);
}
}, []);
if( loading ) {
return <p>Loading...</p>;
}
if( error ) {
return <pre>{JSON.stringify(error, null, 2)}</pre>;
}
return (
<ApiContext.Provider value={{ api, baseImageUrl }}>
{ children }
</ApiContext.Provider>
);
}
and this is the component where I access the value through the custom hook:
import React, { useState } from 'react';
import { ApiProvider, useApi } from './components/context/ApiProvider';
import Header from './components/Header';
import Main from './components/Main';
import Footer from './components/Footer';
import './App.css';
const App = () => {
const [searching, setSearching] = useState(false);
const [searchResults, setSearchResults] = useState([])
const [searchError, setSearchError] = useState();
const {api} = useApi();
const onSearch = (query) => {
try {
setSearching(true);
setSearchResults(api.get('/search/multi', {query: encodeURIComponent(query)} ));
console.log(searchResults);
}
catch (error) {
console.error(error);
setSearchError(error);
}
finally {
setSearching(false);
}
}
return (
<ApiProvider>
<div className="main-layout">
<Header onSearch={ onSearch }/>
<Main
searching={ searching }
searchError={ searchError }
searchResults={ searchResults }
/>
<Footer />
</div>
</ApiProvider>
);
}
export default App;
You can't consume the context in the component where you apply it.
<ComponentA>
<Context.Provider value={"somethong"} >
<ComponentB/>
</Context.Provider>
</ComponentA>
In the above example, only ComponentB can consume the value. ComponentA can't.
If you wan't to consume the value in your App component, it has to be the child (or grandchild ...) of the ContextProvider.
<Context.Provider value={"somethong"} >
<App/>
</Context.Provider>
If I understand your code correctly than you are trying to consume the context in your App, while also returning the provider for the same context.
While trying to use TMDB API in my project I ran into an issue that I am unable to figure out. I use copies of the same code as shown below in two different files and functions - one works, and the other one returned undefined for some reason. Can you please point out what I am not doing right, I need fresh new eyes on this. Thank you
import Head from 'next/head';
import React from 'react';
import { useState, useEffect } from 'react';
import Link from 'next/link';
import styles from '../styles/Home.module.css';
export const getServerSideProps = async () => {
const movieApi = process.env.TMDB_API_KEY;
const res = await fetch(`https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=${movieApi}&page=1`);
const movie_data = await res.json();
return {
props: {
movies : movie_data
},
}
}
const Form = ({movies}) => {
console.log(movies); //returns "Undefined"
const [search, Setsearch] = useState("");
//Handle input value
const getLocation = async (e) => {
// console.log(e.target.value)
e.preventDefault();
}
//Handle Submit
const handleSubmit = (event) =>{
// console.log("clicked")
event.preventDefault();
}
export const getServerSideProps = async () => {
const movieApi = process.env.TMDB_API_KEY;
const res = await fetch(`https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=${movieApi}&page=1`);
const movie_data = await res.json();
return {
props: {
movies : movie_data
},
}
}
export default function Home({movies}) {
console.log(movies); //works perdectly
const [session, loading] = useSession();
const tmdbMpviesResults = movies.results
As per your comment, <Form /> is not a page. Exactly that is your problem:
getServerSideProps can only be exported from a page. You can’t export it from non-page files.
I'm creating React context but it returns a promise. In the file playlistcontext.js I've the following code:
import React, { useEffect } from 'react';
import YouTube from '../services/youtube';
const playlistsData = YouTube.getPlaylists();
// console.log(playlistsData);
const PlaylistsDataContext = React.createContext(playlistsData);
const PlaylistsDataProvider = (props) => {
const [playlists, setPlaylists] = React.useState(playlistsData);
useEffect(() =>{
const playlistsData = YouTube.getPlaylists();
console.log(playlistsData);
setPlaylists(playlistsData);
},[])
return <PlaylistsDataContext.Provider value={[playlists, setPlaylists]}>{props.children}</PlaylistsDataContext.Provider>;
}
export {PlaylistsDataContext, PlaylistsDataProvider};
In the file youtube.js, that I use it like a service, I'have the code below. In this function a console.log(result.data) return me the correct data.
import axios from 'axios';
import { YOUTUBE_API } from '../config/config';
function Youtube() {
const handleError = (resp) => {
let message = '';
switch (+resp.status) {
case 401:
message = resp.data.error;
break;
default:
message = 'general error';
}
return message;
}
const getPlaylists = async () => {
try {
const result = await axios.get(YOUTUBE_API + '');
return result.data;
} catch(e) {
return Promise.reject(handleError(e.response));
}
}
return {
getPlaylists
}
}
const ytMethod = Youtube();
export default ytMethod;
then, I have a containers "tutorialcontainer.js" in which I've wrapped a component:
import React, {useState} from 'react';
import { PlaylistsDataProvider } from '../containers/playlistscontext';
import Tutorials from '../components/tutorials';
const TutorialsContainer = (props) => {
return (
<PlaylistsDataProvider>
<Tutorials />
</PlaylistsDataProvider>
);
}
export default TutorialsContainer;
In the last file tutorials.js I have the component. In this file the console.log(playlist) returns me a promise.
import React, {useState, useEffect} from 'react';
import SectionBoxPlaylist from '../components/html_elements/card_playlist';
import Header from '../components/header';
import { PlaylistsDataContext } from '../containers/playlistscontext';
const Tutorials = (props) => {
const [playlists, setPlaylists] = React.useContext(PlaylistsDataContext);
return (
<div className="app-container">
<Header />
<div className="section section-one text-center">
<div className="section-content">
<div className="section-box-items">
{
Object.keys(playlists).map((item) => {
return <SectionBoxPlaylist key={item} id={item} info={playlists[item]} />
})
}
</div>
</div>
</div>
</div>
);
}
export default Tutorials;
Can you help and explain me why?
Thank you!
setPlaylists is called immediately after YouTube.getPlaylists().
useEffect(() => {
const playlistsData = YouTube.getPlaylists();
console.log(playlistsData); // playlistsData is not fetched
setPlaylists(playlistsData);
},[])
You should be able to use .then():
YouTube.getPlaylists().then(response => {
console.log(response);
setPlaylists(response);
});
You can also create async function inside useEffect():
useEffect(() => {
const getYTPlaylist = async () => {
const playlistsData = await YouTube.getPlaylists();
console.log(playlistsData);
setPlaylists(playlistsData);
}
getYTPlaylist();
},[])
The main gold is to make a serch bar from an external API. I'm using Context API to provide a global state, and a custom async hook to make a call to a pokeapi, I'm currently available, to store the data searched in localstorage, but the thing is that I store that data from a state that changes in a event, so when I reload the page the state is undefined, and sets the local storage value to undefined... there is a better approach to solve this?
context:
import React,{createContext, useEffect} from 'react'
import { usePokemonReducer } from './PokemonReducer'
import {FIND_POKEMON} from './Actions'
export const PokemonContext = createContext()
const PokemonProvider = ({children}) => {
const [state, dispatch] = usePokemonReducer(()=>{
const localData = localStorage.getItem('pokemons');
return localData ? JSON.parse(localData) : [];
});
const { pokemon } = state;
const findPokemon = (pokemon) => dispatch({ type: FIND_POKEMON, pokemon})
useEffect(() => {
localStorage.setItem('pokemons', JSON.stringify(pokemon.pokemon));
}, [pokemon]);
const providerValues = {
pokemon,
findPokemon,
}
return (
<PokemonContext.Provider value={providerValues}>
{children}
</PokemonContext.Provider>
)
}
export default PokemonProvider;
customAsyncHook:
import {useEffect, useState, useContext} from 'react'
import { PokemonContext } from '../../Services/Store/PokemonContext'
import {FIND_POKEMON} from '../../Services/Store/Actions'
import axios from 'axios'
const useAsyncHook = (id) => {
const [result, setResult] = useState();
const [loading, setLoading] = useState('false');
const { findPokemon } = useContext(PokemonContext)
useEffect(() => {
async function getPokemon() {
try {
setLoading('true');
const response = await axios(
`https://pokeapi.co/api/v2/pokemon/${id}`
);
setResult(response.data);
findPokemon({type:FIND_POKEMON, pokemon:response.data });
} catch (error) {
setLoading('null');
findPokemon({type:FIND_POKEMON, pokemon:null });
}
}
if (id !== "") {
getPokemon();
}
}, [id]);
return [result, loading];
}
export default useAsyncHook
You can just use if condition. if pokemon is undefined, you don't need to set item to localStorage.
useEffect(() => {
if (pokemon.pokemon !== undefined) {
localStorage.setItem('pokemons', JSON.stringify(pokemon.pokemon));
}
}, [pokemon]);