NextJS localStorage.getItem() method not working on components? - javascript

In my nextjs-app I want to use localstorage, to store some values across my application.
so inside the pages-folder I have a [slug].tsx-file where I do this:
export default function Page({ data}) {
useEffect(() => {
const page = {
title: data.page.title,
subtitle: data.page.subtitle,
slug: data.page.slug,
}
localStorage.setItem("page", JSON.stringify(page))
})
return ( ... some html....)
}
this basically stores the title, subtitle and slug for the current route.
Now, inside my components-folder I have a Nav.tsx-file, where I do this:
const Nav= () => {
const [pageData, setPageData] = useState()
useEffect(() => {
const current = JSON.parse(localStoraget.getItem('page'))
if(current){
setPageData(current)
}
},[])
return(...some html)
}
So far, the setItem works and in the application-tab of the google inspector I can see, that the key-values changes, each time a new route/page gets rendered BUT the getItem- always returns the same e.g. the key values do not change at all. What am I doing wrong? Is it maybe because the Nav component only gets rendered once?
Can someone help me out?

you have a spelling error from:
localStoraget.getItem('page')
to:
localStorage.getItem('page')
believe your issue also falls under localstorage should be used with async/await so maybe try something like:
const Nav= () => {
const [pageData, setPageData] = useState()
useEffect(() => {
async function settingData() {
const current = await JSON.parse(localStorage.getItem('page'))
if(current)setPageData(current)
}
settingData()
},[])
return(...some html)
}

Note: You should avoid using localStorage to share the state over your App. React provides a good way of doing it with ContextAPI or you could use another lib such as Redux/MobX/Recoil.
At the time when the <Nav> component is rendered (and the useEffect runs) the localStorage probably still doesn't have the key-value set.
If you really want to use localStorage (but I suggest not using it), you can create a timeout to execute after some time and will try to get again the value. Something like this could work:
let localStorageTimer = null;
const Nav = () => {
const [pageData, setPageData] = useState()
useEffect(() => {
const getLocalStorageItems = () => {
const current = JSON.parse(localStorage.getItem('page'))
if (!current) {
localStorageTimer = setTimeout(() => getLocalStorageItems, 1000);
} else {
clearTimeout(localStorageTimer)
setPageData(current)
}
}
localStorageTimer = setTimeout(() => getLocalStorageItems, 1000);
return () => clearTimeout(localStorageTimer)
}, []);
return (.. your JSX code)
}

Related

React: Await for API fetch and LocalStorage set before render children components

I have the following app entry component:
React.useEffect(() => {
const fetchData = async () => {
try {
const libraries: unknown[] = await sendRequest('/libraries');
const softwareComponents: unknown[] = await sendRequest('/softwareComponents');
localStorage.setItem('libraries', JSON.stringify(arraySetup(libraries, 'libraries')));
localStorage.setItem('softwareComponents', JSON.stringify(arraySetup(softwareComponents, 'software-components')));
} catch (err) {
console.error(err);
}
};
isAuthenticated() && fetchData();
}, []);
I am fetching Arrays from two endpoints and then set the result in the Local Storage, so I can read from it in other components.
A child component is using the data like this:
const [data, setData] = React.useState<Array<any>>([]);
React.useEffect(() => {
const libraries = getLocalStorageItem('libraries');
const softwareComponents = getLocalStorageItem('softwareComponents');
const condition = libraries && softwareComponents;
if (condition) {
setData([...libraries, ...softwareComponents]);
}
}, []);
const getDataLength = (category: string) => {
return (data || []).filter((item: any) => item.category === category).length;
};
return (
<React.Fragment>
<OwcGrid item xs={12} s={4}>
<LibrariesCard numberOfElements={getDataLength('libraries')} /> // rendering here the length of the localStorage item.
</OwcGrid>
The following bug now exists:
Opening the app for the first time, the API is called and the localstorage is set.
But the child components that are using localStorage are rendered at the same time, so if (condition) setData([...libraries, ...softwareComponents]); is never met and the numberOfElements prop is always empty at the first time.
Only at the second refresh, localStorage is in place and I can count the elements out from it and render it.
Can somebody give me a hint to wait for localStorage.setItem in the App.layout or if I can wait and check in the child components as long as the storage is set and then render again?
Do you have to use local storage? You could solve your problem by using context api, where you can store all required values and use them in any component that you need to. If you have to use local storage, you can manage it from context.
Please checkout documentation for context: https://reactjs.org/docs/context.html

Issues upgrading async componentDidMount() to async useEffect()

// UPDATE: The issue was using the state immediately after setting it inside useEffect(). See my answer HERE for details.
I'm trying to upgrade one of my React app pages from class component to functional component with Hooks. However, I have some issues due to some async functions.
The way the old page behaves is that in componentDidMount() some data is async fetched from the database and displayed. It works properly, myName and myValue are displayed correctly.
// OLD APPROACH - CLASS COMPONENT
class MyPage extends Component {
constructor(props) {
super(props);
this.state = {
myName: null,
myValue: undefined,
}
}
componentDidMount = async () => {
try {
const myName = await getNameFromDatabase();
const myValue = await getValueFromDatabase();
this.setState({ myName, myValue });
} catch (error) {
alert(
"Some errors occured when fetching from DB"
);
console.error(error);
}
}
render() {
return (
<div>
<h1>{this.state.myName}</h1>
<h1>{this.state.myValue}</h1>
</div>
)
}
export default MyPage
I tried to update the page by carefully following this response.
// NEW APPROACH - FUNCTIONAL COMPONENT WITH HOOKS
function MyPage() {
const [myName, setMyName] = useState(null);
const [myValue, setMyValue] = useState(undefined);
useEffect(() => {
async function fetchFromDatabase() {
const myName = await getNameFromDatabase();
const myValue = await getValueFromDatabase();
setMyName(myName);
setMyValue(myValue);
}
fetchFromDatabase();
}, [])
return (
<div>
<h1>{myName}</h1>
<h1>{myValue}</h1>
</div>
)
}
However, when I do this, they no longer get displayed. I supposed they remain "null" and "undefined". Apparently if I do a console.log(), they eventually get fetched, but only after the page is rendered without them, which is not what was happening in the first case.
Why exactly is this happening? Why is it getting displayed correctly in the first case but not in the second? As far as I know, useEffect() does the same thing as componentDidMount(). Should I proceed another way if I wish to call async functions inside useEffect()?
The useEffect hook and state updates are fine. Function components are instanceless though, so the this is just undefined. Fix the render to just reference the state values directly.
It's also good practice to handle errors when working with asynchronous code.
function MyPage() {
const [myName, setMyName] = useState(null);
const [myValue, setMyValue] = useState(undefined);
useEffect(() => {
async function fetchFromDatabase() {
try {
const myName = await getNameFromDatabase();
const myValue = await getValueFromDatabase();
setMyName(myName);
setMyValue(myValue);
} catch(error) {
// handle any rejected Promises and thrown errors
}
}
fetchFromDatabase();
}, []);
return (
<div>
<h1>{myName}</h1>
<h1>{myValue}</h1>
</div>
);
}
First of all, you are giving the same name for your response as your useState(). Try using different names. Then, put just empty string into your useState() default value instead of null or undefined. Finally, you no longer need to use this but instead access directly the value. It should be something like this :
function MyPage() {
const [myName, setMyName] = useState('');
const [myValue, setMyValue] = useState('');
useEffect(() => {
async function fetchFromDatabase() {
const name = await getNameFromDatabase();
const value = await getValueFromDatabase();
setMyName(name);
setMyValue(value);
}
fetchFromDatabase();
}, [])
return (
<div>
<h1>{myName}</h1>
<h1>{myValue}</h1>
</div>
)
}
function MyPage() {
const [myName, setMyName] = useState(null);
const [myValue, setMyValue] = useState(undefined);
useEffect(() => {
(async () => {
const myName = await getNameFromDatabase();
const myValue = await getValueFromDatabase();
setMyName(myName);
setMyValue(myValue);
})();
}, []);
return (
<div>
<h1>{myName}</h1>
<h1>{myValue}</h1>
</div>
);
}
Alright, so the code in the original post is correct, as other remarked. However, it is a very simplified/abstract version of the actual code I'm working on.
What I was doing wrong is that I was using the state in useEffect() immediately after setting it there.
Something like that:
// WRONG
let fetchedName= getNameFromDatabase();
setMyName(fetchedName);
if(myName==="something") {
setMyValue(1000);
}
The conclusion is: Never use the state immediately after setting it in useEffect() or componentWillMount(), use an intermediary variable.
Instead do:
// CORRECT
let fetchedName= getNameFromDatabase();
setMyName(fetchedName);
if(fetchedName==="something") {
setMyValue(1000);
}

react state is not updating when it's value changes [duplicate]

This question already has answers here:
React setState not updating state
(11 answers)
The useState set method is not reflecting a change immediately
(15 answers)
Closed 1 year ago.
I want to make search request to the api. but problem I'm having is that every time URLSearchParams gets updated. the searchKeyword does not update. I mean it's not rerendering. and when i refresh the page i want to send the request with updated value. but I want to rerender the searchKeyword everytime the value = new URLSearchParams(window.location.search).get("query") update.
const [searchKeyword, setSearchKeyword] = useState("")
let value = new URLSearchParams(window.location.search).get("query")
useEffect(() => {
setSearchKeyword(value)
axios.get(`http://127.0.0.1:8000/music/api/searchtrack/?search=${searchKeyword}`)
.then(res => {
setSongs(res.data)
}).catch(err => {
console.log(err)
})
}, [searchKeyword])
You need something that listens to window.location, one option is to use useLocation from react-router-dom and use the location object in your useEffect.
This would be a full example based on your code
import React, { useEffect, useState } from 'react';
const mockFetch = path => {
return new Promise(res => {
setTimeout(() => {
console.log(path);
res({
path,
data: [1, 2, 3]
});
}, 500);
});
};
const useSearch = () => {
const [search, setSearch] = React.useState(window.location.search);
const listenToPopstate = () => {
const searchPath = window.location.search;
setSearch(searchPath);
};
React.useEffect(() => {
window.addEventListener('popstate', listenToPopstate);
return () => {
window.removeEventListener('popstate', listenToPopstate);
};
}, []);
return search;
};
export default function App() {
const search = useSearch();
const [songs, setSongs] = useState([]);
useEffect(() => {
let value = new URLSearchParams(search).get('query');
const g = async () => {
try {
const data = await mockFetch(
`http://127.0.0.1:8000/music/api/searchtrack/?search=${value}`
);
setSongs(data);
} catch (err) {
console.log(err);
}
};
g();
}, [search]);
return (
<div>
<h1>Hello StackBlitz!</h1>
<p>{JSON.stringify(songs)}</p>
</div>
);
}
This example is not production ready, to make it production ready:
See if you can replace the useSearch by a 3rd party library you are using (are you using a router library), otherwise it's ok to use something like this.
Add a cancellable event to the get request so that it doesn't trigger setSongs when the user navigates away from the page.
This is because useEffect hook renders when only dependency array changes.You will have to put this line in side use effect hook.
let value = new URLSearchParams(window.location.search).get("query")

Separate wrapper with api request in React

I am using React. Tell me how to make it beautifully (right!). On the page, I have two almost identical sections:
And I'm trying to follow the rule, keep containers and components separate. There is a wrapper in which there is one api request to receive a picture (hereinafter it is transmitted as a props) for a specific section, it is rendered in this way:
It turns out that this wrapper is (almost) the same:
I understand that this can be done correctly, but something does not work. I am confused by the fact that it is necessary to return two different components from the wrapper, where the api request to receive a picture goes. (I was looking towards hoc, but I haven't figured out how to use it myself). Thank you in advance.
I did it all the same through hoc. Here is the component itself:
function LoadingSnapshotHOC(Component) {
const NewComponent = (props) => {
const isMounted = useIsMounted();
const state = useSelector(({ dateParams }) => {
const { currentPage } = props;
return {
selectedTimeLabel: dateParams?.[currentPage].selectedTimePeriod.label,
compareTimeLabel: dateParams?.[currentPage].compareTimePeriod.label,
};
});
const [snapshot, setSnapshot] = useState("");
const updateSnapshot = async (deviceID) => {
const img = await getSnapshot(deviceID);
img.onload = () => {
if (isMounted.current) {
setSnapshot(img);
}
};
};
useEffect(() => {
if (props.deviceID) updateSnapshot(props.deviceID);
}, [props.deviceID]);
return (
<Component
{...props}
snapshot={snapshot}
selectedTimeLabel={state.selectedTimeLabel}
compareTimeLabel={state.compareTimeLabel}
/>
);
};
return NewComponent;
}
export default LoadingSnapshotHOC;
Next, I wrapped my components:
function HeatMapSnapshot({...}) {
...
}
export default LoadingSnapshotHOC(HeatMapSnapshot);
and
function TrafficFlowSnapshot({...}) {
...
}
export default LoadingSnapshotHOC(TrafficFlowSnapshot);
And their render. Thank you all for your attention!

Return component data using switch and Hooks

I'm trying to do a switch inside a function and i'm use react hooks.
The switch works fine but i cannot return a component..why?
The idea is that as I go through the array i will load the corresponding component whit all his data at that moment.
export default function Content({content}) {
const [contentBooks, setContentBooks] = useState(null);
const [contentFilms, setContentFilms] = useState(null);
async function data() {
return await Promise.all(content.map(element => element.content).map(async item => {
if (item.type == 'DETAIL') {
switch (item.type) {
case 'BOOKS':
const bookstype = await axios.get(`url`)
setContentBooks(bookstype)
return <Componen1 info={contentBooks} // --> not work
case 'FILMS':
const filmstype = await axios.get(``)
setContentFilms(filmstype)
return <Componen2 info={contentFilms} // --> not work
default:
return null;
}
}
}))
}
useEffect(() => {
const fetchData = async () => {
const result = await data()
};
fetchData();
}, [content]);
return (
<React.Fragment></React.Fragment>
)
}
You probably need this resource: https://www.robinwieruch.de/react-pass-props-to-component/
I don't think that is the correct way of calling a component in react
there is alot wrong with this. first your components have no closing tag not sure how this even compiles.
<Componen2 info={contentFilms}/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.0/umd/react-dom.production.min.js"></script>
next you are going an extremely round about way of calling the component.
load it once, not in the useeffect.

Categories