How do you bind a dynamic API response to a React UI? - reactjs

I am consuming an API which has a few 'required' fields and fields which are not required. As such, depending on the result of the response, the shape can change dramatically with fields being excluded in the response.
An example Schema:
{
name: string; // required
surname: string; // required
addresses: []
telephoneNumbers: []
gender: string; // required
age: number;
}
So a request could return multiple formats:
Example: 'api.com/users/123'
{
name: 'John',
surname: 'Doe',
addresses: [ ....]
telephoneNumbers: [....]
gender: 'male',
age: 20
}
Example: 'api.com/users/456
{
name: 'Betty',
surname: 'Boo',
gender: 'female',
}
When I bind the response to my UI (react) is there a way to check each field exists?
Do I have to check if each field exists and has a value in the response before rendering it? As in:
const DisplayResults = () => {
const { data } = await api.getUser('123');
return (
<div>
<div>{data.name}</div>
<div>{data.surname}</div>
{data.age && <div>{data.age}</div>}
{data.gender && <div>{data.age}</div>}
{data.addresses && <div>{data.addresses.map((address) => ...)}</div>}
</div>
)
}
Bear in mind any nested objects may have 'required' and non required fields which means I either need to sanitize the response to match the response to how I want or do a check on each field to make sure it's not undefined.
If I bind to a grid component, it'll throw errors for undefined fields which may or may not be there.
Any ideas?

Seems like depending on the type of data you need a different way to display the data.
You could create a piece of state which is an array of Objects that hold info on how to handle each key.
import React from 'react'
import ReactDOM from 'react-dom'
class User extends React.Component {
constructor(props) {
super(props);
this.state = {
user_rows: [
{
key_name: "name",
data_function: (data = "") => data,
display:(data) => data.length > 0 ? true : false
},
{
key_name: "addresses",
data_function:(data = []) => data.map((a,idx) => {
return( <div>{a}</div>)
}),
display:(data) => data.length > 0 ? true : false
},
{
key_name: "gender",
data_function:(data = "") => data,
display:(data) => data.length > 0 ? true : false
}
]
}
}
render() {
let data = {
name: 'John',
surname: 'Doe',
addresses: ["adress 1"],
telephoneNumbers: ["000 000 000"],
gender: '',
age: 20
}
return (
<div>
{this.state.user_rows.map((ur,idx) => (
ur.display && <div key={idx}>{ur.data_function(data[ur.key_name])}</div>
))}
</div>
);
}
}
ReactDOM.render(
<User/>,
document.getElementById('container')
);

Related

Where do I add functions in React?

I'm running through several tutorials, and I don't quiet undertand where I can write/put multiline functions?
Say I needed this filter function
const filter = {
address: 'England',
name: 'Mark'
};
let users = [{
name: 'John',
email: 'johnson#mail.com',
age: 25,
address: 'USA'
},
{
name: 'Tom',
email: 'tom#mail.com',
age: 35,
address: 'England'
},
{
name: 'Mark',
email: 'mark#mail.com',
age: 28,
address: 'England'
}
];
users= users.filter(item => {
for (let key in filter) {
if (item[key] === undefined || item[key] != filter[key])
return false;
}
return true;
});s
Where do I put it in React? Most tutorials are focused on App.js page, and the only thing i've learned was to add code here {in brackets}:
<div>
<h3> {1+1} </h3>
</div>
However, it doesn't make sense/looks bulky and unreadable to add the function above just inside there? Or is that the correct way? I'm currently focused on learning Javascript and frontend React only at the moment (not backend).
Here's an example of how you could use that function in React:
Define the function within the App.js file (inside the App component)
Call that function inside the JSX that you're returning
const App = () => {
const yourFunction = () => {
users.filter(function(item) {
for (var key in filter) {
if (item[key] === undefined || item[key] != filter[key])
return false;
}
return true;
});
}
return (
<div>
<h1>{yourFunction()}</h1>
</div>
);
}
It works the same way as putting javascript inside the JSX - it just runs what's inside the tags and in this case, that means running the function outside the returning JSX.

Update array value inside observable typescript

How do you write a function that takes an Observable<T[]> and updates the value inside the observable.
I tried with the below approach but i felt i am not doing the update on proper way.
I want to update the values of the observable with the row object.
import { of } from 'rxjs';
import { map } from 'rxjs/operators';
let gridData = [
{
id: '1',
firstName: 'Sophie',
lastName: 'Beckham',
gender: 'F',
jobTitle: 'Senior Associate-L1',
jobArea: 'London',
monthlyWage: '$100000',
},
{
id: '2',
firstName: 'Sophie',
lastName: 'Beckham',
gender: 'F',
jobTitle: 'Senior Associate-L2',
jobArea: 'London',
monthlyWage: '$100000',
},
{
id: '3',
firstName: 'Chloe',
lastName: 'Bryson',
gender: 'F',
jobTitle: 'Senior Associate-L3',
jobArea: 'London',
monthlyWage: '$100000',
},
];
let gridData$ = of(gridData);
let row = {
id: '2',
firstName: 'TAN',
lastName: 'Ara',
gender: 'M',
jobTitle: 'Senior Associate',
jobArea: 'ATL',
monthlyWage: '$130000',
}
gridData$.subscribe((val: any) => {
val.forEach((list: any) => {
if(list['id'] === row['id']){
for (const property in row) {
list[property] = row[property];
}
}
})
})
gridData$.subscribe((v) => console.log(v));
In my approach i have used for loop and based on the object id updating the value. If there is a better approach, kindly share your approach.
StackBlitz : https://stackblitz.com/edit/rxjs-gdgkyk?devtoolsheight=60
Observables are stateless and its recommended that emitted values are immutable.
But you can return a new observable that returns what you want by using the map operator (on all values).
Note that I've used from instead of of operator.
import { from } from 'rxjs';
import { map } from 'rxjs/operators';
...
let gridData$ = from(gridData);
...
gridData$ = gridData$.pipe(
map((val: any) => {
if(val['id'] === row['id']){
for (const property in row) {
val[property] = row[property];
}
}
return val;
})
);
gridData$.subscribe((v) => console.log(v));
Now each time you call gridData$ it will execute the new observable.
https://stackblitz.com/edit/rxjs-6mh6iu

React Nested Array

I need to be able to combine this into one function. There are 2 separate arrays...
{this.state.telephoneType.map((telephoneType, ttidx) => ())}
and...
{this.state.telephone.map((telephone, tidx) => ())}
Basically, this is because I have a button which concatenates the 2 functions and it has to be outside the row class (MDBRow) so the UI doesn't break.
<MDBRow className="grey-text no-gutters my-2">
{this.state.telephoneType.map((telephoneType, ttidx) => (
<MDBCol md="4" className="mr-2">
<select
key={ttidx}
defaultValue={telephoneType.name}
onChange={this.handleTelephoneTypeChange(ttidx)}
className="browser-default custom-select">
<option value="Mobile">Mobile</option>
<option value="Landline">Landline</option>
<option value="Work">Work</option>
</select>
</MDBCol>
))}
{this.state.telephone.map((telephone, tidx) => (
<MDBCol md="7" className="d-flex align-items-center">
<input
value={telephone.name}
onChange={this.handleTelephoneChange(tidx)}
placeholder={`Telephone No. #${tidx + 1}`}
className="form-control"
/>
<MDBIcon icon="minus-circle"
className="mr-0 ml-2 red-text"
onClick={this.handleRemoveTelephone(tidx)} />
</MDBCol>
))}
</MDBRow>
<div className="btn-add" onClick={this.handleAddTelephone}>
<MDBIcon className="mr-1" icon="plus-square" />
Add Telephone
</div>
This is the handleAddTelephone function...
handleAddTelephone = () => {
this.setState({
telephone: this.state.telephone.concat([{ name: "" }]),
telephoneType: this.state.telephoneType.concat([{ name: "" }])
});
};
and the Constructor looks like this...
class InstallerAdd extends React.Component {
constructor() {
super();
this.state = {
role: "Installer",
name: "",
telephoneType: [{ name: "" }],
telephone: [{ name: "" }],
tidx: "",
emailType: [{ email: "" }],
email: [{ email: "" }],
eidx: "",
notes: ""
};
}
}
Can I nest one array inside the other? I'm not sure how to do this so any advice appreciated. Thanks.
Edit:
These are the 2 telephone functions which need to be 1 function...
I have updated with new nested array for each
handleTelephoneChange = tidx => evt => {
const newTelephone = this.state.telephone.type.map((telephone, tsidx) => {
if (tidx !== tsidx) return telephone;
return { ...telephone, name: evt.target.value };
});
this.setState({ telephone: newTelephone }, () => {
// get state on callback
console.log(this.state.telephone.number[tidx].name)
}
);
};
handleTelephoneTypeChange = ttidx => evt => {
const newTelephoneType = this.state.telephone.number.map((telephoneType, ttsidx) => {
if (ttidx !== ttsidx) return telephoneType;
return { ...telephoneType, name: evt.target.value };
});
this.setState({ telephoneType: newTelephoneType }, () => {
// get state on callback
console.log(this.state.telephone.type[ttidx].name)
}
);
};
My constructor now looks like this...
class InstallerAdd extends React.Component {
constructor() {
super();
this.state = {
role: "Installer",
name: "",
telephone: {
type: [{ name: "" }],
number: [{ name: "" }]
},
tidx: "",
emailType: [{ email: "" }],
email: [{ email: "" }],
eidx: "",
notes: ""
};
}
Although I still don't quite understand how your UI "breaks" (video won't load for me), I hope I can help.
Basically the short answer about trying to map two arrays singly is that you can't (well, shouldn't), but with some assumptions about the two array's length always being equal and the order is the same between the two, then you can map over the first array and use the passed index from the map function to access the second.
arrayA.map((itemFromA, index) => {
// do stuff with item from A
const itemFromB = arrayB[index];
// do stuff with item from B
});
I think the better solution is to keep only a single array in state to map over in the first place.
state = {
telephones: [], // { type: string, number: string }[]
}
...
state.telephones.map(({ type, number }, index) => {
// do stuff with telephone type
...
// do stuff with telephone number
});
If you also really want only a single change handler (I recommend they be separate), you can adopt a redux action/reducer type handler that accepts an object as a parameter that has all the data you need to update an array entry (index, update type, value). Here's a pretty clean example, but requires a bit of "action" setup when called:
changeHandler = (index, type) => e => {
const newTelephoneData = [...this.state.telephones];
newTelephoneData[index][type] = e.target.value;
this.setState({ telephones: newTelephoneData });
}
<select
value={type}
onChange={this.telephoneChangeHandler(index, "type")}
>
// options
</select>
...
<input
value={number}
onChange={this.telephoneChangeHandler(index, "number")}
placeholder={`Telephone No. #${index + 1}`}
/>
Below I've created a few working sandbox demos:
Dual telephone data arrays, single change ("action/reducer") handler, separate add/remove functions, single map pass using index accessor to second array:
Single telephone data array, single change handler, separate add/remove, single map pass:
Use react useReducer, single array, reducer handles add/remove/update, single map pass:
I've included code documentation but if anything is unclear please let me know and I can clean up these demos.
Yes, absolutely, something along:
telephone: {
list: [1,2,3],
type: [“foo”, “bar”, “baz”]
}

Redux state is being updated without dispatching any action

I should start off by saying this is not a duplicate of this question, which happened to have the same title.
I'm simply getting a customers object of arrays from props inside a componentDidMount method like this;
componentDidMount() {
const { customers } = this.props
const expiringCustomers = getExpiringCustomers(customers)
console.log('Expiring customers ', expiringCustomers)
}
Inside another file, I have that getExpiringCustomers function which takes the customers passed and is suppose to return a newly modified list of customers like this;
function numbersOnly(value) {
if(_.isString(value)) {
value = Number(value.replace(/[^\d]/g, ''))
}
return value
}
function normalizeNumber(collection, field) {
return collection.map(obj => {
obj[field] = numbersOnly(obj[field])
return obj
})
}
export function getExpiringCustomers(customers) {
const expiringCustomers = customers.filter(customer => {
const daysLeft = Number(new Date(customer.endDate)) - _.now()
if(daysLeft <= (dateInMonth * 3)) {
return customer
}
})
return normalizeNumber(expiringCustomers, 'rent')
}
I'm connecting my react component with redux state like this;
const mapStateToProps = state => ({
customers: state.customers.filter(customer => customer && !customer.deleted)
})
export default connect(mapStateToProps)(Accounting)
Problem
After the functions run and log results, customers' state is changed in redux store.
This is very confusing as customers_edit action has to pass through some procedures but none of them are called/logged.
Snapshot of the affected object:
Ps. The data is just boilerplate.
//- Focus on rent property
const customers = [
...,
{
id: 'o91wukyfsq36qidkld02a0voo93rna5w',
cardId: 'GD-1101010111',
id_type: 'Driving License',
firstName: 'Maalim',
lastName: 'Guruguja',
names: 'Maalim Guruguja',
property: '5iaprurefg3v3uhad688mypo9kqf6xk3',
rent: '250,000',
email: 'tonimarikapi#yahoo.com',
phone: '239-288-3838-38',
noticePeriod: '3',
status: '2 months remain',
startDate: '2018-07-09',
endDate: '2018-08-17',
createdAt: 1530623480772,
updatedAt: 1531213159147
},
...
]
//- After the functions run, log and edit customers array
const customers = [
...,
{
id: 'o91wukyfsq36qidkld02a0voo93rna5w',
cardId: 'GD-1101010111',
id_type: 'Driving License',
firstName: 'Maalim',
lastName: 'Guruguja',
names: 'Maalim Guruguja',
property: '5iaprurefg3v3uhad688mypo9kqf6xk3',
rent: 250000,
email: 'tonimarikapi#yahoo.com',
phone: '239-288-3838-38',
noticePeriod: '3',
status: '2 months remain',
startDate: '2018-07-09',
endDate: '2018-08-17',
createdAt: 1530623480772,
updatedAt: 1531213159147
},
...
]
From the linked question (possible duplicate one) the guy who answered stated that it's some mutation issue that may cause this. I'm not sure if that applies on props that are suppose to be read-only.
How can I stop these functions from updating my redux store, please help.
You mutate the objects in normalizeNumber, since all the array methods you use don't clone the array's objects.
Change normalizeNumber callback to return a new object with the updated field:
function normalizeNumber(collection, field) {
return collection.map(obj => ({
...obj,
[field]: numbersOnly(obj[field])
}))
}
It looks like you're modifying the customers array unintentionally.
Try:
componentDidMount() {
const { customers } = { ...this.props };
const expiringCustomers = getExpiringCustomers(customers)
console.log('Expiring customers ', expiringCustomers)
}

SetState of an array of Objects in React

Ok, so I'm so frustrated finding the right solution so I'm posting the problem here. Giving an answer would help me a lot, coz I'm stuck!
the state tree looks like this
this.state = {
itemList : [{
_id : 1234,
description : 'This the description',
amount : 100
}, {
_id : 1234,
description : 'This the description',
amount : 100
}],
}
The problems are :
can not update any specific key in the Object of the array according
to the _id
The previous state should remain intact
answered March 25 2018
This is how you would use setState and prevstate to update a certain attribute of an object in your data structure.
this.setState(prevState => ({
itemList: prevState.itemList.map(
obj => (obj._id === 1234 ? Object.assign(obj, { description: "New Description" }) : obj)
)
}));
answered Dec 12 2019 (REACT HOOKS)
import React, { useState } from 'react';
const App = () => {
const [data, setData] = useState([
{
username: '141451',
password: 'password',
favoriteFood: 'pizza',
},
{
username: '15151',
password: '91jf7jn38f8jn3',
favoriteFood: 'beans'
}
]);
return (
<div>
{data.map(user => {
return (
<div onClick={() => {
setData([...data].map(object => {
if(object.username === user.username) {
return {
...object,
favoriteFood: 'Potatos',
someNewRandomAttribute: 'X'
}
}
else return object;
}))
}}>
{JSON.stringify(user) + '\n'}
</div>
)
})}
</div>
)
}
to update state constructed like this you will have to find index of element you want to update, copy the array and change found index.
it's easier and more readable if you keep list of records as object, with id as a key and record as a value.
The only way to do this will be to copy itemList, modify it, and set the state to it.
update() {
let itemList = this.state.itemList.slice();
//update it
this.setState({ itemList });
}
Best way to update data into an array of objects
onChange={ (e) => this.setState({formData: { ...this.state.formData, 'plan_id': e.target.value}})}

Resources