How to load dynamically data from localStorage in React JS? - javascript

I'm trying to display data stored in localstorage dynamically, meaning when data is added to localstorage it should be also displayed on the same page at the same time. I have the following code:
const [peopleInfo, getPeopleInfo] = useState(
JSON.parse(localStorage.getItem("peopleInfo"))
);
const [objects, getObjectsList] = useState(
JSON.parse(localStorage.getItem("objects"))
);
const setPeopleInfo = async () => {
const people = await JSON.parse(localStorage.getItem("peopleInfo"));
getPeopleInfo(people);
};
const getObjects = async () => {
const object = await JSON.parse(localStorage.getItem("objects"));
getObjectsList(object);
};
useEffect(() => {
let isSubscribed = true;
if (isSubscribed) {
setPeopleInfo();
getObjects();
}
return () => (isSubscribed = false);
});
When useEffect doesn't include getObject() like this:
useEffect(() => {
let isSubscribed = true;
if (isSubscribed) {
setPeopleInfo();
}
return () => (isSubscribed = false);
});
code works fine, peopleInfo gets updated every time when localStorage changes, but when I include getObjects() and other similar functions to get all the necessary data, app crashes without showing errors. I don't know the reason why. When I use useEffect with empty [], data doesn't get updated on localStorage change unless I refresh the page (this is not what I want). Can anyone please suggest what to do?

According to your code useEffect call, every time when your state changes, if you are using [] as your dependency useEffect call only one time but I realize when you call getObjects() function in useEffect() it changes the state every time so your state changes infinity times
import React,{useState,useEffect} from "react";
import "./style.css";
export default function App() {
const [objects, getObjectsList] = useState([{ name: "object1" }]);
useEffect(() => {
getObjectsList([{ name: "object1" }])
console.log("loop executed");
});
return <div />;
}
so I just change some code
import React, { useState, useEffect} from "react";
import "./style.css";
export default function App() {
const [objects, getObjectsList] = useState([{ name: "object1" }]);
useEffect(() => {
setInterval(() => {
const localStorageValue = [{ name: "object2" }]; //JSON.parse(localStorage.getItem("objects"));
if (JSON.stringify(localStorageValue) !== JSON.stringify(objects)) {
getObjectsList(localStorageValue);
}
}, [1000]);
}, []);
return <div />;
}
in this code, setInterval check every time if your local storage changes it update your state

Related

How to use data of an Async function inside a functional component to render HTML in React

I've been trying to use the data I get from an Async function inside of another function I use to display HTML on a react project. I have made several attempts but nothing seems to work for me. Hope any of you could help me. Please correct me if I did anything wrong.
I've tried it with a useEffect as well:
import React, { useState, useEffect } from 'react';
import { getGenres } from './api/functions';
const ParentThatFetches = () => {
const [data, updateData] = useState();
useEffect(() => {
const getData = async () => {
const genres = await getGenres('tv');
updateData(genres);
}
getData();
}, []);
return data && <Screen data={data} />
}
const Screen = ({data}) => {
console.log({data}); //logs 'data: undefined' to the console
return (
<div>
<h1 className="text-3xl font-bold underline">H1</h1>
</div>
);
}
export default Screen;
The Error I get from this is: {data: undefined}.
The getGenres function that makes the HTTP Request:
const apiKey = 'key';
const baseUrl = 'https://api.themoviedb.org/3';
export const getGenres = async (type) => {
const requestEndpoint = `/genre/${type}/list`;
const requestParams = `?api_key=${apiKey}`;
const urlToFetch = baseUrl + requestEndpoint + requestParams;
try {
const response = await fetch(urlToFetch);
if(response.ok) {
const jsonResponse = await response.json();
const genres = jsonResponse.genres;
return genres;
}
} catch(e) {
console.log(e);
}
}
I want to use the data inside my HTML, so the H1 for example.
Once again, haven't been doing this for a long time so correct me if I'm wrong.
There are a few conceptual misunderstandings that I want to tackle in your code.
In modern React, your components should typically render some type of jsx, which is how React renders html. In your first example, you are using App to return your genres to your Screen component, which you don't need to do.
If your goal is to fetch some genres and then ultimately print them out onto the screen, you only need one component. Inside that component, you will useEffect to call an asynchronous function that will then await the api data and set it to a react state. That state will then be what you can iterate through.
When genres is first rendered by react on line 6, it will be undefined. Then, once the api data is retrieved, React will update the value of genre to be your array of genres which will cause the component to be re-rendered.
{genres && genres.map((genre) ... on line 20 checks to see if genres is defined, and only if it is, will it map (like looping) through the genres. At first, since genres is undefined, nothing will print and no errors will be thrown. After the genres are set in our useEffect hook, genres will now be an array and we can therefore loop through them.
Here is a working example of your code.
import React, { useState, useEffect } from "react";
import { getGenres } from "./api/functions";
function App() {
const [genres, setGenres] = useState();
useEffect(() => {
async function apiCall() {
const apiResponse = await getGenres("tv");
console.log(apiResponse);
setGenres(apiResponse);
}
apiCall();
}, []);
return (
<div>
<h1 className="text-3xl font-bold underline">H1</h1>
{genres && genres.map((genre) => <div key={genre}>{genre}</div>)}
</div>
);
}
export default App;
You should use a combination or useEffect and useState
You should use useEffect to launch the async get, without launching it each rerendering. If a async function is used to get some data from outside the component, this is called 'side effect' so use useEffect.
You should use useState to react to changes on theses side effects, re-rendering the component to get the data in the dom.
In the next example, Im using a dummy async function getGenres which returns an array of genres.
Here is an example and a WORKING EXAMPLE :
const {useState, useEffect} = React;
async function getGenres() {
var promise = new Promise(function(resolve, reject) {
window.setTimeout(function() {
resolve( ['genre1', 'genre2']);
});
});
return promise;
}
const Screen = () => {
const [genres, setGenres] = useState([])
useEffect(
() => {
getGenres().then(
res => setGenres(res)
)
}, [getGenres]
)
return (
<ul>
{
genres.map(
i => <li>{i}</li>
)
}
</ul>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<Screen/>)

React render component only for few seconds

In my existing react component, I need to render another react component for a specific time period.
As soon as the parent component mounts/or data loads, the new-component (or child component) should be visible after 1-2 seconds and then after another few seconds, the new-component should be hidden. This needs to be done only if there is no data available.
This is what currently I've tried to achieve:
import React, { useState, useEffect } from "react";
function App() {
const [showComponent, setShowComponent] = useState(false);
const sampleData = [];
useEffect(() => {
if (sampleData.length === 0) {
setTimeout(() => {
setShowComponent(true);
}, 1000);
}
}, [sampleData]);
useEffect(() => {
setTimeout(() => {
setShowComponent(false);
}, 4000);
}, []);
const componentTwo = () => {
return <h2>found error</h2>;
};
return <>First component mounted{showComponent && componentTwo()}</>;
}
export default App;
The current implementation is not working as expected. The new-component renders in a blink fashion.
Here is the working snippet attached:
Any help to resolve this is appreciated!
Every time App renders, you create a brand new sampleData array. It may be an empty array each time, but it's a different empty array. Since it's different, the useEffect needs to rerun every time, which means that after every render, you set a timeout to go off in 1 second and show the component.
If this is just a mock array that will never change, then move it outside of App so it's only created once:
const sampleData = [];
function App() {
// ...
}
Or, you can turn it into a state value:
function App() {
const [showComponent, setShowComponent] = useState(false);
const [sampleData, setSampleData] = useState([]);
// ...
}
I have modified the code to work, hope this how you are expecting it to work.
import React, { useState, useEffect } from "react";
const sampleData = [];
// this has to be out side or passed as a prop
/*
reason: when the component render (caued when calling setShowComponent)
a new reference is created for "sampleData", this cause the useEffect run every time the component re-renders,
resulting "<h2>found error</h2>" to flicker.
*/
function App() {
const [showComponent, setShowComponent] = useState(false);
useEffect(() => {
if (sampleData.length === 0) {
const toRef = setTimeout(() => {
setShowComponent(true);
clearTimeout(toRef);
// it is good practice to clear the timeout (but I am not sure why)
}, 1000);
}
}, [sampleData]);
useEffect(() => {
if (showComponent) {
const toRef = setTimeout(() => {
setShowComponent(false);
clearTimeout(toRef);
}, 4000);
}
}, [showComponent]);
const componentTwo = () => {
return <h2>found error</h2>;
};
return <>First component mounted{showComponent && componentTwo()}</>;
}
export default App;
You can try this for conditional rendering.
import { useEffect, useState } from "react";
import "./styles.css";
const LoadingComponent = () => <div>Loading...</div>;
export default function App() {
const [isLoading, setLoading] = useState(true);
const [isError, setIsError] = useState(false);
const onLoadEffect = () => {
setTimeout(() => {
setLoading(false);
}, 2000);
setTimeout(() => {
setIsError(true);
}, 10000);
};
useEffect(onLoadEffect, []);
if (isLoading) {
return <LoadingComponent />;
}
return (
<div className="App">
{isError ? (
<div style={{ color: "red" }}>Something went wrong</div>
) : (
<div>Data that you want to display</div>
)}
</div>
);
}
I needed to do imperatively control rendering an animation component and make it disappear a few seconds later. I ended up writing a very simple custom hook for this. Here's a link to a sandbox.
NOTE: this is not a full solution for the OP's exact use case. It simply abstracts a few key parts of the general problem:
Imperatively control a conditional render
Make the conditional "expire" after duration number of milliseconds.

React Hook useEffect has a missing dependency: 'getContacts'

Before posting the below, I have reviewed similar posts on stackoverflow but none resolved my issue.
I'm new to react and fetching data from firestore database. The below code works as required but getting this prompt within react
import React, {useState, useEffect} from 'react'
import {db} from '../firebase'
const ListRecord = () => {
const [details, setDetails] = useState([]);
useEffect(() => {
getContacts()
},[]);
const getContacts = async() => {
await db.collection('contacts').get().then((querySnapshot) => {
let arr = []
querySnapshot.forEach((doc) => {
arr.push({id: doc.id, value: doc.data()})
});
setDetails(arr);
});
console.log(details);
return details
}
return (
<div>
<h2>List Contact Details</h2>
</div>
)
}
export default ListRecord
As per other similar posts I tried moving the getContacts function inside useEffect body which make the prompt disapper but the getContacts function goes in a continuous loop.
I'm not sure what I'm missing here and any help would be appreciated.
There are different potential solutions:
1. Move getContacts() inside the useEffect() hook:
If you call getContacts() only once and only when the component mounts for the first time, this is probably the most logic solution.
useEffect(() => {
const getContacts = async () => {
await db.collection('contacts').get().then((querySnapshot) => {
let arr = []
querySnapshot.forEach((doc) => {
arr.push({
id: doc.id,
value: doc.data()
})
});
setDetails(arr);
});
//console.log(details);
//return details // why are you returning details?
}
getContacts()
}, [setDetails]); // setDetails() is granted to never change therefore the hook will never re-run
or, of course, you can use an IIFE:
useEffect(() => {
(async function() {
// ... same body as getContacts
})()
}, [setDetails])
2. Use a useCallback() hook:
This is something you might want to do if getContacts() is called more than once (for example, when the component mounts and every time some prop changes or when you click on some button)
const getContacts = useCallback(async () => {
await db.collection('contacts').get().then((querySnapshot) => {
let arr = []
querySnapshot.forEach((doc) => {
arr.push({
id: doc.id,
value: doc.data()
})
});
setDetails(arr);
});
//console.log(details);
//return details // why are you returning details?
}, [setDetail]); // setDetails() is granted to never change therefore getContacts() will never be re-created
useEffect(() => {
getContacts()
}, [getContacts]); // as getContacts() never changes, this will run only once
3. Move getContacts() out of the component and make it an independent function:
This can make sense if you want to reuse the same logic into other components:
// getContacts.js file
// OR this code could be in the ListRecord.js file but **outside** the component,
// although, in this case, solutions (1) or (2) would make more sense
import { db } from 'path/to/firebase'
export async function getContacts() {
await db.collection('contacts').get().then((querySnapshot) => {
let arr = []
querySnapshot.forEach((doc) => {
arr.push({
id: doc.id,
value: doc.data()
})
});
return arr; // this time you HAVE TO return arr
});
}
// ListRecord.js file
import React, { useState, useEffect } from 'react';
import { getContacts } from 'path/to/getContacts.js';
const ListRecord = () => {
const [details, setDetails] = useState([]);
useEffect(async () => {
const arr = await getContacts();
if (arr && arr.length > 0) setDetails(arr);
}, [setDetails]);
//...
}
I suggest you have a look at how useEffect and its dependency list works in the official document.
In short, do the following:
useEffect(() => {
getContacts()
}, [getContacts]);
This means when getContacts changes, the useEffect will be re-run.

remove items in local storage based on the cms data updates

I need to remove an item from the localStorage based on the fact that it was deleted from the CMS data.
So, I need to access cmsData collectionId in my useEffect() hook and give to each of the collections a new state. I have an access to the IDs though cmsData but I'm not sure how should I check for changes and assign a new state to each of collections in the useEffect() hook.
(Now my code just resets the whole localStorage on the reload.)
Could you give me a hint?
import React = require("react");
import {PropsWithChildren, useEffect, useState} from "react";
import {CollectionId} from "../../DataTypes";
import {ExpandingContext, ExpandingInfo} from "../../Expanding";
import {useCmsData} from "../../api/CmsDataProvider";
function ExpandControllerComponent (props: PropsWithChildren<any>) {
const [state, setState] = useState(() => getPersistState())
const cmsData = useCmsData();
useEffect(() => {
cleanUpPersistState()
}, [])
const expandingInfo: ExpandingInfo = {
state: state,
toggleExpand: collectionId => {
setState((state) => {
const isExpanded = expandingInfo.state.get(collectionId) ?? true
state.set(collectionId, !isExpanded)
const newState = new Map<CollectionId, boolean>(state)
persistState(newState)
return newState
})
},
}
return (
<ExpandingContext.Provider value={expandingInfo}>
{props.children}
</ExpandingContext.Provider>
)
}
const persistenceKey = "expandingState"
function persistState (state: Map<CollectionId, boolean>) {
const json = JSON.stringify(Array.from(state.entries()))
localStorage.setItem(persistenceKey, json)
}
function getPersistState (): Map<CollectionId, boolean> {
const json = localStorage.getItem(persistenceKey)
return new Map(JSON.parse(json))
}
function cleanUpPersistState () {
localStorage.removeItem(persistenceKey);
}
export = ExpandControllerComponent
You can check for changes in useEffect Hook by useEffect({},[cmsData]), if cmsData gets some value assigned or updated it the useEffect will run. Be careful useEffect will also run on page load and only run when cmsData values changes. Further more you can also listen for other variable by useEffect({}, [cmsData, otherVar, etc]) by this way useEffect will also listen for those variables and run when any of the values provided in the array changes.

State updates by itself in React

I want to build a small quiz app in React using jservice.io
However, I can't find a solution for this problem: after I fetch random question in updateQuestion(), somehow, data fetches again (or changes in some other way) when I call checkAnswer(), even though it's not needed and not called.
The code:
import React, { useState, useEffect } from 'react';
import './App.css';
function App() {
const [question, setQuestion] = useState("");
const [answer, setAnswer] = useState("");
useEffect(() => {
updateQuestion()
}, [])
async function updateQuestion () {
await fetch(`http://jservice.io/api/random`)
.then(data => data = data.json())
.then(function (response) {
setQuestion(response[0].question)
setAnswer(response[0].answer)
console.log("fetched")
} )
const title = document.querySelector(".question_title")
title.innerText = question
console.log(`${question} - when update question`)
console.log(`${answer} - when update question`)
}
function checkAnswer () {
console.log(`${question} - when check answer`)
console.log(`${answer} - when check answer`)
}
}
It is hard to say without seeing how you are calling checkAnswer or a valid code example. However, I've tried to reproduce what I presume is a comparable example that works as I believe you expect it should:
import React, { useState, useEffect } from "react";
function App() {
const [question, setQuestion] = useState("");
const [answer, setAnswer] = useState("");
function updateQuestion() {
// Fake fetch
new Promise(resolve => {
const data = [
{
answer: "to winnow",
question: "To sift through material \u0026 toss out the junk"
}
];
resolve(data[0]);
}).then(response => {
setQuestion(response.question);
setAnswer(response.answer);
});
console.log(`${question} - when update question`);
console.log(`${answer} - when update question`);
}
function checkAnswer() {
console.log(`${question} - when check answer`);
console.log(`${answer} - when check answer`);
}
useEffect(() => {
console.log("calling useEffect");
updateQuestion();
});
return (
<div>
<h1>{question}</h1>
<p>{answer}</p>
<button onClick={() => checkAnswer()}>Click Me!</button>
</div>
);
}
export default App;
If you need more information, we're going to need more code. :)
Hope that helps!
You triggered it yourself because useEffect is triggered at least twice:
you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.
https://reactjs.org/docs/hooks-effect.html
So it triggers once at componentDidMount, then once again at componentDidUpdate

Categories

Resources