Return component data using switch and Hooks - javascript

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.

Related

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

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)
}

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);
}

Rendering async promises through function components

Sorry if the post is duplicated i just find examples for class components.
I have this code:
export const getUniPrice = async () => {
const pair = await Uniswap.Fetcher.fetchPairData(HOKK, Uniswap.WETH[ETH_CHAIN_ID]);
const route = new Uniswap.Route([pair], Uniswap.WETH[ETH_CHAIN_ID]);
const priceUni = route.midPrice.toFixed(9);
return priceUni
}
It does work, answer me the promise object:
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: "1106278.001628948"
What i would like to know is, how can i properly work with this object in order to be able to render it through function components? I'm doing something like this which obviously will not work because react doesn't render objects.
const Price = () => {
const { state, dispatch } = useContext(AppContext);
return(<>
{state.dex === 'uni' ? getUniPrice() : state.dex === 'cake'
? getCakePrice() : getMDexPrice()
}
</>)
}
Could someone give me a hint? This function is running outside a function component so I can't just use useState
You're right. The result of getUnitPrice() is a Promise, not a value, so what React does is it prints out the stringified version of that Promise. If you need the fulfilled value, you need a state value that will re-render the page if updated. Something like this:
const [price, setPrice] = useState('');
useEffect(() => {
getUnitPrice().then((p) => setPrice(p));
}, []);
...
<div>Price: {price}</div>
If you're using a class component, you can initialize the state the same way like this:
state = {
price: '',
}
async componentDidMount() {
const p = await getUniPrice();
this.setState({ price: p });
}

get value from async function in useState

I'm using async storage to retrieve my data but how do I use it with useState()?
Example:
async function getdata(){
let d= await AsyncStorage.getItem('Name');
return d;
}
export default function App() {
const [name, setName] = useState(() => getdata());
return (<View>...</View>)
}
but this doesn't work since getdata() is async, so how do I solve this problem?
Edit:
I forgot to mention I tried the useEffect() Hook like this:
async function getdata(){
let d= await AsyncStorage.getItem('Name');
console.log('retrieved Data!');
return d;
}
const [name, setName] = useState(()=>0);
useEffect(() => {
getData().then(setName);
console.log('moving on...');
}, []);
but the order of execution was:
'Moving on...'
'retrieved Data!'
where as it should have been the other way around
You'll initialize your state to some empty value. Maybe an empty string, maybe a null. If you like, you can have your component render something different during this time, such as a loading indicator. Then once the data loads, you can set state to render again.
export default function App() {
const [name, setName] = useState(null);
useEffect(() => {
getData().then(value => setName(value));
}, []);
if (name === null) {
return <Text>Loading...</Text>
} else {
return (<View>...</View>)
}
}
You have to use useEffect
export default function App() {
const [name, setName] = useState('');
useEffect(() => {
const bootstrap = async () => {
const name = await getdata();
setName(name);
};
bootstrap();
}, []);
return <View>...</View>;
}
I think the best solution here is to initialize name as false, and then use 'setName' in a .then() callback. setName (and those setter functions that come out of useState generally) is meant to be a way of updating that value asynchronously.
I'm setting name to the boolean 'false' here and checking it strictly. If there is a case where that name field is empty it can just be set to an empty string without causing an infinite loop.
async function getdata(){
let d= await AsyncStorage.getItem('Name');
return d;
}
export default function App() {
const [name, setName] = useState(false);
useEffect(() => {
if (name === false) {
getData().then((name) => {
setName(name);
});
}
}, []);
return (<View>...</View>)
}
Keep in mind that your view has to be able to deal with a false name value. How it does that is up to you.
^^^The above solution should effectively solve your problem but I'll make a quick note here: If your component is relatively complex it might be re-rendering a number of times for other reasons and will consequently run the if(name === false) condition as it waits for the callback to resolve. This won't break things but it'll hammer async storage more than necessary and might affect performance. If you find that to be the case, look into 'debouncing' callbacks using the helpful article below.
https://medium.com/#gabrielmickey28/using-debounce-with-react-components-f988c28f52c1

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!

Categories