UPDATE: its working. problem was from useEffect
When I try to 'submit' a specific task, it is shown in the local storage that the status attribute is not changed. I wonder where I am going wrong. The data i am trying to use to update the local storage is initialState.stuff .
Here is my code for reducer.js:
const reducer = (state,action)=>{
if(action.type ==="completed"){
//the relevant attributes to be changed
let temp = state.stuff.map((task)=>{
if(task.id === action.payload){
return {...task,status:status,enddate:enddate}
}
return task;
});
// the rest of the parts of this code is irrelevant
here is my code for context.js:
import React, {useState,useContext,useEffect,useReducer} from "react"
import reducer from "./reducer";
const AppContext = React.createContext()
const initialState = {
stuff: null
}
const AppProvider = ({children}) =>{
const [tasks,setTasks] = useState([])
const [status,changeStatus] = useState("");
const [state,dispatch] = useReducer(reducer,initialState)
const complete = (id) =>{
dispatch({type:"completed",payload:id})
console.log("done", initialState.stuff)
setTasks(initialState.stuff)
}
const deleted = (id) =>{
dispatch({type:"deleted",payload:id})
}
const getLocalStorage = () =>{
state.stuff = localStorage.getItem("tasks")
if(state.stuff){
state.stuff = JSON.parse(state.stuff)
return JSON.parse(localStorage.getItem("tasks"))
}
else{
return []
}
}
const fetchData = async()=>{
setLoading(true)
setTasks(getLocalStorage());
setLoading(false);
}
useEffect(()=>{
fetchData()
},[])
useEffect(()=>{
localStorage.setItem("tasks",JSON.stringify(tasks))
},[tasks])
return <AppContext.Provider
value = {{
loading,
tasks,setTasks
}}>
{children}
</AppContext.Provider>
}
export const useGlobalContext = () =>{
return useContext(AppContext)
}
export {AppContext,AppProvider}
I think there might be (I have not read your code completely) variable collision in your code. You can try writing window.localStorage instead of localStorage and check whether it solves your problem.
Related
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.
This is going to be really hard to explain, but here goes. I am building a React card grid with a filter. The data is pulled from an MySQL AWS API I built. The .tags property is JSON with an array that stores each tag associated with the card. I have written Javascript in App.jsx to turn this JSON into an object, and then store every unique tag in a piece of state. See code below:
//App.jsx
import { useEffect, useState } from 'react';
import '../assets/css/App.css';
import Card from './Card';
import Filter from './Filter'
import {motion, AnimatePresence} from 'framer-motion'
function App() {
const [cards, setCards] = useState([]);
const [filter, setFilter] = useState([]);
const [activeFilter, setActiveFilter] = useState("all");
const [tags,setTags] = useState([]);
useEffect(() => {
fetchData();
}, []);
/*useEffect(() => {
console.log(tags);
console.log(activeFilter);
}, [activeFilter,tags]);
*/
const getTags = () => {
let tags = [];
cards.forEach((card) => {
let obj = JSON.parse(card.tags);
obj.forEach((tag) => {
if (!tags.includes(tag)) {
tags.push(tag);
}
});
});
setTags(tags);
}
const fetchData = async () => {
const data = await fetch("<<api>>");
const cards = await data.json();
setCards(cards);
setFilter((cards));
getTags();
}
return (
<div className="App">
<Filter
cards={cards}
setFilter={setFilter}
activeFilter={activeFilter}
setActiveFilter={setActiveFilter}
/>
<motion.div layout className="Cards">
<AnimatePresence>
{filter.map((card) => {
return <Card key={card.id} card={card}/>;
})}
</AnimatePresence>
</motion.div>
</div>
);
}
export default App;
The problem that I am having is that when I run the app initially, the tags state is empty when inspecting from React Dev tools. However, when I keep the app running, and then add something like a console.log(tags); before setTags(tags) is called in the getTags() function, the data suddenly appears in the state. If someone could explain why the state seems to be empty even though I am updating it on the initial render that would be really appreciated.
You are running getTags on empty array. setCards doesn't set the const variable instantly. New values will be present in the next render cycle.
Try adding cards param
const getTags = (cards) => {
let tags = [];
cards.forEach((card) => {
let obj = JSON.parse(card.tags);
obj.forEach((tag) => {
if (!tags.includes(tag)) {
tags.push(tag);
}
});
});
setTags(tags);
}
And use it like this:
const fetchData = async () => {
const data = await fetch("API url");
const cards = await data.json();
setCards(cards);
setFilter((cards));
getTags(cards);
}
I'm trying to fetch data from Firebase, I implemented nearly same codes like my react native app but this time I'm facing some problems that I couldn't figure out.
import {db} from './firebase'
const formatMarketData = (data) =>{
let formattedData = [];
data.forEach(item=>{
const formattedItem = {
...item
}
formattedData.push(formattedItem);
});
return formattedData;
}
export const FirebaseService = async (id) => {
id=id.replace(/['"]+/g, "");
const historyKey="/coins/0/"+id
await
db.ref(historyKey)
.once('value')
.then(snapshot => {
const data = snapshot.val();
const formattedResponse= formatMarketData(data);
console.log(formattedResponse)
return formattedResponse
});
};
This function helps me to retrieve data from my firebase rtdb. The console.log functions works and writes as expected but when I'm trying to catch it in my News page it becomes undefined.
import React from 'react'
import { useState,useEffect } from 'react'
import { FirebaseService } from '../../Services/FirebaseService'
const NewList = ({id}) => {
const [data,setData] = useState([])
useEffect(()=>{
console.log(id)
const fetchMarketData = async () =>{
const marketData = await FirebaseService(id);
return setData(marketData);
}
fetchMarketData();
return () => {
}
},[])
return (
<div>
{data!==undefined?data.map((d)=>{return(<p>{d.id}</p>)})
:<p>no data yet</p>}
</div>
)
}
export default NewList
I'm missing something but I can't notice. Another view might be helpful.
Define formattedResponse at start of function and return it at very end.
I have edited you function
import {db} from './firebase'
const formatMarketData = (data) =>{
let formattedData = [];
data.forEach(item=>{
const formattedItem = {
...item
}
formattedData.push(formattedItem);
});
return formattedData;
}
export const FirebaseService = (id) =>{
let formattedResponse = [];
id=id.replace(/['"]+/g, "");
const historyKey="/coins/0/"+id
await
db.ref(historyKey)
.once('value')
.then(snapshot => {
const data = snapshot.val();
formattedResponse=
formatMarketData(data);
console.log(formattedResponse)
})
return formattedResponse
};
My goal is to use custom hooks created from Context to pass and modify stored values
The final goal is to use something like useFeedContext() to get or modify the context values
What I am actually getting is either the functions that I call are undefined or some other problem ( I tried multiple approaches)
I tried following this video basics of react context in conjunction with this thread How to change Context value while using React Hook of useContext but I am clearly getting something wrong.
Here is what I tried :
return part of App.js
<FeedProvider mf={/* what do i put here */}>
<Navigation>
<HomeScreen />
<ParsedFeed />
<FavScreen />
</Navigation>
</FeedProvider>
Main provider logic
import React, { useState, useEffect, useContext, useCallback } from "react";
import AsyncStorage from "#react-native-async-storage/async-storage";
const FeedContext = React.createContext();
const defaultFeed = [];
const getData = async (keyName) => {
try {
const jsonValue = await AsyncStorage.getItem(keyName);
return jsonValue != null ? JSON.parse(jsonValue) : null;
} catch (e) {
console.log(e);
}
};
const storeData = async (value, keyName) => {
console.log(value, keyName);
try {
const jsonValue = JSON.stringify(value);
await AsyncStorage.setItem(keyName, jsonValue);
} catch (e) {
console.log(e);
}
};
export const FeedProvider = ({ children, mf }) => {
const [mainFeed, setMainFeed] = useState(mf || defaultFeed);
const [feedLoaded, setFeedLoaded] = useState(false);
let load = async () => {
let temp = await AsyncStorage.getItem("mainUserFeed");
temp != null
? getData("mainUserFeed").then((loadedFeed) => setMainFeed(loadedFeed))
: setMainFeed(defaultFeed);
setFeedLoaded(true);
};
useEffect(() => {
load();
}, []);
useCallback(async () => {
if (!feedLoaded) {
return await load();
}
}, [mainFeed]);
const setFeed = (obj) => {
setMainFeed(obj);
storeData(mainFeed, "mainUserFeed");
};
return (
<FeedContext.Provider value={{ getFeed: mainFeed, setFeed }}>
{children}
</FeedContext.Provider>
);
};
//export const FeedConsumer = FeedContext.Consumer;
export default FeedContext;
The custom hook
import { useContext } from "react";
import FeedContext from "./feedProviderContext";
export default function useFeedContext() {
const context = useContext(FeedContext);
return context;
}
What I would hope for is the ability to call the useFeedContext hook anywhere in the app after import like:
let myhook = useFeedContext()
console.log(myhook.getFeed) /// returns the context of the mainFeed from the provider
myhook.setFeed([{test:1},{test:2}]) /// would update the mainFeed from the provider so that mainFeed is set to the passed array with two objects.
I hope this all makes sense, I have spend way longer that I am comfortable to admit so any help is much appreciated.
If you want to keep using your useFeedContext function, I suggest to move it into the your 'Provider Logic' or I'd call it as 'FeedContext.tsx'
FeedContext.tsx
const FeedContext = createContext({});
export const useFeedContext = () => {
return useContext(FeedContext);
}
export const AuthProvider = ({children}) => {
const [mainFeed, setMainFeed] = useState(mf || defaultFeed);
...
return (
<FeedContext.Provider value={{mainFeed, setMainFeed}}>
{children}
</FeedContext.Provider>
);
};
YourScreen.tsx
const YourScreen = () => {
const {mainFeed, setMainFeed} = useFeedContext();
useEffect(() => {
// You have to wait until mainFeed is defined, because it's asynchronous.
if (!mainFeed || !mainFeed.length) {
return;
}
// Do something here
...
}, [mainFeed]);
...
return (
...
);
};
export default YourScreen;
I'm exploring hooks with react-redux-firebase but my "setDataProducts" is behaving oddly.
I'm using useEffect() like I could use componentDidMount() but not sure if this is the right way.
export default function ProductList() {
const [dataProducts, setDataProducts] = useState([]);
const firestore = useFirestore();
const fetchProducts = async () => {
const response = firestore.collection("products");
const data = await response.get();
data.docs.forEach((product) => {
setDataProducts([...dataProducts, product.data()]);
console.log(product.data());
});
};
useEffect(() => {
fetchProducts();
}, []);
return (
<div>
{isLoaded &&
dataProducts.map((product) => {
return (
<div>
<h4>{product.title}</h4>
<h3>{product.price}</h3>
</div>
);
})}
</div>
);
}
I cannot render the both products I have in Firestore. Only One is rendering... So I dont understand. Should not it rerender when state is updated ?
Thanks for reply
We can see there was not rerendering
I think it is because you called setDataProducts again before dataProducts updated.
Please replace fetchProducts method with my code following:
const fetchProducts = async () => {
const response = firestore.collection("products");
const data = await response.get();
const newProducts = data.docs.map((product) => product.data());
console.log(newProducts);
setDataProducts([...dataProducts, ...newProducts]);
};