How to render components based on localStorage value - javascript

How do I render based on the data in the localStorage? The localStorage has a key-value pair (location: US or Location: AU) and is added when the user visits.
import React, { useEffect, useState} from 'react';
let localUserLoc = userLoc; //works fine when I set this variable to 'US' or 'AU'
const LoadSections= (props) => {
const { sections, className } = props;
const [userLoc, setUserLoc] = useState(JSON.stringify(localStorage.getItem('userLoc')) || '');
useEffect(() => {
const userLoc = JSON.stringify(localStorage.getItem('userLoc'));
setUserLoc(userLoc);
}, []);
useEffect(() => {
getUserLoc = userLoc;
}, [userLoc]);
// // console.log(local1, 'outside');
console.log(abTestType, '4 - outside');
return (
<Wrapper className={className}>
{sections.map((section, sectionIndex) => {
const userLocProp = section.userLoc;
if (userLocProp === localUserLoc) {
return <SomeComp />
}
else if (userLocProp === 'undefined') {
return <AnotherComp />
}
The code above only loads the if I manually set the localUserLoc to 'US' or 'AU'. However, when I try to update the variable from localStorage it doesn't display .
I think it is because the LocalStorage is read but not rendered? I tried using two UseEffect hooks and added the first one as a dependency but to no avail.
How can I read and then set the localUserLoc variable to whatever it is in the localStorage and render the relevant component?
The user loc is detected via IP and then I setItem in localStorage.

It doesn't work because you are using JSON.stringify after getting the value from localStorage but the value stored in localStorage is already a string because localStorage can only store string values.
So you have to use JSON.parse for getting the value from localStorage and convert it to a javascript object.
const [userLoc, setUserLoc] = useState(JSON.parse(localStorage.getItem('userLoc'))

Related

Is the any way to identify whether a query is in view or not in react query

Assume I have 3 queries x, y and z. I want to update the query x only when it is in view. Is there any way to identify it ?
I tried to maintain the view query key as a global state, but it seems not working fine. Is there any way to identify without maintaining viewing query key as global state.
Is there any possible way to get list of viewing queries ???
First of all, you need to hold the state of the component if it's in the viewport or not. The IntersectionObserver API allows you to detect when an element enters or leaves the viewport, which you can use to update the state of your component.
Then you can use that state as a key in your useQuery. Also you can use it in enabled option if you want to prevent refetch when item is not on the viewport.
import React, { useState, useEffect, useRef } from 'react';
import { useQuery } from 'react-query';
function Test() {
const [isInViewport, setIsInViewport] = useState(false);
const ref = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setIsInViewport(true);
} else {
setIsInViewport(false);
}
});
});
observer.observe(ref.current);
return () => {
observer.unobserve(ref.current);
};
}, []);
useQuery(
[isInViewport],
() => (
/// your query
),
{
enabled: isInViewport,
},
);
return (
<div ref={ref}>
<div>{isInViewport ? 'In viewport' : 'Not in viewport'}</div>
</div>
);
}
export default Test;

How to print out my api data in to a list?

I want to print the names of every country, I am still new to this. So, if you don't mind, can you please explain my mistake?
I understand that countries is an array of objects, and once it passes through
{countries.map ((c) => <Countries country = {c} key ={c.name}/>) }
it becomes an object, I thought I could call object.properties but I can't. I am not sure what to do next.
import axios from 'axios'
import {useState, useEffect} from 'react'
const App = () => {
const [countries, setCountries] = useState([])
const hook = () => {
console.log('effect')
axios
.get('https://restcountries.com/v3.1/all')
.then(response => {
console.log('promise fulfilled')
setCountries(response.data)
})
}
useEffect(hook, [])
const Countries = (country) => {
console.log('countries:', country)
return ( <li>{country.name}</li>)
}
console.log(countries)
return (
<div>
{countries.map ((c) => <Countries country = {c}/>) }
</div>
)}
export default App;
so I understand that countries are an array of objects, and once it gets passed through
{countries.map ((c) => <Countries country = {c} key ={c.name}/>) }
it becomes an object, I thought I could call object.properties but I cant. so I am not sure what to do next.
You are correct. You can pass the values as props between components and you can get the properties from them if that prop is an object.
const Countries = (country) => {
You are passing them as props. But the variable is an object. Basically, React send the props from one component to another component via props which is a property object. Eg, <Component a={value} /> is the component that we are calling. const Component = (props) => {...} is the component that we defined.
So the props is containing all the information in object form. props = {a: value}. In your case, const Countries = (country) => { country is the prop object. You can access the property from it as country.country or you can destruct them. You code should be like this.
const Countries = ({ country }) => {
or
const Countries = (country) => {
...
return ( <li>{country.common}</li>)
}
When checked the response from the url you've mentioned, seems like you are passing wrong value. If you are just trying to get the country name like Uruguay for example, this value is in object and can be accessed as [index].name.common. But you're passing [index].name which gives the object itself.
I believe you need to set the country differently as
<div>
{countries.map ((c) => <Countries country =
{c.name}/>) }
</div>
And access as
const Countries = (country) => {
console.log('countries:', country)
return ( <li>{country.common}</li>)
}
Or just do the following in your existing code as you've already passed the object which has property common and it ultimately has name.
return ( <li>{country.common.name}</li>)
I've just tried seeing the response from the link you've given and with assumptions and I think it should work.
Note : You can directly pass the country name if you're just wishing to get the names of the countries as c.name.common and as return ( <li>{country}</li>) in your existing code.
const [countries] = useState([])
const [array , setArray] = useState([])
const hook = () => {
axios
.get('https://restcountries.com/v3.1/all')
.then(response => {
for(var i=0; i<response.data.length; i++){
// if more than data length, dont add (for if useEffect runs twice.)
if(countries.length < response.data.length){
// pushes country's common name (change "common" to "official" for the official country name)
countries.push(response.data[i].name.common)
} else {}
}
// this second array is for actually rendering the list
setArray(countries)
})
}
useEffect(hook, [])
return (
<div>
{array.map (c => (<li key={c}>{c}</li>))}
</div>
)}
When fetching data like this it's key to console log what you're actually receiving and dive deeper into the data you are actually receiving. In your case, you were fetching a country's entire array of data. In my code, I narrowed it down to the common name of the country, and fixed some react child errors.

Filter an object from an array in react

I am fetching data from an API using axios.
On my invoice details page when I try to get data of only one invoice using this code
const id = props.match.params.id;
const invoice = useSelector((state) => state.invoices.find(invoice => invoice._id === id));
It returns an object or undefined but I only want an object inside an array or an empty array not undefined how should I do that?
When I tried to use .filter method instead of .find, it logged the array into the console infinite time.
Complete code:
import React, { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import backIcon from '../assets/images/icon-arrow-left.svg'
import InvoiceDetailsHeader from './InvoiceDetailsHeader';
import { useSelector } from 'react-redux';
// remove this after adding DB
import data from '../data.json'
import InvoiceCardDetails from './InvoiceCardDetails';
const InvoiceDetails = (props) => {
const [invoiceData, setInvoiceData] = useState([]);
const id = props.match.params.id;
const invoice = useSelector((state) => state.invoices.find(invoice => invoice._id === id));
useEffect(() => {
setInvoiceData(invoice);
// console.log(invoiceData)
}, [id, invoice]);
return (
<div className="mx-auto px-12 py-16 w-full max-w-3xl">
<Link to="/" className="text-neutral text-xs"><img className="inline -mt-1 mr-4" src={backIcon} alt="back" /> Go back</Link>
<InvoiceDetailsHeader data={invoiceData} />
<InvoiceCardDetails data={invoiceData} />
</div>
)
}
export default InvoiceDetails
Anyone please help me with this.
I think it's because you're setting setInvoiceData(invoice) which is undefined at the very start. so make a check on it
if(invoice){
setInvoiceData([invoice])
}
please try this one
useEffect(() => {
if(invoice){
setInvoiceData([...invoiceData, invoice])
}
}, [id, invoice]);
First of all, I don't know if I missed anything, but I don't think it's a good way for invoice to be possible for both objects and empty array. I think a better way is to divide the conditions and render the invoice when the ID is not found.
If a filter method is used instead of a find, the filter method returns a new array instance each time. So as the second argument(invoice) of use Effect changes, the update callback of use Effect will continue to be repeated.
const invoice = useSelector((state) => state.invoices.find(invoice => invoice._id === id) ?? []);
What you want can be done simply using Nullish coalescing operator.
However, [] also creates a new array instance, so update callback is repeated indefinitely.
So to make what you want work in the current code, please remove the invoice from the dependence of useEffect as below.
useEffect(() => {
setInvoiceData(invoice);
// console.log(invoiceData)
}, [id]);

React TypeError: Cannot use 'in' operator to search for 'length' in null

I want to access a local array file, filter it, and store the filtred array as a state, finally pass the state to child component.
// array file
const originalData =
[
{ "ComName":"A", "SDGs":"1", "No":1 },
{ "ComName":"B", "SDGs":"2", "No":2 },
...
]
I use getComany() and filterCompany() to get the filtred data, and store it in filtredCompany as a state. But it turns out TypeError: Cannot use 'in' operator to search for 'length' in null.
// Services.js
export function getCompany() {
const companyList = originalData;
return companyList;
}
export function filterCompany(comType) {
// filter detail
let companyList = getCompany();
let filtredMatchCompany = getCompany().filter(type => type.SDGs === comType && type.No == comType);
let matchCom = _.map(filtredMatchCompany, 'ComName');
let filtredCompany = companyList.filter((type)=> matchCom.includes(type.ComName))
// Filter.js
export default function Filter() {
const [filtredCompany,setFiltredCompany] = useState(null);
useEffect(() => {
setFiltredCompany(getCompany());
}, []);
function handleD3(e) {
let typeCompany = e.target.value;
typeCompany !== "all"
? setFiltredCompany(filterCompany(typeCompany))
: setFiltredCompany(getCompany());
}
const outputFilter = filtredCompany;
return (
<>
{buttons &&
buttons.map((type, series) => (
<>
<button key={series} value={type.no} onClick={handleD3}>
{type.name}
</button>
</>
))}
<>
<ObjectD3 outputFilter = {filtredCompany}/>
</>
</>
}
The error might come from initial state null. I try to fix that, and change the initial state to
const [filtredCompany,setFiltredCompany] = useState([]) . TypeError doesn't occur, but setState, which is setFiltredCompany , doesn't work anymore.
const [filtredCompany,setFiltredCompany] = useState(null);
console.log(filtredCompany)
// *click button*
// the filtred data I want
const [filtredCompany,setFiltredCompany] = useState([]);
console.log(filtredCompany)
// *click button*
// []
Does anyone know how to handle this situation or better idea to pass data? Thank you so much in advanced!
Source code here
Let go through a part of your code here.
First of all, for a React Component, the lifecycle methods are executed in the following order: constructor -> render -> componentDidMount.
Within the Filter component, you are setting initial state like this:
useEffect(() => {
setFiltredCompany(getCompany());
}, []);
Now, one thing to remember is all the setState() functions and the useEffect hook, are asynchronous, that is, they complete their execution at some time in the future. So, when React renders your app, by the time ObjectD3 component is rendered, the useEffect hook has not executed, so
the ObjectD3 receives null as a prop and the statement in ObjectD3
this.dataset = this.props.outputFilter;
assigns null to the dataset, thereby giving you the error.
A better way to do it, is to implement another lifecycle method in ObjectD3, named componentDidUpdate, where you can compare the changes in props, since the update and take necessary actions.
Check the updated version of code here.

Invalid hook call in work with local Storage

I have a 3 files:
Main component,
File with states that are stored in local storage
A file with a reset function for resetting these same states to default
values.
I import the file with the states and reset file in the main component and everything is ok. But when I try use reset function for set localState value to default, i got error “Error: Invalid hook call. Interceptors can only be called inside the body of a functional component. "
I read the documentation on react, but I did not understand the error
First file code:
import React from "react";
import { LocalStorage } from "./localState";
import { resetLocalStorage } from "./resetLocalState";
function App() {
const localState = LocalStorage(); // local storage keys
const resetState = () => resetLocalStorage(); // reset local storate states
return (
<div>
<button onClick={() => resetState()}>Refresh State to default</button>
<br />
<button
onClick={() => localState.setLocalStorageState("State was changed")}
>
Change State
</button>
<p>{localState.localStorageState}</p>
</div>
);
}
export default App;
Second file code:
import { useState, useEffect } from "react";
const useLocalStorageList = (key, defaultValue) => {
const stored = localStorage.getItem(key);
const initial = stored ? JSON.parse(stored) : defaultValue;
const [value, setValue] = useState(initial);
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
};
//local storage keys
export const LocalStorage = () => {
const [localStorageState, setLocalStorageState] = useLocalStorageList(
"State",
"Default Value"
);
return { localStorageState, setLocalStorageState };
};
Third file code
import { LocalStorage } from "./localState";
export const resetLocalStorage = () => {
const localState = LocalStorage(); //local storage keys
localState.setLocalStorageState("Default Value");
};
Link to SandBox
I didnt see anything to reset all states in your resetLocalStorage(). I assume you will keep track of all the 'local storage keys' and define reset-functions for each. This example modifies your hook to return a third function to reset the state so another reset-function doesn't have top be defined.
https://codesandbox.io/s/smoosh-sound-0yxvl?file=/src/App.js
I made few changes in your code to achieve the use case you were trying to achieve. (Let me know my implementation is suffices your use case)
The resetLocalStorage is not starting with use That's why you were getting the previous error.
After renaming that function to useResetLocalStorage, still it will not work since -
you were calling the custom hook onClick of button. This breaks one react hook rule which is react hooks must not be called conditionally https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

Categories