Rendering data from fetch request React Native - reactjs

I'm trying to use data from a fetch request but it doesn't load even after some time. How can I get my return function to display the instagram function data? The useEffect hook isn't getting the data to load.
export default function Dashboard(props) {
const [instagram, setInstagram] = useState('');
useEffect(() => {
var url = "https://graph.instagram.com/me/media?fields=media_url,media_type,permalink&access_token=IGQVJHB6RAZDZD"
fetch(url)
.then(response => response.json())
.then((user) => {
console.log("INSTA", user.data);
setInstagram(user.data)
});
},[])
var pics = function(instagram) {
if(instagram){
console.log("found")
instagram.map(function(item, i){
return <li>Pic</li>
})
}
}
return (
<View id="instafeed" style={styles.instagram}></View>
{
pics()
}
</View>
)
}
}

Your pics function doesn't return anything, add this return statement.
var pics = function(instagram) {
if(instagram){
console.log("found")
return instagram.map(function(item, i){
return <li>Pic</li>
})
}
}

Your problem is you re-declare instagram as a parameter of the pics function, then call the pics function without any argument.
The code inside your pics function refers to the instagram from the parameter, not the parent scope instagram and as you called the pics function without any argument, will always be undefined.
To fix your code, either removing instagram parameter of the pics function, or to call pics function with the state.
For example:
...
var pics = function() {
if(instagram){
console.log("found")
instagram.map(function(item, i){
return <li>Pic</li>
})
}
}
...
Or
return (
<View id="instafeed" style={styles.instagram}></View>
{
pics(instagram)
}
</View>
)
I reckon it will be beneficial if you do some research on the topic of Javascript closure and lexical scope.

Related

React useState overwritten even with spread

So I have a component where I have to make an API call to get some data that has IDs that I use for another async API call. My issue is I can't get the async API call to work correctly with updating the state via spread (...) so that the checks in the render can be made for displaying specific stages related to specific content.
FYI: Project is a Headless Drupal/React.
import WidgetButtonMenu from '../WidgetButtonMenu.jsx';
import { WidgetButtonType } from '../../Types/WidgetButtons.tsx';
import { getAllInitaitives, getInitiativeTaxonomyTerm } from '../../API/Initiatives.jsx';
import { useEffect } from 'react';
import { useState } from 'react';
import { stripHTML } from '../../Utilities/CommonCalls.jsx';
import '../../../CSS/Widgets/WidgetInitiativeOverview.css';
import iconAdd from '../../../Icons/Interaction/icon-add.svg';
function WidgetInitiativeOverview(props) {
const [initiatives, setInitiatives] = useState([]);
const [initiativesStages, setInitiativesStage] = useState([]);
// Get all initiatives and data
useEffect(() => {
const stages = [];
const asyncFn = async (initData) => {
await Promise.all(initData.map((initiative, index) => {
getInitiativeTaxonomyTerm(initiative.field_initiative_stage[0].target_id).then((data) => {
stages.push({
initiativeID: initiative.nid[0].value,
stageName: data.name[0].value
});
});
}));
return stages;
}
// Call data
getAllInitaitives().then((data) => {
setInitiatives(data);
asyncFn(data).then((returnStages) => {
setInitiativesStage(returnStages);
})
});
}, []);
useEffect(() => {
console.log('State of stages: ', initiativesStages);
}, [initiativesStages]);
return (
<>
<div className='widget-initiative-overview-container'>
<WidgetButtonMenu type={ WidgetButtonType.DotsMenu } />
{ initiatives.map((initiative, index) => {
return (
<div className='initiative-container' key={ index }>
<div className='top-bar'>
<div className='initiative-stage'>
{ initiativesStages.map((stage, stageIndex) => {
if (stage.initiativeID === initiative.nid[0].value) {
return stage.stageName;
}
}) }
</div>
<button className='btn-add-contributors'><img src={ iconAdd } alt='Add icon.' /></button>
</div>
<div className='initiative-title'>{ initiative.title[0].value } - NID ({ initiative.nid[0].value })</div>
<div className='initiative-description'>{ stripHTML(initiative.field_initiative_description[0].processed) }</div>
</div>
);
}) }
</div>
</>
);
}
export default WidgetInitiativeOverview;
Here's a link for video visualization: https://vimeo.com/743753924. In the video you can see that on page refresh, there is not data within the state but if I modify the code (like putting in a space) and saving it, data populates for half a second and updates correctly within the component.
I've tried using spread to make sure that the state isn't mutated but I'm still learning the ins and outs of React.
The initiatives state works fine but then again that's just 1 API call and then setting the data. The initiativeStages state can use X amount of API calls depending on the amount of initiatives are returned during the first API call.
I don't think the API calls are necessary for this question but I can give reference to them if needed. Again, I think it's just the issue with updating the state.
the function you pass to initData.map() does not return anything, so your await Promise.all() is waiting for an array of Promise.resolve(undefined) to resolve, which happens basically instantly, certainly long before your requests have finished and you had a chance to call stages.push({ ... });
That's why you setInitiativesStage([]) an empty array.
And what you do with const stages = []; and the stages.push() inside of the .then() is an antipattern, because it produces broken code like yours.
that's how I'd write that effect:
useEffect(() => {
// makes the request for a single initiative and transforms the result.
const getInitiative = initiative => getInitiativeTaxonomyTerm(
initiative.field_initiative_stage[0].target_id
).then(data => ({
initiativeID: initiative.nid[0].value,
stageName: data.name[0].value
}))
// Call data
getAllInitaitives()
.then((initiatives) => {
setInitiatives(initiatives);
Promise.all(initiatives.map(getInitiative))
.then(setInitiativesStage);
});
}, []);
this code still has a flaw (imo.) it first updates setInitiatives, then starts to make the API calls for the initiaives themselves, before also updating setInitiativesStage. So there is a (short) period of time when these two states are out of sync. You might want to delay setInitiatives(initiatives); until the other API requests have finished.
getAllInitaitives()
.then(async (initiatives) => {
const initiativesStages = await Promise.all(initiatives.map(getInitiative));
setInitiatives(initiatives);
setInitiativesStage(initiativesStages)
});

Expected an assignment or function call and instead saw an expression ( Im new to react.js so i cant understand how solve this error)

Im getting the error (AS I COMMENTED OUT THE LINE CAUSING ERROR IN CODE ) in the following code as im in learning phase of reactjs this error doesnt make any sense to me. Guidance is required
componentDidMount() {
let rooms = this.formatData(items);
console.log(rooms);
}
formatData(items) {
let tempItems = items.map((item) => {
let id = item.sys.id;
let images = item.fields.images.map((image) => {
image.fields.file.url; // error is on this line
}); // The error suggest that its
// expecting an assignment or function
// but instead got an expression???
let room = { ...this.fields, images, id };
return room;
});
return tempItems;
}
render() {
return (
<>
<RoomContext.Provider value={{ ...this.state }}>
{this.props.children}
</RoomContext.Provider>
</>
);
}
}
Currently the arrow function ((image) => { ... }) you're giving as an argument to map doesn't return anything. Arrow function expressions require either an expression or a function body on the right-hand-side of the arrow. What you've written is a function body without a return statement, and thus (image) => { ... } returns undefined as default. You can either write the expression image.fields.file.url as-is to be the right side of the arrow (making the right side an expression instead of a function body) or add a return statement to the body.
That is, change
let images = item.fields.images.map((image) => {
image.fields.file.url;
});
either to
let images = item.fields.images.map((image) =>
image.fields.file.url
);
or
let images = item.fields.images.map((image) => {
return image.fields.file.url
});

How to use React.Suspense with a api call?

I've read the doc about React.Suspense, it seems great, but how can I use it while performing an api call, with axios for example?
To be more specific, why this code doesn't work?
export default function Foo(){
const [state, setState] = useState()
useEffect(()=> {
axios.get("url")
.then(res=> setState(res.data))
.catch(_=> setState(null)
}, [])
return (
<Suspense fallback="loading data">
<div>hello {state.name}</div>
</Suspense>
)
}
Thanks!
According to the React documentation, what you are trying to achieve is possible. There is an experimental feature called Concurrent Mode. You can find the implementation details in React documentation.
Link to the Suspense documentation: https://reactjs.org/docs/concurrent-mode-suspense.html
But there is a limitation, you have to use an npm package which leverages this feature. Relay is what is suggested by the React team.
This is the code sandbox link provided in the documentation itself.
https://codesandbox.io/s/frosty-hermann-bztrp
Try something like this:
// fetch data
function fetchSomeData(baseCatUrl) {
let status = "pending";
let result;
let suspender = axios(baseCatUrl).then((res) => {
status = "success";
result = res.data;
}, (e) => {
status = "error";
result = e;
});
return {
read() {
if (status === "pending") {
throw suspender;
} else if (status === "error") {
throw result;
}
else if (status === "success") {
return result;
}
}
}
}
// Component using data
const resource = fetchSomeData(baseCatUrl);
const Component = () => {
return (
<Suspense fallback={<p>Loading...</p>}>
<ChildComponent resource={resource} />
</Suspense>
)
};
Suspense is used to asynchronously load Components, not APIs. It's used to lazily load components imported using React.lazy.
Even if you manage to render your component it'll not make any difference in your case. You'll need it for a different use case

Unable to display promise data inside a method in render

I realize I can't return objects in a react render but I'm having a problem figuring out how to do this.
Error message:
Invariant violation, objects are not valid as a React child error
My method:
displayListItemComponent = item => {
return this.getConcernsDB(item.values)
.then(returnedConcerns => {
if(returnedConcerns.length) {
returnedConcerns.forEach(el => {
return(el.name);
})
}
}
)
}
Partial render:
<CollapseBody>
{ el.details.map(item => (
this.displayListItemComponent(item)
))}
</CollapseBody>
Here I am trying to return el.name from displayListItemComponent
I am attempting to return displayListItemComponent in my react render.
My assumption was that by returning the function this.getConcernsDB that it would chain down and it would return el.name. How can I achieve this?
Your method returns a Promise. Render should be synchronous and have no side effects (e.g. it should only return allowed values from props and state). Therefore you need to store your data in state, and render elements from state
You could do something like this. Also what does el.name contain?
displayListItemComponent =(props) => {
const {item} = props;
const [data, setData] = useState();
useEffect(() => {
// i see you did this.getConcernsDB. where is it coming from?
getConcernsDB(item.values)
.then(returnedConcerns => {
setData(returnedConcerns.values(el => el.name))
)
}, [])
if(!data) return <SomeLoadingSpinner />
// now you have access to the names in `data` variable. Return whatever component you want using the `data`
}

Calling a non-returning JSX function inside of render() in React

I got a quick question on calling a function inside of the render method or some potential way to update a method when a user decides to go to the next screen via clicking "Send".
My goal is to change the old created "selectedExchange" to an updated "selectedExchange" as the user taps the "Send" arrow to go to the next screen.
// On Send Functionality
onSend = () => {
const { exchanges } = this.props
// Testing to see if hard coded values get sought out response
// Hard code values work
// this.props.selectExchange updates the selectedExchange
this.props.selectExchange(exchanges.exchanges[0])
this.props.navigation.navigate('selectRecipient', { transition: 'slideToLeft' })
//return ( exchange )
}
// Additional helper method, able to replace the above "this.props.selectExchange()"
// if necessary
updateExchange = (value, exchange) => {
this.props.selectExchange(exchange)
this.setState({ selectedDisplayName: value })
// Render call
render() {
const { navigation, account, exchanges } = this.props
const { selectedExchange } = exchanges
const { status, symbolPriceTicker, dayChangeTicker } = selectedExchange
const loading = status === 'pending' && !symbolPriceTicker && !dayChangeTicker
const avatar = account.profile_img_url ? { uri: account.profile_img_url } : IMAGES.AVATAR
return (
<View style={globalStyles.ROOT}>
<Loading loading={loading} />
<Image style={globalStyles.backgroundImg} source={IMAGES.BACKGROUND} />
<TopArea navigation={navigation} avatar={avatar} />
<ScrollView>
{
exchanges.exchanges.length > 0 &&
exchanges.exchanges.map(exchange => (
<View style={screenStyles.container}>
<ServiceInfo
account={account}
balances={exchange.balances}
displayName={exchange.label}
symbolPriceTicker={symbolPriceTicker}
// onRequest={this.onRequest}
onSend={this.onSend}
/>
...
...
What I've tried:
Passing an exchange prop via "onSend={this.onSend(exchange)}" to see if I could pass the necessary object that would be used to update selectedExchange. This didn't work as it required me to return something from onSend.
Directly calling the helper method in the JSX between various views. Also didn't work as it required I returned some form of JSX.
Not sure how else I could tackle this. Thanks for any help!

Resources