Load date by request fetch() - reactjs

I have got a JSON file and I want to call it by fetch() request. My JSON looks like this:
{
id: { hotelId: "102835", provider: { provider_id: "23", dmnid: 3984 } },
hotelinfo: {
name: "Pera Rose Hotel",
hotelsearch: {
realname: "Pera Rose Hotel",
hotelid: 0.0,
hotelimage: "",
webserviceimage:
"http://service.stage.Paximum.com/media/images/product/2/1/2/2/102835-fed561d75dec40ca4d83fd6fc9da9967-jpg/pera_rose_hotel.jpg",
countryid: 1002247,
ecountryname: "Turkey",
ecityname: "Istanbul",
cityid: 1177676,
star: 4,
desc:
"This hotel is located in the Istanbul's business, shopping and entertainment centre, around a 5-minute walk from the historical Galata Tower, the world's second oldest subway and some 8 minutes' walk away from Taksim Square. In Taksim itself, around 5 minutes' walk from the hotel, guests will find restaurants, bars, shops and clubs. The nearest underground station is Taksim-Meydan, a 10-minute walk away and guests will find the Hagia Sophia, the Topkapi Palace, the Grand Bazaar and the Egyptian Market all around a 15-minute ride away by public transport, as is Sirkeci Station. Istanbul Airport is around 15 km away.",
enable: "",
delete: ""
},
information: { viewname: "-" }
}
}
But my setState function is not executing and there is this error:
SyntaxError: "JSON.parse: expected ',' or '}' after property value in
object at line 1 column 549 of the JSON data"
And it is because of fieldd desc. As you can see there are some words in desc such as Istanbul's which have '. Is there any way to solve this problem?
(When I use ajax() request to load json.bc file there is no error.)
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
library: null,
perPage: 20,
currentPage: 1,
maxPage: null,
filter: ""
};
}
componentDidMount() {
fetch("/json.bc", {
method: "POST",
body: "cityid=[##cms.form.cityid##]"
})
.then(response => response.text())
.then(text => {
var Maindata = JSON.parse(text.replace(/\'/g, '"'));
this.setState(
state => ({
...state,
data: Maindata
}),
() => {
this.reorganiseLibrary();
}
);
})
.catch(error => console.error(error));
}
reorganiseLibrary = () => {
const { filter, perPage, data } = this.state;
let library = data;
if (filter !== "") {
// ...
}
library = _.chunk(library, perPage);
this.setState({
library,
currentPage: 1,
maxPage: library.length === 0 ? 1 : library.length
});
};
previousPage = event => {
this.setState({
currentPage: this.state.currentPage - 1
});
};
nextPage = event => {
this.setState({
currentPage: this.state.currentPage + 1
});
};
handlePerPage = evt =>
this.setState(
{
perPage: evt.target.value
},
() => this.reorganiseLibrary()
);
renderLibrary = () => {
const { library, currentPage } = this.state;
if (!library || (library && library.length === 0)) {
return <div class="nodata">No Result</div>;
}
return library[currentPage - 1]
.sort((a, b) => a.total - b.total)
.map((item, i) => <div class="item">{item.id}</div>);
};
render() {
const { library, currentPage, perPage, maxPage } = this.state;
return (
<div className="Main-wrapper">
<div className="wrapper-data">{this.renderLibrary()}</div>
<ul id="page-numbers">
<li class="nexprev">
{currentPage !== 1 && (
<button onClick={this.previousPage}>
<span class="fa-backward" />
</button>
)}
</li>
<li class="controls active">{this.state.currentPage}</li>
<li class="controls">{this.state.maxPage}</li>
<li class="nexprev">
{currentPage < maxPage && (
<button onClick={this.nextPage}>
<span class="fa-forward" />
</button>
)}
</li>
</ul>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("Result"));

Replace the single quotes inside double quotes string with ' html entity, then replace single quotes with double quotes and lastly (optional) replace single quotes html entity with single quotes.
function stringToJSON(str) {
// convert single qoute inside double qoutes to html entity
const singleQuoteToEntity = str.replace(/"[^"]+"/g, function(m) {
return m.replace(/\'/g, ''');
});
const replaceSingleQuotes = singleQuoteToEntity.replace(/\'/g, '"');
return replaceSingleQuotes.replace(/'/g, '\'');
}
const jsonString = "{ 'id': { 'hotelId': '102835', 'provider': { 'provider_id': '23', 'dmnid': 3984 } }, 'hotelinfo': { 'name': 'Pera Rose Hotel', 'hotelsearch': { 'realname': 'Pera Rose Hotel', 'hotelid': 0.0, 'hotelimage': '', 'webserviceimagine': 'http://service.stage.Paximum.com/media/images/product/2/1/2/2/102835-fed561d75dec40ca4d83fd6fc9da9967-jpg/pera_rose_hotel.jpg', 'countryid': 1002247, 'ecountryname': 'Turkey', 'ecityname': 'Istanbul', 'cityid': 1177676, 'star': 4, 'desc': \"This hotel is located in the Istanbul's business, shopping and entertainment centre, around a 5-minute walk from the historical Galata Tower, the world's second oldest subway and some 8 minutes' walk away from Taksim Square. In Taksim itself, around 5 minutes' walk from the hotel, guests will find restaurants, bars, shops and clubs. The nearest underground station is Taksim-Meydan, a 10-minute walk away and guests will find the Hagia Sophia, the Topkapi Palace, the Grand Bazaar and the Egyptian Market all around a 15-minute ride away by public transport, as is Sirkeci Station. Istanbul Airport is around 15 km away.\", 'enable': '', 'delete': '' }, 'information': { 'viewname': '-' } } }";
console.log(JSON.parse(stringToJSON(jsonString)))
In your App component you do the following
class App extends React.Component {
....
stringToJSON = (str) => {
// convert single qoute inside double qoutes to html entity
const singleQuoteToEntity = str.replace(/"[^"]+"/g, function(m) {
return m.replace(/\'/g, ''');
});
const replaceSingleQuotes = singleQuoteToEntity.replace(/\'/g, '"');
return replaceSingleQuotes.replace(/'/g, '\'');
}
componentDidMount() {
fetch("/json.bc", {
method: "POST",
body: "cityid=[##cms.form.cityid##]"
})
.then(response => response.text())
.then(text => {
var Maindata = JSON.parse(this.stringToJSON(text));
this.setState(
state => ({
...state,
data: Maindata
}),
() => {
this.reorganiseLibrary();
}
);
})
.catch(error => console.error(error));
}
....

Related

this.setState isn't making changes in state

I am using functions that change a value in a nested object in the state :
an I am calling those functions in a button , they are executed when I click on that button , but one of those functions doesn't make changes to the state
This is the state :
state = {
data: {
attributesLength: this.props.product.attributes.length,
modalMessage: "",
isOpen: false,
},
};
and these are the functions :
addToCart = (id) => {
let data = { ...this.state.data };
if (Object.keys(this.state).length === 1) {
data.modalMessage = "Please, select product attributes";
this.setState({ data});
return;
}
if (
Object.keys(this.state).length - 1 ===
this.state.data.attributesLength
) {
const attributes = Object.entries(this.state).filter(
([key, value]) => key !== "data"
);
if (this.props.cartProducts.length === 0) {
this.props.addItem({
id: id,
quantity: 1,
attributes: Object.fromEntries(attributes),
});
data.modalMessage = "Added to cart !";
this.setState({ data });
return;
}
const product = this.props.cartProducts.filter((item) => item.id === id);
if (product.length === 0) {
this.props.addItem({
id: id,
quantity: 1,
attributes: Object.fromEntries(attributes),
});
data.modalMessage = "Added to cart !";
this.setState({ data });
return;
}
if (product.length !== 0) {
this.props.changeQuantity({ id: id, case: "increase" });
data.modalMessage = "Quantity increased !";
this.setState({ data });
return;
}
if (this.state.data.attributesLength === 0) {
this.props.addItem({
id: id,
quantity: 1,
attributes: Object.fromEntries(attributes),
});
data.modalMessage = "Added to cart !";
this.setState({ data });
return;
}
} else {
data.modalMessage = 'please, select "ALL" product attributes!';
this.setState({ data });
}
};
changeModalBoolean = () => {
let data = { ...this.state.data };
data.isOpen = !data.isOpen;
this.setState({ data });
};
and this is where I am calling functions :
<button
className={product.inStock ? null : "disabled"}
disabled={product.inStock ? false : true}
onClick={() => {
this.addToCart(product.id);
this.changeModalBoolean();
}}
>
{product.inStock ? "add to cart" : "out of stock"}
</button>
NOTE
changeModalBoolean function works and change state isOpen value,
this.addToCart(product.id);
this.changeModalBoolean();
This code run synchronously one after the other. In every function, you create a copy of previous state let data = { ...this.state.data };
so the this.changeModalBoolean(); just replace state which you set in this.addToCart(product.id); to fix this problem, use this.setState((state) => /*modify state*/)
changeModalBoolean = () => {
this.setState((state) => {
let data = { ...state.data };
data.isOpen = !data.isOpen;
return { data };
})
};
or modify the same object in both functions

scrollIntoView does not work on first load and after each refresh

I am seeing a very strange behavior in my chat. Once the chat is opened, the scroll moves only a tiny bit down only when there are images in the chat. When there is only text, it goes all the way down. Also, if I close the chat and open it again, the scroll goes all the way down regardless of the content. However, If I refresh the page, the scroll returns to its weird behavior. I am puzzled as to why this is happening. Here's my code:
Here's how the chat starts:
startChat () {
document.getElementById("myForm").style.display = "block";
const ref = firebase.firestore().collection('Chats').doc(this.state.uid).collection('Messages');
const query = ref.orderBy('timestamp', 'desc').limit(10)
this.unsubFromMessages = query.onSnapshot((snapshot) => {
if (snapshot.empty) {
console.log('No matching documents.');
firebase.firestore().collection('Chats').doc(this.state.uid).
set({
name: this.state.displayName,
uid: this.state.uid,
email: this.state.email
}).then(console.log("info saved"))
.catch((error) => {
console.log("Error saving info to document: ", error);
});
}
snapshot.docChanges().reverse().forEach((change) => {
if (change.type === 'removed') {
console.log(change.doc.data().content)
}
else if (change.type === 'added') {
this.setState(state => {
const messages = [...state.messages, {id: change.doc.id, body: change.doc.data()}]
return {
messages
}
})
setTimeout( this.scrollToBottom(), 2000)
}
else if (change.type === 'modified') {
const filteredMessages = this.state.messages.filter(message => message.body.allowed === "yes")
this.setState(state => {
const messages = [...filteredMessages, {id: change.doc.id, body: change.doc.data()}]
return {
messages
}
})
setTimeout( this.scrollToBottom(), 2000)
}
});
}, (error) => {console.log(error)});
}
Here's the scroll function:
scrollToBottom = () => {
this.myRef.current.scrollIntoView({ behavior: "smooth" });
}
Here's the JSX of the chat:
<div className="form-popup" id="myForm">
<form className="form-container" onSubmit={this.chatFormSubmit}>
<h1>Chat</h1>
<label htmlFor="msg"><b>Message</b></label>
<div className="chatArea" id='messages'>
{
this.state.messages.map((message, index) => {
return message.body.uid === this.state.uid && !message.body.imageUrl
?
<p className="message-sent" key={index}>{message.body.content}</p>
:
message.body.uid === this.state.uid && message.body.imageUrl
?
<img src={message.body.imageUrl} className="message-sent" key={index}></img>
:
<p className="message-received" key={index}>{message.body.content}</p>
})
}
<div style={{ float:"left", clear: "both" }}
ref={this.myRef}>
</div>
</div>
And if the functions for closing and submitting messages to the chat are of any use, here they are:
closeForm() {
document.getElementById("myForm").style.display = "none";
this.setState({messages: []})
this.unsubFromMessages();
}
chatFormSubmit(e) {
e.preventDefault();
this.setState({ writeError: null });
firebase.firestore()
.collection('Chats')
.doc(this.state.uid)
.collection('Messages')
.doc()
.set({
docId: this.state.docId,
content: this.chatArea.current.value,
allowed: "yes",
timestamp: new Date(),
uid: this.state.uid,
name: this.state.displayName,
email: this.state.email
}, { merge: true })
.catch((error) => {
this.setState({ writeError: error.message });
})
.then(this.chatArea.current.value = '')
}
Again, I figured it out myself. Instead of calling "this.scrollToBottom()" in setTimeout, I should have simply passed it like this setTimeout( this.scrollToBottom, 2000). That is why setTimeout was not working and the scroll stopped half way. Credit goes to Felix Kling's comment in ReactJS: setTimeout() not working?.

Chartjs populate data with Axios response

I am attempting to have the data on the chart populate based on the set of data the user selects i.e past 24-hours, past week, etc. I am saving the data and the labels in state. The labels update according to the selected time frame, but none of the data populates. I have console logged the data (this.state.data.datasets[0].data[0]) and it is the correct data.
Here is my code:
class ChartDemo extends Component {
state = {
target: 20,
timeFrame: "past-hours",
totalSales: [],
data: {
labels: [],
datasets: [
{
label: "",
backgroundColor: "",
// data results
data: []
}
]
},
chartIsLoaded: false,
}
getData = (start, end) => {
API.getData(start, end)
.then(res =>
this.setState(state => {
// if any sales have occured in selected time period
if (res.data[0] !== undefined) {
let total = res.data[0].profit.toFixed(2);
let totalString = total.toString();
const totalSales = state.totalSales.concat(totalString);
return {
totalSales
};
} else {
// set to zero if no sales
const noSale = "0.00";
const totalSales = state.totalSales.concat(noSale);
return {
totalSales
};
}
})
)
.catch(error => console.log( error));
}
UNSAFE_componentWillMount() {
this.setTimeFrame();
}
setTimeFrame() {
const day-one = 2019-08-01;
const day-two = 2019-08-02;
const timeFrame = this.state.timeFrame;
this.setState({ target: 20 });
if (timeFrame === "past-hours") {
this.getData(day-one, day-two);
if (this.state.totalSales.length < 8) {
this.setState({ target: 7, chartIsLoaded: true });
setTimeout(
function () {
this.setState(prevState => ({
data: {
...prevState.data,
labels: [
timeset-one,
timeset-two,
timeset-three,
timeset-four,
timeset-five,
timeset-six,
timeset-seven,
timeset-eight,
],
datasets: [{
...prevState.data.datasets,
label: "24-hour Profit in $",
backgroundColor: "rgb(1,41,95)",
data: [this.state.totalSales]
}]
}
}))
}.bind(this), 1000
)
}
}
}
I solved this by removed the [] around this.state.totalSales. I was essentially putting an array into another array.

One data from two get method

I have two separate axios get method which map response data to separate data object. Then I map the data in render. I want to put data from both axios in to one object to map only one object in render. How could i do that?
One of two get function
getData() {
axios
.get("http://localhost/GetAll?", {
params: { rok: this.state.rok, idUchwaly: "1" },
headers: { Authorization: "Bearer " + this.state.token }
})
.then(response =>
response.data.map(data2 => ({
IDA: `${data2.idZadania}`,
idWersji: `${data2.idWersji}`,
Dzial: `${data2.dzial}`
}))
)
.then(data2 => {
if (data2 == "") {
} else {
this.setState({ data2, isLoadingdane: true });
}
})
.catch(error => this.setState({ error }));
}
Then I map data into a table
{this.state.isLoadingdane ? (
data2.map(user2 => {
const { IDA, idWersji, DziaƂ } = user2;
return (
<tr id={IDA}>
<td>
<p>{idWersji}</p>
</td>
<td>
<p>{Dzial}</p>
</td>
</tr>
);
})
) : (
<tr>
<td colSpan="3">
<center>
<p>Brak</p>
</center>
</td>
</tr>
)}
I want one table in which i could put values from both get function
something like this: {value from getData}{value from getData2}
Instead of putting the response to the axios request in state directly, you could return the promise and wait for both of the requests to finish with Promise.all and then merge the objects in both arrays into one array.
Example
class App extends React.Component {
componentDidMount() {
Promise.all([this.getData(), this.getData2]).then(([data1, data2]) => {
this.setState({
data2: data1.map((item, index) => ({ ...item, ...data2[index] })),
isLoadingdane: true
});
});
}
getData = () => {
return axios
.get("http://localhost/GetAll?", {
params: { rok: this.state.rok, idUchwaly: "1" },
headers: { Authorization: "Bearer " + this.state.token }
})
.then(response =>
response.data.map(data2 => ({
IDA: `${data2.idZadania}`,
idWersji: `${data2.idWersji}`,
Dzial: `${data2.dzial}`
}))
);
};
getData2 = () => {
return axios.get(/* ... */);
// ...
};
render() {
// ...
}
}
Bartek, you need to map results like that:
const first = [{
x: 'x',
y: 'y',
}];
const second = [{
x: 'x',
z: 'z',
}];
const all = first.map(o => ({ ...o, ...second.find(f => f.x === o.x)}));
console.log(all);

ReactJS seems combine two state updates as one render, how to see separate rendering effects?

I am trying to come up a react exercise for the flip-match cards game: say 12 pairs of cards hide (face down) randomly in a 4x6 matrix, player click one-by-one to reveal the cards, when 2 cards clicked are match then the pair is found, other wise hide both again., gane over when all pairs are found.
let stored = Array(I * J).fill(null).map((e, i) => (i + 1) % (I * J));
/* and: randomize (I * J / 2) pairs position in stored */
class Board extends React.Component {
constructor() {
super();
this.state = {
cards: Array(I*J).fill(null),
nClicked: 0,
preClicked: null,
clicked: null,
};
}
handleClick(i) {
if (!this.state.cards[i]) {
this.setState((prevState) => {
const upCards = prevState.cards.slice();
upCards[i] = stored[i];
return {
cards: upCards,
nClicked: prevState.nClicked + 1,
preClicked: prevState.clicked,
clicked: i,
};
}, this.resetState);
}
}
resetState() {
const preClicked = this.state.preClicked;
const clicked = this.state.clicked;
const isEven = (this.state.nClicked-1) % 2;
const matched = (stored[preClicked] === stored[clicked]);
if (isEven && preClicked && clicked && matched) {
// this.forceUpdate(); /* no effects */
this.setState((prevState) => {
const upCards = prevState.cards.slice();
upCards[preClicked] = null;
upCards[clicked] = null;
return {
cards: upCards,
nClicked: prevState.nClicked,
preClicked: null,
clicked: null,
};
});
}
}
renderCard(i) {
return <Card key={i.toString()} value={this.state.cards[i]} onClick={() => this.handleClick(i)} />;
}
render() {
const status = 'Cards: '+ I + ' x ' + J +', # of clicked: ' + this.state.nClicked;
const cardArray = Array(I).fill(null).map(x => Array(J).fill(null));
return (
<div>
<div className="status">{status}</div>
{ cardArray.map((element_i, index_i) => (
<div key={'row'+index_i.toString()} className="board-row">
{ element_i.map((element_j, index_j) => this.renderCard(index_i*J+index_j))
}
</div>
))
}
</div>
);
}
}
Essentially, Board constructor initialize the state, and handleClick() calls setState() to update the state so it trigger the render of the clicked card's value; the callback function resetState() is that if the revealed two card did not match, then another setState() to hide both.
The problem is, the 2nd clicked card value did not show before it goes to hide. Is this due to React combine the 2 setState renderings in one, or is it rendering so fast that we can not see the first rendering effects before the card goes hide? How to solve this problem?
You're passing resetState as the callback to setState, so I would expect after the initial click your state will be reset.
You might want to simplify a bit and do something like this:
const CARDS = [
{ index: 0, name: 'Card One', matchId: 'match1' },
{ index: 1, name: 'Card Two', matchId: 'match2' },
{ index: 2, name: 'Card Three', matchId: 'match1', },
{ index: 3, name: 'Card Four', 'matchId': 'match2' },
];
class BoardSim extends React.Component {
constructor(props) {
super(props);
this.state = {
cardsInPlay: CARDS,
selectedCards: [],
checkMatch: false,
updateCards: false
};
...
}
...
componentDidUpdate(prevProps, prevState) {
if (!prevState.checkMatch && this.state.checkMatch) {
this.checkMatch();
}
if (!prevState.updateCards && this.state.updateCards) {
setTimeout(() => {
this.mounted && this.updateCards();
}, 1000);
}
}
handleCardClick(card) {
if (this.state.checkMatch) {
return;
}
if (this.state.selectedCards.length === 1) {
this.setState({ checkMatch: true });
}
this.setState({
selectedCards: this.state.selectedCards.concat([card])
});
}
checkMatch() {
if (this.selectedCardsMatch()) {
...
}
else {
...
}
setTimeout(() => {
this.mounted && this.setState({ updateCards: true });
}, 2000);
}
selectedCardsMatch() {
return this.state.selectedCards[0].matchId ===
this.state.selectedCards[1].matchId;
}
updateCards() {
let cardsInPlay = this.state.cardsInPlay;
let [ card1, card2 ] = this.state.selectedCards;
if (this.selectedCardsMatch()) {
cardsInPlay = cardsInPlay.filter((card) => {
return card.id !== card1.id && card.id !== card2.id;
});
}
this.setState({
selectedCards: [],
cardsInPlay,
updateCards: false,
checkMatch: false
});
}
render() {
return (
<div>
{this.renderCards()}
</div>
);
}
renderCards() {
return this.state.cardsInPlay.map((card) => {
return (
<div key={card.name} onClick={() => this.handleCardClick(card)}>
{card.name}
</div>
);
});
}
...
}
I've created a fiddle for this you can check out here: https://jsfiddle.net/andrewgrewell/69z2wepo/82425/

Resources