parse string from an api to html - arrays

I have api call that sets a collection of 10 objects into a state array. Below is an example of that code:
class Example extends Component {
constructor(props) {
super(props);
this.state = {
quiz_data: [],
quiz_answers:[]
}
}
componentDidMount() {
Axios.get('api call here')
.then(response => {
this.setState({ 'quiz_data': response.data });
})
}
Then, I map over that state array like so:
this.quizData = this.state.quiz_data.map((item, id) => {
return (
<div key={id}>
<h3 className='quiz-question'>{item.Title}</h3>
<p>{item.Question}</p>
</div>
)
})
My question/problem is, that the item.question is returned as a string in the array, and comes out that way.
So i end up with example text with code inside instead of just example text.
How do i get it to return as html instead of a string?

why dont you try setting the html dangerously read here
this.quizData = this.state.quiz_data.map((item, id) => {
return (
<div key={id}>
<h3 className='quiz-question'>{item.Title}</h3>
<div dangerouslySetInnerHTML={{__html:item.Question}} />
</div>
)
})

Related

How to call API and render a new component

What would be a way to call an API in a component class (do not use a functional component) and render the received data in a new component. I am asking for the order of operation to setState. My current solution includes setState and a function as a second argument that set anther state. Function getStockInfo calls API and then I want to render the GetQuote component switching showComponent to true. Note that in ComponentDidMount I call an initial API. Please let me know if this is a valid solution or there is a neater way.
class Portfolio extends Component {
constructor(props) {
super(props);
this.state = {
res: " ",
data: [],
showComponent: false,
};
this.showStockiaDetails = this.showStockDetails.bind(this);
this.getStockInfo = this.getStockInfo.bind(this);
this.handleClick = this.handleClick.bind(this);
}
async getStockInfo(item) {
const stock = item.symbol;
const API_KEY = "1D";
const PATH = `https://www.alphavantage.co/query?function=OVERVIEW&symbol=${stock}&apikey=${API_KEY}`;
await axios
.get(PATH)
.then((response) => {
this.setState({ res: response.data }, () => this.showStockDetails());
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
}
handleClick(item) {
this.getStockInfo(item);
}
showStockDetails() {
console.log("-->", this.state.res);
this.setState({ showComponent: true });
}
componentDidMount() {
axios
.get("http://127.0.0.1:8000/api/data")
.then((response) => {
this.setState({ data: response.data });
})
.catch(function (error) {
console.log(error);
});
}
render() {
return (
<div className="container">
<h1>Day Eight</h1>
<div className="row col-12">
<div className="col-md-5 col-lg-4 ">
<ul className="list-group">
{this.state.dayeightData.map((s) => (
<li
key={s.id}
onClick={() => this.handleClick(s)}
className="list-group-item"
aria-current="true"
>
{s.symbol}
</li>
))}
</ul>
</div>
<div className="col-md-7 col-lg-8 order-md-last">
{this.state.showComponent ? (
<GetQuote stock={this.state.res} />
) : null}
</div>
</div>
</div>
);
}
}
export default Portfolio;
You did it right. There would be many other alternatives too depending on what GetQuote component is rendering. To make it look a little bit more cleaner you can do something like this:
{
this.state.res ? <GetQuote stock={this.state.res} /> : ''
}
But make sure to initialize res as "" (without any space) as "" will be false and " " will be true.
In that way, you don't really have to call showStockDetails function.

Render data from async method is undefined

I'm trying to render data from my MongoDB document but I can't figure out why the state is undefined when it's an async function and I'm awaiting it.
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
offers: null
};
this.getOffers = this.getOffers.bind(this);
this.renderOffer = this.renderOffer.bind(this);
}
componentDidMount() {
if(!this.state.offers) {
this.getOffers();
}
}
async getOffers() {
let res = await offerService.getAll();
this.setState({ offers: res });
}
renderOffer(product) {
return (
<li key={product._id} className="list__item product">
<h3 className="product__name">{product.title}</h3>
</li>
);
};
render() {
return (
<div className="App">
<Nav />
<ul className="list">
{(this.state.offers && this.state.offers.length > 0) ? (
this.state.offers.map(offer => this.renderOffer(this.state.offer))
) : (
<p>Loading...</p>
)}
</ul>
</div>
);
}
}
Whenever I load the page, it returns TypeError: Cannot read property '_id' of undefined, despite the fact that if I log the output of res in getOffers() it shows the data. Why is this?
The problem lies in your map call:
this.state.offers.map(offer => this.renderOffer(this.state.offer))
You're attempting to use the undefined variable this.state.offer rather than your offer map variable. What you actually want is:
this.state.offers.map(offer => this.renderOffer(offer))

How convert date properly in ReactJS frontend side

My API return some array of objects with LocalDate string and when I get it in my frontend side I want to convert it from state to JS Date object with timezone.
I tried to do it in componentDidMount() function after fetching API data but after setting state react developer tool shows me that dateList state is udentified.
DateComponent
class DateComponent extends Component {
constructor(props) {
super(props);
this.state = {
dateList: [],
id: this.props.id,
}
}
componentDidMount() {
DateService.retrieveProfile(this.state.id)
.then(response => {
this.setState({
dateList:this.convertDatesToTimezoneUTC(response.data.dateList)
})
})
}
convertDatesToTimezoneUTC(dateList) {
dateList.map((value) => (
value.date = new Date(value.date),
)
);
}
render() {
let {dateList, id} = this.state;
return (
<div>
<Formik
enableReinitialize
initialValues={{dateList}}>
<FieldArray name="dateList"
render={() => (
<div>
{dateList.map((value, index) => (
<div className={"m-3 form-row"} id={value.id} key={index}>
<Field className="form-control col-md-2 mr-2" type="text"
name={`dateList[${index}].date`} readOnly/>
</Formik>)
}
</div>
)
}
}
export default DateComponent;
Model
dateList[{
id:"some id"
date:"2019-08-16"
}]
Without convert convertDatesToTimezoneUTC() function everything is alright and return data properly. Did I miss something ?
I guess you forgot to return from the convertDatesToTimezoneUTC():
convertDatesToTimezoneUTC(dateList) {
return dateList.map((value) => (
value.date = new Date(value.date),
));
}
You need to return your newly computed array from convertDatesToTimezoneUTC function, also you need to only change the date and keep rest of the object properties as it is,
convertDatesToTimezoneUTC = (dateList) => {
return dateList.map((value) => ({
...value,
date : new Date(value.date),
})
)
}

How to use react stateless component when API response didn't got yet?

I'm working with react in Laravel, and i'm trying to built a simple FriendsList component to display data from API.
The problem is that the Parent (Profile) component is finish loading before it's get the data, so the FriendsList component return an error, because the props are empty for the first time. It's important to say that regardless of the API response - the parent (Profile) component works well, it's loaded for the first time empty - and then the data is adding.
The Api call
export const getProfile = () => {
return axios
.get('api/profile', {
headers: { Authorization: `Bearer ${localStorage.usertoken}` }
})
.then(response => {
// console.log(response.data)
return response.data
})
.catch(err => {
console.log(err)
})
}
The Parent Component
import React, { Component } from 'react'
import { getProfile } from './UserFunctions'
import FriendsList from './FriendsList';
class Profile extends Component {
constructor() {
super()
this.state = {
name: '',
hobbies: '',
user_bday: '',
members: [],
error: '',
}
}
componentDidMount() {
getProfile().then(res => {
// console.log(JSON.parse(res))
this.setState({
name: res.user.name,
hobbies: res.user.hobbies,
user_bday: res.user.user_birthday,
related_friends: res.user.related_friends,
members: res.user.members,
})
})
}
render() {
return (
<div className="container">
<div className="jumbotron mt-5">
<div className="col-sm-4 mx-auto">
<h1 className="text-center">PROFILE</h1>
</div>
<table className="table col-md-4 mx-auto">
<tbody>
<tr>
<td>Name</td>
<td>{this.state.name}</td>
<td>{this.state.hobbies}</td>
<td>{this.state.user_bday}</td>
</tr>
</tbody>
</table>
<FriendsList members={this.state.members}>
</FriendsList>
</div>
</div>
)
}
}
export default Profile
import React from 'react';
class FriendsList extends React.Component {
render() {
console.log(this.props)
const { members } = this.props;
const listmembers = members.map((item, index) => (
<li key={item + index}>{item.name}</li>
));
return (
<div>
{listmembers}
</div>
);
}
}
export default FriendsList
There are a couple of ways to go about this.
First approach:
class Profile extends Component {
render() {
// check if your `state` has all the necessary values
// before rendering your JSX
const { name, hobbies, user_bday, members } = this.state
const shouldRender = name !== '' &&
hobbies !== '' &&
user_bday !== '' &&
Array.isArray(members) && members.length > 0
if (!shouldRender) {
return null;
}
return (...)
}
}
This way, you're only rendering JSX when your state has everything that you need.
Second approach:
class Profile extends Component {
constructor() {
// ...
this.setState = {
members: []
}
}
}
Set your members to an empty array, rather than an empty string, so that way when you're passing it as prop to FriendList component, calling this.props.friends.map is actually correct, but it won't render anything since the array is initially empty.
Also, it looks like you are never updating your members after your API call finishes:
componentDidMount() {
getProfile().then(res => {
this.setState({
name: res.user.name,
hobbies: res.user.hobbies,
user_bday: res.user.user_birthday,
related_friends: res.user.related_friends,
})
})
}
So your members actually stays as an empty string. Make sure your updating your state with the right type, which in this case should be an array.
if I understood your question I guess you need to take a look at https://www.npmjs.com/package/prop-types, I think your problem is with the default props value and this library could help you achieve the wanted behavior.
If I understood the question correctly you're talking about null props... Also, Kellen in the comments is correct... You should set members to an empty array instead and I do not see you updating the state for members in your setState...
Try:
render() {
const friendsList =
this.props.firends &&
this.props.friends.map(function(item, index) {
<li key={index}>{item.name}</li>;
});
return (
<div>
<ul>{friendsList}</ul>
</div>
);
}
Another approach here would be to use loading state, during which you'll show a loading indicator, etc.
class Profile extends Component {
constructor() {
super();
this.state = {
name: "",
hobbies: "",
user_bday: "",
members: "",
error: "",
isMembers: false,
loading: true // Add loading state
};
}
componentDidMount() {
getProfile().then(res => {
// console.log(JSON.parse(res))
this.setState({
name: res.user.name,
hobbies: res.user.hobbies,
user_bday: res.user.user_birthday,
related_friends: res.user.related_friends,
loading: false // Remove the loading state when data is fetched
});
});
}
render() {
return this.state.loading ? (
<p> Loading... </p>
) : (
<div className="container">
<div className="jumbotron mt-5">
<div className="col-sm-4 mx-auto">
<h1 className="text-center">PROFILE</h1>
</div>
<table className="table col-md-4 mx-auto">
<tbody>
<tr>
<td>Name</td>
<td>{this.state.name}</td>
<td>{this.state.hobbies}</td>
<td>{this.state.user_bday}</td>
</tr>
</tbody>
</table>
{/*<FriendsList friends={this.state.members}></FriendsList>*/}
</div>
</div>
);
}
}

Setting State Array and Appending Value on Update

I'm still learning about state and lifecycle with ReactJS and have run into a scenario where I have a form that on submit should save the form value and then append the returned JSON object to the end of an array which would re-render the component storing the original array.
With my current setup, I have the components setup and form submit with returned JSON object, but the state contains an empty array rather than the object spread {...comment} and it doesn't look like the setState is updating component, but that could be due to the empty array mentioned before. Can anyone point me in the right direction?
Comment:
import React from 'react';
import fetch from 'node-fetch';
//record Comment - Comment Form Handle POST
class CommentForm extends React.Component {
constructor(props){
super(props);
this.state = {
value: '',
comments: []
};
this.onChange = this.onChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
postComment(comment, recordId, csrfToken) {
var body = { comment: comment };
var route = 'http://localhost:3000/record/' + recordId + '/comment';
fetch(route,
{
method: 'POST',
body: JSON.stringify(body),
headers: {
'X-CSRF-Token': csrfToken,
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then(res => {
return res.json();
})
.then(data => {
console.log(data);
let commentsArr = this.state.comments;
this.setState({comments: commentsArr.concat(data)});
})
.catch(err => {
console.log(err);
});
}
onChange(e){
this.setState({
value: e.target.value
});
}
handleSubmit(e){
e.preventDefault();
this.postComment(this.state.value, this.props.recordId, this.props.csrf);
}
render(){
return (
<div className="record-comment__form">
<div className="row">
<form action={"/record/" + this.props.recordId + "/comment"} method="post" onSubmit={this.handleSubmit}>
<input type="hidden" name="_csrf" value={this.props.csrf}/>
<textarea name="comment" className="record-comment__form-text-area" onChange={e => this.setState({ value: e.target.value })} value={this.state.value}></textarea>
<button type="submit" className="record-comment__form-button" disabled={!this.state.value}>Comment</button>
</form>
</div>
</div>
)
}
}
//record Comment - Comment
const Comment = props => {
return (
<div className="row">
<div className="col-md-12">
<h5>{props.user_id}</h5>
<h4>{props.comment}</h4>
<h3>{props.synotate_user.fullNameSlug}</h3>
</div>
</div>
)
}
//record Comment - Container
export default class Comments extends React.Component {
render() {
return (
<div className="record-comment-container">
<CommentForm recordId={this.props.recordId} csrf={this.props.csrf}/>
{ this.props.record_comments.map((comment, i) =>
<Comment {...comment} key={this.props.recordCommentId}/>
)}
</div>
);
}
}
Record (Parent component)(Where Comment is being set):
//GET /api/test and set to state
class RecordFeedContainer extends React.Component{
constructor(props, context) {
super(props, context);
this.state = this.context.data || window.__INITIAL_STATE__ || {records: []};
}
fetchList() {
fetch('http://localhost:3000/api/test')
.then(res => {
return res.json();
})
.then(data => {
console.log(data);
this.setState({ records: data.record, user: data.user, csrf: data.csrfToken });
})
.catch(err => {
console.log(err);
});
}
componentDidMount() {
this.fetchList();
}
render() {
return (
<div className="container">
<h2>Comments List</h2>
<RecordFeed {...this.state} />
</div>
)
}
};
//Loop through JSON and create Record and Comment Container Component
const RecordFeed = props => {
return (
<div>
{
props.records.map((record, index) => {
return (
<div className="row">
<div className="col-md-6 col-md-offset-3 record-card">
<RecordCard {...record} key={record.recordIdHash} user={props.user} />
<Comments {...record} key={index} recordId={record.recordIdHash} csrf={props.csrf}/>
</div>
</div>
);
})
}
</div>
)
}
Your problem is that when rendering <Comments>, the this.props.record_comments is not the comments you've updated in the state of the <CommentForm> component. Each component has it's own internal state.
You need to pass the state along to your <Comments> component. You will need to move your state up to the top level or use a state management system like Redux which will allow you to access a shared state which could contain your comments array.
From the top level component you could manage the state there, like so:
this.state = {
comments: [],
// other shared state
};
You can pass along an update comments function, named for example updateCommentsFunc() to <CommentForm> like so:
<CommentForm updateComments={this.updateCommentsFunc} recordId={this.props.recordId} csrf={this.props.csrf}/>
Which will allow you to pass the updated comments back up to the parent component via something like:
const updateCommentsFunc = (newComments) => {
this.setState({comments: [...this.state.comments, newComments]});
}
Your postComment() function doesn't appear to be properly bound to your enveloping <CommentForm/> component's this. As a result; calling this.setState() from within the function isn't really doing anything.
Try binding it within your constructor method.
constructor(props) {
// ...
this.postComment = this.postComment.bind(this)
}
Or by declaring it using an arrow function.
postComment = (comment, recordId, csrfToken) => {
// ...
}
See this article for more info on React binding patterns.

Resources