How can I use LocalStorage in a functional component like this
I know how do this in a class component but can I solve this problem in this case?
ERROR: TypeError: repositories is not a function
export default function Main() {
const [newRepo, setNewRepo] = useState('');
const [repositories, setRepositories] = useState([]);
const [clearInput] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
repositories(localStorage.getItem('repositories'));
if (repositories) {
setRepositories(JSON.parse(repositories));
}
}, [repositories]);
useEffect((_, prevState) => {
if (prevState.repositories !== repositories) {
localStorage.setItem('repositories', JSON.stringify(repositories));
}
});
In your first useEffect, the repositories is your state which an array. Not a function.
Also, in your second useEffect you need to make correction to the way you access the prevState in hooks.
Fix for 1st useEffect
export default function Main() {
const [newRepo, setNewRepo] = useState('');
const [repositories, setRepositories] = useState([]);
const [clearInput] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const localRepoItems = localStorage.getItem('repositories');
if (localRepoItems) {
setRepositories(JSON.parse(localRepoItems));
}
}, []); // do not give the dependency as repositories as it will go to infinite loop
});
To obtain previous state in hooks, you can write a little custom hook:
Like this:
export const usePrevious = value => {
const ref = React.useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
Usage in your component:
const prevRepositories = usePrevious(repositories);
useEffect(() => {
if (prevRepositories.length !== repositories.length) {
localStorage.setItem('repositories', JSON.stringify(repositories));
}
}, [repositories]);
Related
**the code below is my context which I am using useFetch **
**when i change the url with changing the searchTerm **
** i am getting an infinite loop **
import React, { useContext, useState, useEffect } from "react";
import { useFetch } from "../hooks/useFetch";
const context = React.createContext();
const AppProvider = ({ children }) => {
let url = " https://www.thecocktaildb.com/api/json/v1/1/search.php?s=";
let [searchTerm, setSearchTerm] = useState("a");
useFetch(`${url}${searchTerm}`);
setSearchTerm('s');
return <context.Provider value={"hello"}>
{children}
</context.Provider >
}
const useGlobal = () => {
return useContext(context);
}
export { AppProvider, useGlobal };
** the code below is my custom hook useFetch**
`
import { useEffect, useState } from "react";
export const useFetch = (url) => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const getData = async () => {
try {
const response = await fetch(url);
const jsonResponse = await response.json();
setData(jsonResponse);
setLoading(false);
} catch (err) {
console.log(err);
}
}
useEffect(() => {
getData();
}, [url])
return { data, loading };
}
`
I tried to change the search Term like this
searchTerm="h"
and it works perfectly but when i change searchTerm with setSearchTerm it gives me infinite loop
setSearchTerm('s'); inside a useEffect
const [url] = useState(" https://www.thecocktaildb.com/api/json/v1/1/search.php?s=");
const [searchTerm, setSearchTerm] = useState("a");
const { data, loading } = useFetch(`${url}${searchTerm}`);
useEffect(() => {
setSearchTerm('s');
}, [])
I developed an application where I get an api (pokeAPI) with pokemons, and basically I have a global array with "myPokemons", so I want to display all my pokemons except the ones in that array, so I created the function "filterMyPokemons" that I filter the pokemons that should be displayed, and then I call this function in useEffect so that it is updated along with the page, putting a dependency array from the API list. The problem is that I now have an infinite loop that hurts the performance of the application.
import * as C from './styles';
import logo from '../../assets/pokelogo.png';
import { useContext, useState } from 'react';
import { useApi } from '../../hooks/useApi';
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import Pokelist from '../../components/PokeList';
import CatchingPokemonIcon from '#mui/icons-material/CatchingPokemon';
import CatchContext from '../../context/Context';
const Homepage = () => {
const api = useApi();
const { showMyPokemons } = useContext(CatchContext);
const navigate = useNavigate();
const [pokemonList, setPokemonList] = useState([]);
const [loading, setLoading] = useState(false);
const [text, setText] = useState('');
const [myPokemonsList, setMyPokemonsList] = useState([]);
const [pokemonListFiltered, setPokemonListFiltered] = useState([]);
useEffect (() => {
const getAllPokemons = async () => {
const myPokemons = await showMyPokemons();
const pokemon = await api.getAllPokemon();
setLoading(true);
setPokemonList(pokemon);
setMyPokemonsList(myPokemons);
setLoading(false);
}
filterMyPokemons();
getAllPokemons();
}, [myPokemonsList]);
const filterMyPokemons = async () => {
const filteredList = await pokemonList.filter(pokemons => !myPokemonsList.includes(pokemons.name))
return setPokemonListFiltered(filteredList);
};
const lowerSearch = text.toLocaleLowerCase();
const filteredPokemons = pokemonListFiltered.filter(pokemon => pokemon
.name.toLowerCase().includes(lowerSearch)
);
const handleHome = () => {
navigate('/')
}
const handleMyPokemons = () => {
navigate('/mypokemons')
}
return (
<C.Container>
<C.Logo>
<img src={logo} alt="" />
</C.Logo>
<C.Navbar>
<input
type="text"
placeholder='Busque um pokémon...'
onChange={(e) => setText(e.target.value)}
value={text}
/>
</C.Navbar>
<C.Pokedatabase onClick={handleMyPokemons}>
<button>Meus pokémons <i><CatchingPokemonIcon /></i></button>
</C.Pokedatabase>
<C.Pokelist>
{filteredPokemons.map(pokemon => {
return (
<Pokelist
name={pokemon.name}
/>
)
})}
</C.Pokelist>
</C.Container>
)
}
export default Homepage;
If I leave useEffect's dependency array empty, the items are not displayed, but if I leave any dependencies it causes an infinite loop. How to solve this problem?
The problem comes with updating the myPokemonsList array within the useEffect hook that depends on that array.
useEffect (() => {
const getAllPokemons = async () => {
const myPokemons = await showMyPokemons();
const pokemon = await api.getAllPokemon();
setLoading(true);
setPokemonList(pokemon);
setMyPokemonsList(myPokemons); // Here's the infinite loop
setLoading(false);
}
filterMyPokemons();
getAllPokemons();
}, [myPokemonsList]); // Here's the infinite loop
You should have another use effect for updates on the myPokemonList in order to avoid updating and depending on the same list.
To load more images, the list should accept the values of images, but it's not working.
Why can't I get the value of the list? (list.length=0 in console)
const { images, imagesLoaded } = useSelector((state: RootState) => state.gallery);
const dispatch = useDispatch();
const [imageUrl, setImageUrl] = useState('');
useEffect(() => {
if(!imagesLoaded) {
dispatch(getImages());
}
// eslint-disable-next-line
}, []);
const [list, setList] = useState([...images.slice(0, 5)])
console.log(images.slice(0, 5))
console.log(list.length)
const [loadMore, setLoadMore] = useState(false)
const [hasMore, setHasMore] = useState(images.length > 5)
const handleLoadMore = () => {
setLoadMore(true)
}
Becasue images only has value after you call dispatch(getImages());
const [list, setList] = useState([...images.slice(0, 5)]) will declare the initial value for list and not update when images update.
if you want update list when images update, you can use useEffect:
useEffect(() => {
setList(images.slice(0, 5))
// eslint-disable-next-line
}, [images]);
Currently, I have a custom fetching data hook written in javascript and it works
import {useState, useEffect} from 'react';
const useApi = apiName => id => {
const [response, setResponse] = useState();
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const fetching = async () => {
setLoading(true);
const data = await fetch(`/api/${apiName}${id ? `/${id}` : ""}`)
.then((x) => x.json())
.catch((error) => setError(error));
setResponse(data);
setLoading(false);
};
useEffect(() => {
fetching();
}, [id]);
return { response, loading, error };
};
Then I can use pass in what api I want to call to get the hook. For examples:
const useCustomer = useApi("customer")
const useHello = useApi("hello")
.....
const {response, loading, error} = useCustomer("id_1")
It works fine.
Then, I try to convert to typescript
const useApi = (apiName:string) => (id?:string) => {
const [response, setResponse] = useState({})
.......
}
and eslint complains that
React Hook "useState" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function
I would like to know whats wrong with this approach, I know I can have something like:
const useApi = (apiName:string, id?:string) => {}
or disable the eslint(react-hooks/rules-of-hooks)
But just curious about whats the potential problems having higher order function of hook since it actually return the response .
Thanks
When you name you function with prefix hooks, eslint thinks of it as a custom hook according to the general convention. Now that implements useState in a nested function which is why it gives you an error
The best way to write the above code is to not use currying function but pass in the apiName as a param directly
const useApi = (apiName, id) => {
const [response, setResponse] = useState();
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const fetching = async () => {
setLoading(true);
const data = await fetch(`/api/${apiName}${id ? `/${id}` : ""}`)
.then((x) => x.json())
.catch((error) => setError(error));
setResponse(data);
setLoading(false);
};
useEffect(() => {
fetching();
}, [id]);
return { response, loading, error };
};
and use it like
.....
const {response, loading, error} = useApi("customer","id_1");
P.S. Hooks are meant to be an alternative to HOC's and there is no point writing a hook if you use it as an HOC itself
There's a much easier way if you don't need the id variable to be in the hook. The reason why you get the warning is because your hooks are in your CB instead of your root function.
Correct Example:
const useApi = (apiName:string) => {
const [response, setResponse] = useState({});
return (id?: string) => {
.......
};
}
States
const [searchTerm, setSearchTerm] = useState("");
const [URL, SetURL] = useState<URL | String>();
const [searchResults, setSearchResults] = useState([]);
resetSearch Function
const resetSearch = () => {
setSearchTerm("");
setSearchResults([]);
inputRef.current.value = ""
}
ResetQuery Function
const resetSearchQueryOnRouteChange = () => {
SetURL(window.location.href)
if (window.location.href != URL) {
resetSearch();
}
}
useEffect
useEffect(() => {
resetSearchQueryOnRouteChange()
}, [window.location.href, URL])
The issue is that i need the the resetSearchQueryOnRouteChange() function to be run on every route render, but when i change routes the function is not called, but the function itself is fully functional.
How can i make the function run when i change route/url?
If you are not using 'react-router' each location change will trigger whole re-render of the page. So, this block of code wouldn't execute.
You need to start to use 'react-router' and you'll get access to location