reactJS and dynamic render and variable population - reactjs

I want to dynamically generate output in my render() but I have run into a very strange situation. I have a process whereby I retrieve some data from a database using a fetch(). Once I get the data back, I determine the number of data records and then execute a for loop to populate an array with the returned data. I have a console.log() before the for loop to display the contents of my data receiving array and another console.log() as I populate the receiving array. For some reason, as I populate a specific occurrence of the array, all occurrences of the array appear to be changing. This is the entire code that I have:
import React from 'react';
import '../styles/app.css';
class testpage extends React.Component {
constructor(props) {
super(props);
this.state = {
productArray: []
};
}
componentWillMount() {
var tempData = '';
var numberOfRecords = 0;
let url = "http://wmjwwebapi-dev.us-west-2.elasticbeanstalk.com/api/getdata";
const options = { method: 'GET' };
fetch(url, options)
.then(function(response) {
return response.json();
})
.then(function(myJson) {
if (myJson == undefined)
{
console.log("fetch failed");
}
else
{
//inspect the data that the WebAPI returned
var return_code = myJson[0].return_code;
if (return_code == "Default Return code"){
tempData = '';
numberOfRecords = -2;
} else {
tempData = JSON.parse(myJson[0].return_string)
numberOfRecords = tempData.barcode.length;
var counter = 0;
var productArray = new Array;
var product = {
barcode: '',
name: '',
description: '',
image: '',
price: ''
}
for (counter=0;counter<numberOfRecords;counter++) {
product.barcode = tempData.barcode[counter];
product.name = tempData.name[counter];
productArray[counter] = product;
}
}
}
});
}
render() {
<div>
<div>
{this.state.productArray[0].barcode}
</div>
</div>
}
}
export default testpage;
Here is an image of what I see in the console.log() when the loop counter = 0:
Notice the barcode value of 5000159459228? This value gets pushed into productArray[0].barcode. And this is what I expected.
Here is an image of what I see in the console.log() when the loop counter = 1:
Here, the barcode of the record read is 5000159459230. This value should go into productArray1.barcode, which the image shows it does. However, the image also shows that the value of productArray[0].barcode has changed from 5000159459228 (the value of the first record) to 5000159459230 (the value of the second record).
Here is an image from the 3rd time through the loop:
Again, a new record with barcode = 5000159459231. It appears that this value gets pushed into productArray2.barcode but productArray[0].barcode and productArray1.barcode have now been changed.
How is that possible?
Eventually, the goal is to dynamically render the data that is retrieved.
I thank you in advance for any assistance.

Console output can generally be relied on for primitives but not for objects. Console implementation is specific to the environment where a script is executed (browser).
It is object reference that is being outputted, not object snapshot. When an object is changed, its representation in console can change as well.
Notice that in Chrome console the array was initially logged as [] empty array, while next log entry is [...] non-empty array.
To output object snapshot that doesn't change retroactively, use console.log(JSON.stringify(array)) or other serialized primitive value.

I would recommend to use chrome devtools for the debugging and add break point where array is populating.
Also you can add debugger in any where in code It will prompt devtools then.
I second #estus about changes reflect upon object updation where ever you have used Console.

After the chat and learned how the data shape is, with the help of Code Review, this is how we can do this:
const json = [{
return_string: {
"barcode": ["5000159459228", "5000159459229", "5000159459230"],
"scan_date": ["20180825173416", "20180825173416", "20180825173416"],
"name": ["Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz x 6)", "Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz x 6)", "Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz x 6)"],
"description": ["Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz x 6)", "Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz x 6)", "Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz x 6)"],
"image": ["http://thumbs2.ebaystatic.com/m/mv0sDuMCXy5TgjQFYC0CJAQ/140.jpg", "http://thumbs2.ebaystatic.com/m/mv0sDuMCXy5TgjQFYC0CJAQ/140.jpg", "http://thumbs2.ebaystatic.com/m/mv0sDuMCXy5TgjQFYC0CJAQ/140.jpg"],
"price": ["1", "2", "3"]
}
}];
const fakeRequest = () => new Promise( resolve =>
setTimeout( () => resolve( json ) )
);
class App extends React.Component {
state = {
data: "",
}
componentDidMount() {
fakeRequest()
.then( res => {
this.setState({
data: res[0].return_string,
})
})
}
renderData = () => {
const { data } = this.state;
const values = Object.values(data);
const keys = Object.keys(data);
const transposed = values[0].map((col, i) =>
values.map(row => row[i]));
const items = transposed.map(itemArr =>
keys.reduce((acc, key, i) => (
{ ...acc, [key]: itemArr[i] }), {})
);
return items.map( item => (
<div style={{ border: "1px solid black", marginBottom: "3px" }}>
<p>Barcode: {item.barcode}</p>
<p>Scan date: {item.scan_date}</p>
<p>Name: {item.name}</p>
<p>Description: {item.description}</p>
<p>Image: {item.image}</p>
<p>Price: {item.price}</p>
</div>
) )
}
render() {
return <div>{this.state.data && this.renderData()}</div>;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
The hard part is converting the data into an array of objects properly. Since, this shape of data is a little bit strange to me :) An object containing properties of arrays. And those array elements are matched by order. Phew :)

Related

Count the duplicates in a string array using React JS

Following is a code I implemented to create a bar chart using chart js in React app. Here it creates a bar chart with all the data in an array. But, I want to change this code only to give the output in the x-axis - destination, y-axis - no. of occurrence of this destination since it has many repeated destinations.
I searched methods to this but I couldn't get a correct solution.
Can anyone help me to do this?
const dataArrayY4 = [];
res.data.map(item => {
dataArrayY4.push(item.time)
})
const dataArrayX4 = []
res.data.map(item => {
dataArrayX4.push(item.destination)
})
this.setState({
data4: dataArrayY4,
labels4: dataArrayX4,
});
This could be done as follows:
const res = {
data: [
{ time: 1, destination: 'A'},
{ time: 3, destination: 'A'},
{ time: 2, destination: 'B'}
]
};
let tmp4 = [];
res.data.map((o, i) => {
const existing = tmp4.find(e => e.destination == o.destination);
if (existing) {
existing.time += o.time;
} else {
tmp4.push({time: o.time, destination: o.destination});
}
})
this.setState({
data4: tmp.map(o => o.time);
labels4: tmp.map(o => o.destination);
});
Above code could further be optimized by using Array.reduce() instead of Array.map().
I would make the code more efficient. Instead of dataArrayY4 being an array, I would make it an object that has a key of value and the number of occurrence of each value. This way, you can count all the number of occurrences of the all items in res.data
const dataArrayY4 = {};
res.data.map(item => {
dataArrayY4[item.destination] = (dataArrayY4[item.destination] || 0) + 1
})
const dataArrayX4 = []
res.data.forEach(item => {
dataArrayX4.push(item.destination)
})
this.setState({
data4: dataArrayY4,
labels4: dataArrayX4,
});
Then if you want to look for the occurrence of a particular value you
use this eg. Sri Lanka
this.state.data4['Sri Lanka']

Add total price to react shopping cart

I'm new to react and followed this tutorial to built a shopping cart. The code is pretty simple and straight forward but I would like for it to display the total price of products. How would I implement that within this code?
Here's the code. I've later separated these into 3 files, "Products", "Cart" and "Productspage" but displayed it here all together so it would simpler to see it.
import React, {useState} from 'react'
import './ProductsPage.css'
const PAGE_PRODUCTS = 'products';
const PAGE_CART = 'cart';
function ProductsPage() {
    const [cart, setCart] = useState ([]);
    const [page, setPage] = useState (PAGE_PRODUCTS);
    const [products] = useState ([
        {
            name: 'Breakfast box ',
            cost:'9.99$',
            image: 'https://images.unsplash.com/photo-1578863950596-a74dfe8267b5?ixlib=rb-1.2.1&auto=format&fit=crop&w=1573&q=80',
        },
        {
            name: 'Breakfast box ',
            cost:'8.99$',
            image: 'https://images.unsplash.com/photo-1557686652-6731ba12410f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=634&q=80',
        },
    ])
    const addToCart = (product) => {
        setCart ([...cart, {...product}])
    }
    const removeFromCart = (productToRemove) => {
        setCart(cart.filter(product => product !== productToRemove))
    }
    const navigateTo = (nextPage) => {
        setPage(nextPage);
    };
    const renderProducts = () => (
        <>
         <h1>Products</h1>
            <div className="products">
            {products.map((product , index) => (
            <div className="product" key={index}>
               <h3>{product.name}</h3>
               <h4>{product.cost}</h4>
               <img src={product.image} alt={product.name}/>
               <button onClick={() => addToCart(product)}>
                   Add to Cart
                </button>
            </div>
                
                ))}
         </div>
         </>
    );
        const renderCart = () => (
            <>
            <h1>Cart</h1>
               <div className="products">
               {cart.map((product , index) => (
               <div className="product" key={index}>
                  <h3>{product.name}</h3>
                  <h4>{product.cost}</h4>
                  <img src={product.image} alt={product.name}/>
                  <button onClick={() => removeFromCart(product)}>
                      Remove
                   </button>
               </div>
                   
                   ))}
            </div>
            </>
        )
    return (
            <div className="productspage">
            <header>
                <button onClick={()=> navigateTo(PAGE_CART)}>
                    Go to Cart ({cart.length})
                </button>
                <button onClick={()=> navigateTo(PAGE_PRODUCTS)}>
                    View Products
                </button>
            </header>
            {page === PAGE_PRODUCTS && renderProducts()}
            {page === PAGE_CART && renderCart()}
            </div>
    );
};
export default ProductsPage;
you could simply loop through array created from object, and then convert string cost to numeric (by replacing $ to nothing and cast cart to numeric with + before that) and then sum up all of them like here:
Object.keys(cart).reduce(
(prevVal, currentVal) =>
prevVal + +cart[currentVal].cost.replace("$", ""),
0
)
Check it out i have added js code with comments to understand easily:
`const [total,setTotal]=useState(0)` //make a new state variable
const cart =[
{
name: 'Breakfast box ',
cost:'9.99$',
image: 'https://images.unsplash.com/photo-1578863950596-a74dfe8267b5?ixlib=rb-1.2.1&auto=format&fit=crop&w=1573&q=80',
},
{
name: 'Breakfast box ',
cost:'8.99$',
image: 'https://images.unsplash.com/photo-1557686652-6731ba12410f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=634&q=80',
},
]
//modified addToCart
const addToCart = (product) => {
const products = [...cart, product]
console.log(products)
const totalPrice = products.reduce((acc, curr)=>{ //calculate total
let cur =curr.cost.match(/\d./g).join('') //parse string to integer(cost)
return acc + Number(cur);
}, 0)
console.log("total:", totalPrice);
// setCart (products);
//setTotal(totalPrice);
}
//end addToCart
const newProduct = {
name: 'Breakfast box ',
cost:'7.99$',
image: 'https://images.unsplash.com/photo-1557686652-6731ba12410fixlib=rb1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=63',
}
addToCart(newProduct)
Call this function with your products list wherever you want the total cost to appear.
const getTotalCost = (productList) => {
return productList.reduce((totalCost, { cost: itemCost }) => totalCost + parseFloat(itemCost), 0);
};
This is just a regular JavaScript function that iterates the list of products and sums the cost key and returns it.
It's not accessing any props or state so can sit outside of your component body.

Is it possible to get / return Highcharts Axis values?

I would like to be able to use the values printed on Highcharts x and y axes when the chart renders in another part of my app, so for example an array of number from 0 to n.
I am using highcharts-react-official - I can't find any documentation on methods that return the values as they are printed exactly on the screen.
Is this possible?
Update
With this code I can return an array of numbers that represent the tick values on the y axis:
const options: Highcharts.Options = {
yAxis: {
labels: {
formatter(): string {
console.log('YAXIS', Object.keys(this.axis.ticks)
}
}
}
}
Although the array includes a negative number which I cannot see in the chart axis itself.
The same trick does not work for the x axis.
Is there a cleaner approach do getting the correct result?
Update 2
Following the solution below I finally got what I was looking for:
This does not work as you would expect:
const res = this.yAxis
.map((axis) => axis.ticks)
.map((axisTicks) => Highcharts.objectEach(axisTicks, (tick) => tick.label.textStr));
console.log(res); // undefined;
However this does:
const yAxisVals: string[][] = [];
const axisTicks = this.yAxis.map((axis) => axis.ticks);
axisTicks.forEach((item, idx) => {
yAxisVals[idx] = [];
Highcharts.objectEach(item, (tick) => yAxisVals[idx].push(tick.label.textStr));
});
console.log(yAxisVals);
You can use render event and get the values by Axis.tickPositions property:
chart: {
events: {
render: function() {
var xAxisTickPositions = this.xAxis[0].tickPositions,
yAxisTickPositions = this.yAxis[0].tickPositions;
console.log(xAxisTickPositions, yAxisTickPositions);
}
}
}
Live demo: http://jsfiddle.net/BlackLabel/6m4e8x0y/4960/
API Reference:
https://api.highcharts.com/highmaps/chart.events.render
https://api.highcharts.com/class-reference/Highcharts.Axis#tickPositions

Nested callback function (React native JS )

I have a couple of different functions that helps set different states of the component and i wish to run the functions in order of one another. I know that there are multiple posts on these but they seem to be mostly catered for running one function after the other but i have more than two functions that i need to execute in order
Desired order
1) Set state of starting and final destination
2) Run this.getDirections() (This function sets the state of arrOfPolylines which i desire to reset through resetRouteSelectionStatus())
3) Run resetRouteSelectionStatus()
4) After running these functions i wish to have an empty this.state.arrOfPolylines
Actual results
There is no error in the code but it isnt entering the resetRouteSelectionStatus() as none of the console log are printed. Can someone please guide me on the right path?
<Button
onPress={() => { //on button press set final destination and starting location
{
(this.state.tempDestination.longitude != null && this.state.tempStarting.longitude != null) &&
this.setState({
finalDestination: {
latitude: this.state.tempDestination.latitude,
longitude: this.state.tempDestination.longitude,
},
startingLocation: {
latitude: this.state.tempStarting.latitude,
longitude: this.state.tempStarting.longitude,
}
}, () => {
this.getDirections((this.state.startingLocation.latitude + "," + this.state.startingLocation.longitude), (this.state.finalDestination.latitude + "," + this.state.finalDestination.longitude),
() => {this.resetRouteSelectionStatus()});
}
);
}
}}
title="Determine Directions"
color="#00B0FF"
resetRouteSelectionStatus() {
console.log('entered reset route selection status function')
this.setState({arrOfPolyline: null }, () => {console.log("i should be null nd come first" + this.state.arrOfPolyline)}) ;
this.setState({ selectChallengeStatus: null });
this.setState({ userRouteSelectionStatus: null }); //when user click on button set seleection status to 0 so route options will be displayed again after generation new route
//this.setState({arrOfDirectionDetails: []}); // clear past direction details when user select generate route with new starting/ending location
// clear past poly lines when user has selected new routes
//console.log("everything has been reset");
}
async getDirections(startLoc, destinationLoc) {
let resp = await fetch(`https://maps.googleapis.com/maps/api/directions/json?origin=${startLoc}&destination=${destinationLoc}&key="KEY"&mode=driving&alternatives=true`)
let respJson = await resp.json();
let routeDetails = respJson.routes;
let tempPolyLineArray = [];
let tempDirArrayRoute = [];
//console.log(startLoc);
//console.log(destinationLoc);
for (i = 0; i < routeDetails.length; i++) // respJson.routes = number of alternative routes available
{
let tempDirArray = []; // at every new route, initalize a temp direction array
let points = Polyline.decode(respJson.routes[i].overview_polyline.points);
let coords = points.map((point, index) => {
return {
latitude: point[0],
longitude: point[1]
}
})
tempPolyLineArray.push(coords);
for (k = 0; k < routeDetails[i].legs[0].steps.length; k++) // for each route go to the correct route, enter legs ( always 0), get the number of instruction for this route
{
//console.log (routeDetails[i].legs[0].steps[k])
tempDirArray.push(routeDetails[i].legs[0].steps[k]) // push all instructions into temp direction array
//this.state.arrOfDirectionDetails.push(routeDetails[i].legs[0].steps[k]); // for each instruction save it to array
}
tempDirArrayRoute.push(tempDirArray); // at the end of each route, push all instructions stored in temp array as an array into state
}
this.setState({ arrOfDirectionDetails: tempDirArrayRoute });
this.setState({ arrOfPolyline: tempPolyLineArray });
//creating my html tags
let data = [];
let temptitle = "Route ";
for (let j = 0; j < routeDetails.length; j++) {
data.push(
<View key={j}>
<Button
title={temptitle + j}
onPress={() => this.updateUser(j)}
/>
</View>
)
}
this.setState({ routebox: data })
}
So a few things to note here:
I would extract the inline function for the onButtonPress function into an async function
Something along the lines of:
const onButtonPressHandler = async () => {
... put your code here
}
Then you'll modify your onButtonPress to something like:
<Button
onPress={this.onButtonPressHandler}
... rest of your props here
/>
You can then properly use async / await in the buttonPress handler.
The setState function is not a synchronous function. If you rely on the results right away you'll be disappointed.
Each time you call setState you could trigger a rerender.
I would instead merge all of your setState calls into a single at the end.
The getDirections function does not include the callback, while it should be:
async getDirections(startLoc, destinationLoc, callback) {
.....
this.setState({ routebox: data },callback());
}
or but not sure if it would be in order:
async () => {
await this.getDirections((this.state.startingLocation.latitude + "," + this.state.startingLocation.longitude), (this.state.finalDestination.latitude + "," + this.state.finalDestination.longitude) );
this.resetRouteSelectionStatus();
}
You might need edit the resetRouteSelectionStatus to be:
resetRouteSelectionStatus = async ()=>{

How to write array with deleted image id?

When i remove images from news i catch id, and id come to along.
How to write in array all this lonlies id ?
How to create streamIds array with streamId ?
this.state = {
mainImage: null,
mainImageUrl: "",
crop: {
aspect: 2 / 1
},
pixelCrop: null,
cropped: false,
loaded: false,
streamIds: []
};
removeImage(imageKey, streamId) {
const {singleNews} = this.props;
let streamIds = this.state.streamIds;
console.log(streamId);
singleNews.secondaryImages.splice(imageKey, 1);
if (!singleNews.secondaryImages.length) {
singleNews.secondaryImages = null;
delete singleNews.secondaryImages;
this.props.updateSingleNews(null, singleNews);
} else {
streamIds.push(streamId);
singleNews.secondaryImages.map(image => {
const index = singleNews.secondaryImages.indexOf(image);
if (index > -1) {
singleNews.secondaryImages.slice(index, 1);
FilesApi.getDocument(image.streamId).then(resp => {
singleNews.secondaryImages[index] = new File([resp], image.name, {lastModified: Date.now()});
});
}
});
this.props.updateSingleNews('streamIds', streamIds);
}
}
this is your method
If not in this func where i need to place
if you want to keep the array of ids in the same component, use
let streamIds = [];
at the top of your react component and do
removeImage (imageKey, streamId) {
console.log(streamId);
streamIds.push(streamId); // insert the item to array
}
in your removeImage method
if you want to keep the removed ids in the application state, then the concept is the same, but it need to be done on the state management tool you are using (like redux, mobx etc)

Resources