Best way to display data form an api call - reactjs

I was wondering what is the best way to display data from a rest api.
Actually what I do:
Call fetch function from componentDidMount();
setState to store my response
Check with ternary when render() if the value is set or not
it look like this :
(getcall() is a fetch function) :
async componentDidMount() {
const response= await getCall(
`event?ref=23876186`, // this is just to illustrate
);
this.setState({ payload: response})
}
Then in render() I do some :
{this.state.payload ? (
<h1>{this.state.payload.event.name}</h1>) : ( <h1></h1>)}
I though about calling my fetch function from the constructor, but it's weird to call an async function in a constructor, you loose the aync purpose.
I imagine some case like for an input :
<Input
type="text"
name="name"
id="name"
**value={this.state.event.name}**
placeholder="Name..."
onChange={this.handleName}
required
/>
If I want to set a value for it, like this.state.event.name, if I have 10 fields, I will have 10*2 times this kind of code because for each one I wrote a ternary.
So what is the best way to display data from an api call ?
Thanks for your answers

Instead of adding a lot of ternaries where you check if payload is set, you can return null early from the render method if it is not set yet.
Example
class App extends React.Component {
state = {
payload: null
};
async componentDidMount() {
const response = await getCall("/event?ref=23876186");
this.setState({ payload: response });
}
render() {
const { payload } = this.state;
if (payload === null) {
return null;
}
return <div>{/* ... */}</div>;
}
}

Related

map to dropdown if condition is met reactjs

I want to populate dropdowns only if the a certain condition is met.
I am using the axios get method for fetching data.
The code is as follows -
componentDidMount = e => {
axios
.get("https://jsonplaceholder.typicode.com/users")
.then(function (res) {
let FullData = res.data;
});
};
My form is as follows-
<form>
<select>address</select>
<option id={FullData.zipcode}>{FullData.street}</option>
</form>
I need this to work only if the { FullData.city } is "Gwenborough".
Please refer here for the JSON.
Here's a way to do it. You generally need to put your values in state, so whenever they are received in asynchronous way, the component gets updated.
Here's a Demo sandbox
class DropDown extends React.Component {
constructor(props) {
super(props);
this.state = {
FullData: [],
}
}
componentDidMount=(e)=>{
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => res.json())
.then(res => {
this.setState({
FullData: res
})
})
}
render() {
let { FullData } = this.state;
return (
<form>
<label>Address </label>
<select>
{FullData
.filter(user => user.address.city === "Gwenborough")
.map(user => {
return <option key={user.id} id={user.zipcode}>{user.address.street}</option>
})}
</select>
</form>
);
}
}
Note I was using fetch instead of axios.
Another note - I really like Dexter's answer and the way the action is put into separate function. It looks like a better approach to structuring your code in general. It probably wouldn't work as it is only because of how your data is structured: you get and array of users and you only have city and street inside the address key. Cheers!
Something like this can help. Note your question isn't clear enough to provide working examples to help me understand the full scope of what you're talking about, so this is my best guess on the solution to your question.
constructor(){
this.selection=null;
this.doSomeTesting=this.doSomeTesting.bind(this);
}
doSomeTesting(FullData){
if(FullData.city === "Gwenborough"){
return (<option id={FullData.zipcode}>{FullData.street}</option>);
}
return ""; //return nothing useful if not what you're looking for
}
componentDidMount=(e)=>{
axios.get('https://jsonplaceholder.typicode.com/users')
.then(function(res){
let FullData=res.data
this.selection = this.doSomeTesting(FullData);
})
}
render(){
<form>
<select>address</select>
{this.selection}
</form>
}

How to work with async server calls in Reactjs 16.7.0

What is the correct way to use async methods as props in ReactJs? I have an app which mounts a component "dashboard" and this component then loads two drop down menus. The first drop down is populated by async fetch at the componentDidMount lifecycle method.
When the first drop down changes, the async event handler is triggered and the state property selectedAId is supposed to be set. If I debug this in the browser I do see this state prop set. After the state is set, the same event handler also calls an async server method to get some data based on the state property selectedAId.
If I then go to the async server method getEntitledSites, the parameter sent to the method is "0", instead of the value that I previously saw being set in the event handler.
Here is the gist to a summary of the code:
https://gist.github.com/thehme/c4b5a958ef1a6e5248f697375c9cb84b#file-api-ts-L15
api.ts
class Api {
...
async getEntitledForA() {
try {
const orgsResponse = await fetch('/api/v1/orgs');
const orgResponseJson = await orgsResponse.json();
return orgResponseJson.data;
} catch (err) {
console.error(err);
return null;
}
}
async getEntitledSites(orgId: number) {
try {
const sitesResponse = await fetch('/api/v1/study/' + orgId + '/sites');
const sitesResponseJson = await sitesResponse.json();
return sitesResponseJson;
} catch (err) {
console.error(err);
return null;
}
}
}
export default new Api();
dashboard.jsx
import React, {Component} from 'react';
import SelectDropDownA from '../components/SelectDropDownA';
import SelectDropDownB from '../components/SelectDropDownB';
import SelectDropDownC from '../components/SelectDropDownC';
class CreateDashboard extends Component {
constructor() {
super();
this.state = {
organizations: [],
sites: [],
selectedAId: "0",
selectedBId: "0",
selectedCId: "0",
showBMenu: false
}
}
async componentDidMount() {
let organizations = await Api.getEntitledForA();
this.setState({ organizations });
}
async onSelectedOrgChange(selectedOrgId) {
if (selectedOrgId !== 0) {
this.setState({ selectedAId });
let sitesData = await Api.getEntitledSites(this.state.selectedAId);
this.setState({
sites: sitesData.sites,
showSitesMenu: true
});
}
}
...
render() {
return (
<div className="panel">
<div className="insidePanel">
<SelectStudyDropDown
onChange={e => this.onSelectedOrgChange(e.target.value)}
...
/>
</div>
{this.showSitesMenu &&
<div className="insidePanel">
<SelectSiteDropDown
onChange={e => this.onSelectedSiteChange(e.target.value)}
...
/>
</div>
}
</div>
)
}
}
export default CreateDashboard;
I am wondering if this has to do with how I am using async/await or the binding of the onChange method.
SOLUTION: In my particular case, I do not need this.state to set selectedAId before starting my server request, so by passing the parameter straight to the request, works fine. Then I can simply move the setting of selectedAId to the second instance of this.setState. This would not work, if I needed selectedAId to be set before making the server request, in which case, I would have to wait for this.state to finish being set, but this isn't the case since I want my server request to start right away.
async onSelectedOrgChange(selectedOrgId) {
if (selectedOrgId !== 0) {
let sitesData = await Api.getEntitledSites(selectedOrgId);
this.setState({
selectedAId,
sites: sitesData.sites,
showSitesMenu: true
});
}
}
this.setState is asynchronous and not awaitable (i.e. does not return a promise), so if you want to do something with the new state after it has been set, you need to do it in a callback:
this.setState({ selectedAId }, async () => {
let sitesData = await Api.getEntitledSites(this.state.selectedAId);
this.setState({
sites: sitesData.sites,
showSitesMenu: true
});
});
Additionally, in your example, onSelectedOrgChange isn't binded to this anywhere. However if this was an issue the whole thing would have broken long before so I suppose that you just left it out by accident?
Furthermore I would advise you to extract the e.target.value inside the handler and pass onChange={this.onSelectedOrgChange} as the prop, because in your current code you're creating a new onChange handler every time render is called.

Saving input data on async storage

I have been trying to save input data on async storage but whenever i go back after input, it shows the previous data, not the updated one. Here's the code where i think some changes are needed.
constructor(props) {
super(props)
this.state = {
userData: {
address: ''
}
}
}
saveAddressEdit = (fieldName, fieldValue) => {
AsyncStorage.setItem(userData.address, fieldValue)
}
render() {
const {address} = this.state.userData
return (
<Input placeholder=" Type your Address:"
onChangeText={(address) => this.setState({address})}
value={address} />
<Button full danger onPress={()=>this.saveAddressEdit('address', address)} style={{backgroundColor:'#ff0000'}}>
);
}
If you want to persist you address through the component lifecycle using AsyncStorage then you should first save it properly and once you get back on the screen, fetch it and populate back it into your state, as component's state needs to be reinitialised using your AsyncStorage.
The following code should help initialize the address back to the stored one:
componentDidMount () {
AsyncStorage.getItem('address').then(value => {
if (value) {
this.setState({address: value});
}
})
}
and the below specifies the correct way to set the data to AsyncStorage:
saveAddressEdit = async (fieldName, fieldValue) => {
await AsyncStorage.setItem(fieldName, fieldValue)
}
OR you can also you the promise way just like I did in getItem above like setItem(fieldName, fieldValue).then(value => console.log('saved')) but since we don't need to handle anything in the then so I resist using promise in such places.

function returning data but not showing

I have this component
const SummaryBar = props => {
const { MainObject} = props;
const localGetUserFromID = userID => {
getEmailFromId(userID).then(results => {
return results.data.Title; //Comment: This one returning friendly name
});
};
return (<span>Hello {localGetUserFromID(MainObject.AuthorId)}</span>)
}
but when I render it somehow the its only showing Hello and not the output I am getting from my localGetUserFromID function. Am I doing wrong? Note the AuthorId is being pass to an API and the MainObject came from the App Level,
FYI when I try to debug it using dev tools the function is retuning the text I am look for.
localGetUserFromID() returns nothing, that is, undefined, and that's why you see Hello only.
And because localGetUserFromID() makes an asynchronous call to get an email from user ID, it doesn't have to be in render() method. Now this component is defined as a state-less component, but you can re-define it as a stateful component, call the getEmailFromId() in componentDidMount() life-cycle method, and use a return value as an internal state.
Then you can show a value of the internal state after Hello.
class SummaryBar extends Component {
// Skipping prop type definition.
constructor(props) {
super(props)
this.state = {
username: '',
}
}
componentDidMount() {
const { MainObject: { AuthorId } } = this.props
getEmailFromId(AuthorId).then((results) => {
this.setState({
username: results.data.title,
})
})
}
render() {
const { username } = this.state
return (
<span>
Hello { username }
</span>
)
}
}
When things run when debugging but not when running and you are using promises as you are, the 99% of the times is because promises hasn't been resolved when you print.
localGetUserFromID indeed returns a promise that resolves to the friendly name.
You can just prepend await to localGetUserFromID(MainObject.AuthorId) and rewrite you return as this:
return (<span>Hello {await localGetUserFromID(MainObject.AuthorId)}</span>)

Reactjs Select v2 - How to handle Ajax Typing?

I am using reactjs select 2 but I don't know how to make it work so that when a user types something in a ajax request is made and the results are sent back.
I see it has some async options but I don't get how it works and how I would get it to work with axios.
I come up with this but it is kinda laggy when a user types(probably because it is re-rendering it after each type) and when the user selects a choice the value disappears.
export default class TestComponent extends Component {
constructor(props) {
super(props);
this.state = {value: ""};
}
onInputChange(option) {
this.getOptionsAsync(option)
}
getOptionsAsync(newInput) {
var that = this;
console.log("ffd", newInput)
axios.get(`https://localhost:44343/api/States/GetStatesByText?text=${newInput}`)
.then(function (response) {
var formatedResults = response.data.map((x)=> {
return {value: x.id, label: x.name}
})
that.setState({
options: formatedResults,
value: newInput
})
})
.catch(function (error) {
});
}
render() {
console.log(this.state.value, "value")
return (
<div className="test">
<Select
onInputChange={this.onInputChange.bind(this)}
value={this.state.value}
options={this.state.options }
/>
</div>
);
}
}
You're going to be doing an api call every single time that you type a letter with the current way you're doing things. I would recommend just loading the states once at the beginning, perhaps in your ComponentDidMount() method.
If you pass the isSearchable prop to React-Select it will automatically work as a filter anyways.
Another thing I've had to do in this case which I believe will fix your change problem is to make sure it calls the handler on change not just on input change.
Pass this prop:
<Select
value={this.state.value}
options={this.state.options }
onChange={value => {
if (value) this.onInputChange(value)
else this.onInputChange('')
}
/>
Due to the way this is automatically bound to arrow functions, you won't have to bind to this if you change your onInputChange to the following:
onInputChange = (value) => {
this.getOptionsAsync(value)
}
Finally, you should be setting the state in the above function so the value is stored.
onInputChange = (value) => {
this.getOptionsAsync(value)
this.setState({value})
}

Resources