How to map data from an array of objects for an options selector product in react? - reactjs

I'm simply trying to map data from an API with the intention of creating options to select for a product.
I am able to do it as long as the API data for the specific concept (for example bluetooth, color, weight) has a simple array format as:
"bluetooth":["4.0","A2DP","LE"]
Then using the following code renders the options to choose on the selector
<OptionsCss onChange={(e) => setSize(e.target.value)}>
{product.bluetooth?.map((s) => (
<FieldCss key={s}>{s}</FieldCss>
))}
However with a concept named storage I'm unable to get the values on the product options to choose. Here the array structure is as follows :
"storages":[{"code":2000,"name":"16 GB"},{"code":2001,"name":"32 GB"}]
Here I just need the values as for example 16GB and 32GB. So mapping the api following the previous way gives me empty choice options.
Then the question would be, How can I get access to the name values from the example mentioned
previously ? and what should I change from the previous mapping example?

When you map through the array, you get one object at a time then you can access the property you want from the object using storage.name.
obj.storages.map(storage => storage.name)
So in the case of your component you'd have
<OptionsCss onChange={(e) => setSize(e.target.value)}>
{product.storages?.map((s) => (
<FieldCss key={s.name}>{s.name}</FieldCss>
))}
/>

Related

Svelte make {:then} value of {#await} statement reactive

I have to make an api call and then display the data (an array). I want to sort the array, but the displayed data is not updated. I suppose this is because my data is not a reactive variable? How can I make my each loop rerun when data changes?
My code (simplified):
{#await promise} // api call
<p>Loading</p>
{:then data}
<button on:click={() => data.sort()}>sort data</button> // does not work
{#each data as student}
<p>{student.firstname}</p>
{/each}
{/await}
data is a reactive variable. The presence of a promise is unrelated to the issue: this occurs even if data is a normal variable. The issue is that Svelte doesn't properly pick up that a change occurred to data when you click the button. You can force Svelte to pick up the changes by explictly assigning to data.
The other issue is that you can't sort objects without providing a custom sort function to sort based on a key of the object. .sort((a, b) => a.firstname.localeCompare(b.firstname)) sorts based on the firstname property of the objects.
Putting that together gives us:
<button on:click={() => data = data.sort((a, b) => a.firstname.localeCompare(b.firstname))}>sort data</button>

React Redux Advanced Search in front-end

I am trying a new web app like a movie, book,song search app
I have to do the search completely with react so my problem starts when I have to filter my array of data with filters.
Filter params are dynamic since user can search from Title or use advanced search forms for producer , year etc but those fields are not required.
So my params look like : query={title:"Larry"} or {title:"Larry", year:"2010",...}
Can I search in array dynamically with Lodash ? If yes , do I need to use forEach(query)and then for each value, key combination filter array?
Is there any other way ?
JS has Array.filter method used like this:
myNumbers.filter(entry => entry.title === query.title && entry.year === query.year)
You get your elements that matches in your condition..

React keeps children from previous render

I have problem rendering page based on array of templates stored in redux. I'm unable to provide the actual code as it is too complex to reproduce in sandbox, but I will try with simplified structure.
I have pinterest-style grid based on flex-grow and js calculations for each item based on its dimensions so I need to have all of this inside one container.
I have 3 groups of items in same div:
blank_item + templates.map + tailItems.map
<div className="grid">
{shouldRenderBlankItem && <BlankItem />}
{templates.map((template) => (
<Template
key={template.uuid}
template={template}
/>
))}
{shouldRenderTail &&
tailItems.map(item, i) => (
<TailItem
key={i}
item={item}
/>
))}
</div>
The problem is - sometimes after render I have EXTRA children left from previous render and React puts them in front of other elements within div.grid so the result I have will look like:
3-4 of EXTRA <Template/> + blank_item + templates.map + tailItems.map
As a key for Template I use template.uuid that is coming from backend and I know they're unique and react also doesn't show any warnings for duplicated keys - so I know I'm not getting any duplicated items from API what I thought might be an issue.
Redux is fine - I see correct number of templates in it e.g. 50, grid and react dev tools shows same 50 templates coming as a prop when I inspect parent component, but the actual DOM has e.g. 53 elements in the same moment.
How can I debug issue like this? I suppose something is wrong during reconciliation but I don't know where exactly to start debugging this.
I have React/ReactDOM 16.13.1
Ok so in my case the problem was broken backend api which might return same template twice - so uuid's that I use for keys were unique for each template but they are not really unique in terms of the page and DOM elements.
I faced this only in production build that is why I didn't have duplicated key warning (on dev build I have another DB with much less templates so I was unable to reproduce this).
So anyone facing similar issue: go check your keys are really unique within page, because what happens after new templates load comes:
React wants to remove old item
It searches corresponding DOM element for each key and finds item with key="XXX" and removes it from DOM
Their duplicated items stays in DOM as corresponding key was processed and React doesn't care about this particular key anymore
We load new templates and they get appended to the parent container
Voila! We have old items before newly loaded
What I did here - my key now is:
templates.map((template, i) => <Template key={template.uuid + i} />
So in this case I am safe even if backend returns duplicated items.
Of course we will fix backend as well. Thanks for reading and helping!
Just quick example how to search for items with same id in data:
var duplicates = new Set();
data.map(t => t.id).forEach((id, i, arr) => {if(arr.filter(item => item == id).length > 1){
duplicates.add(id);
}});
console.log("DUPLICATED IDs:", duplicates)

How to debug this search filter in react

I made a simple search filter and it is working properly in the sense that it will return back the number of results back with it being filtered.
here is a bit of the code to help understand what I mean
{this.state.subject.filter(search(this.state.search))
.map((subject,index) => {
return <Subject key={index}
subjectId={this.state.subject[index].id}
subjectName={this.state.subject[index].name} />
})}
As you can see to access the subjectI need to access the array index inside in order to get the result. If i search for something and say returns 2 it will return the first two index instead of the specific ones.
Is there a way to get around it?
You don't need to access elements from the original array, you can use the category of the corresponding iteration inside your map call:
{this.state.categories.filter(searchingFor(this.state.search))
.map((category) => {
return <Category key={category.id}
categoryId={category.id}
categoryName={category.name} />
})}

AngularJS drop down (ng- options) not binding - string to object (initial selection)

I am having a problem binding data retrieved from the server to a drop down list. The main issue I think is the fact that the comparison is done on differing object types.
For example:
1. The object returned from the server contains a currency code string. we want this to be bound to an item in the dropdown list
"baseCurrencyCode":"GBP"
The view model returns the list of currencies.. These are returned as a list of currency objects with different properties
{"currencies":[{"id":1,"rateId":0,"abbreviation":"AFN","description":"Afghani","rate":0.0,"rateDescription":null,"languageCode":"en-gb","isDefault":true,"fullDescription":"AFN - Afghani - ","shortDescription":"AFN - Afghani"}}
etc.
Currently, I have got this working by writing a function to go through every property for every item in the list, find the correct property we wish to compare to - do the comparison and then return the initial selection.
When calling my save method I then need to manually bind the currency abbreviation to the object I wish to return to the server.
Surely there must be a better way to do this?
Some of my code for reference..
<select ng-model="selectedCurrency" ng-options="currency.shortDescription for currency in viewModel.currencies"></select>
// Call to my custom method..List, PropertyName, value to compare
$scope.selectedCurrency = InitialiseDropdown($scope.viewModel.currencies, "abbreviation", $scope.updatedObject.baseCurrencyCode);
// Code executed when saving - to bind the currency to the updated object
$scope.updatedObject.baseCurrencyCode = $scope.selectedCurrency.abbreviation;
Any help is appreciated!
EDIT
Sorry if I wasn't clear enough.. To summarise..
The main problem here is binding to the drop down and initial selection.
The object we are updating contains a parameter (string) of the currency abbreviation.
The list we select from is a list of currency objects. As these are two differing object types I have been unable to plug in angulars 2 way binding and have written some code to do this on initial retrieval and when saving.
The cleanest way to fix this would be to return a currency object in our retrieval instead of a simple string of the abbreviation, but this is not an option.
Is there a better way of enabling 2 way binding on different object types ?
Thanks again
It is not exactly clear what the problem is, but you can save yourself some work by binding the <select> to the currently selected currency object (so you don't have to look it up later).
select + ngOptions allow you to bind to one value while displaying another, with the following syntax:
<select ng-model="selectedCurrency"
ng-options="currency as currency.shortDescription
for currency in viewModel.currencies">
</select>
In the above example, $scope.selectedCurrency will be bound to the whole currency object, but currency.shortDescription will be displayed in the dropdown.
See, also, this short demo.
UPDATE:
In case you don't need to bind to the whole currency object, but just bind updatedObject's baseCurrencyCode property to the abbreviation of the selected (in dropdown) currency, you can do it like this:
<!-- In the VIEW -->
<select ng-model="updatedObject.baseCurrencyCode"
ng-options="c.abbreviation as c.shortDescription
for c in currencies">
</select>
// In the CONTROLLER
$scope.currencies = [...];
$scope.updatedObject = {
...
baseCurrencyCode: <baseCurrencyCodeFromServer>
};
See, also, that short demo.
I have had the same problem, ng-model and ng-option being from 2 different sources. My ng-model is bound to a value in a json object representing a chosen filename and my ng-option is a list of possible values taken from a csv file.
In the controller I am reading a directory via a Nodejs route, and creating a json array of filenames like this
var allCsvFiles = [{"name":"file1.csv"},{"name","file2.csv},etc..]
The current csv file, i.e. the selected one is stored in another json array
[{"date":"01-06-2017","csvfile":"file1.csv","colour":"red"},{...}, etc].
I was using the following code for the dropdown:
<select type="text" ng-model="file.csvfile"
ng-options="opt.name for opt in allCsvFiles track by opt.name"></select>
Which caused the current selection to be blank and if I selected an item from the dropdown it put [object],[object] as the current selection. If I stepped through the code I found that it seemed to be selecting {"name","file1.csv"} as the option and couldn't display it, this seemed odd as my ng-options selection looks like it should just return the value of "name" not the array entry. I tried many different ways to make this work but eventually I found that if I made the list of possible selections a plain javascript array:
var allCsvFiles = ["file1.csv","file2.csv", "file3,csv]
and changed the select to:
<select type="text" ng-model="file.csvfile" ng-options="opt for opt in allCsvFiles"></select>
then the dropdown selection worked as expected.
I may have missed some other obvious solution here, but as the array of json objects is one dimensional anyway it doesn't seem to be an issue.
It looks like the OPs question has been answered, I just thought I'd add this as it solved it for me.

Resources