I'm trying to build onChange search feature. In suggestion list, my state like this:
results: {
page: 1,
results: [
{value: 'popularity.desc', label: 'Popularity Descending'},
{value: 'popularity.asc', label: 'Popularity Ascending'},
{value: 'new value', label: 'new label'},
{value: 'vote_average.desc', label: 'Rating Descending'}
]
}
When the user picks {value: 'new value', label: 'new label'} object.
How can I shift object to the first index of results array of state?
For example: After Object was picked. The state should be like this:
results: {
page: 1,
results: [
{value: 'new value', label: 'new label'},
{value: 'popularity.desc', label: 'Popularity Descending'},
{value: 'popularity.asc', label: 'Popularity Ascending'},
{value: 'vote_average.desc', label: 'Rating Descending'}
]
}
My idea is using spread operator and filter but I don't know how to implement that.
Select Item method:
onSuggestionSelected = (event, {suggestion, suggestionValue }) => {
console.log('Selected', suggestion); // {value: 'new value', label: 'new label'}
if (suggestion.media_type === 'movie') {
this.navigate('/search/movie', suggestionValue, suggestion);
} else if (suggestion.media_type === 'tv') {
this.navigate('/search/tv', suggestionValue, suggestion);
} else {
this.navigate('/search', suggestionValue, suggestion);
}
};
After selected, it will navigate:
navigate = (pathname, queryValue, resultValue) => {
// ResultValue is that Object that I want to shift first.
this.props.history.push({
pathname,
search: `?query=${queryValue}`,
state: {results: this.state.data}});
};
//filtered the remaining item
let remainValue = result.filter((obj, key) => obj.value != suggestion.value);
//merge here
let newResult = [suggestion, ...remainValue]; //it will contain reordered item
At first we need to find an index using findIndex, then filter current state to exclude the object containing selected data.
Last thing we need to do is to combine results into one array using spread operator ....
Example:
const currentState = [
{value: 'popularity.desc', label: 'Popularity Descending'},
{value: 'popularity.asc', label: 'Popularity Ascending'},
{value: 'new value', label: 'new label'},
{value: 'vote_average.desc', label: 'Rating Descending'}
];
const selectedValue = {value: 'new value', label: 'new label'};
const onSelectionChange = (selected) => {
const selectedIndex = currentState.findIndex(({ value }) => value === selected.value);
const selectedItem = currentState[selectedIndex];
const stateExcludedItem = currentState.filter(({ value }) => value !== selected.value);
const newState = [ selectedItem, ...stateExcludedItem ]
// this.setState
return newState;
}
const result = onSelectionChange(selectedValue);
console.log(result);
You could find the index of the result you want to put first in the array, and then put that first followed by what used to be before and after the result in the array.
Example
class App extends React.Component {
state = {
results: {
page: 1,
results: [
{ value: "popularity.desc", label: "Popularity Descending" },
{ value: "popularity.asc", label: "Popularity Ascending" },
{ value: "new value", label: "new label" },
{ value: "vote_average.desc", label: "Rating Descending" }
]
}
};
putResultFirst = result => {
this.setState(previousState => {
const { results } = previousState.results;
const resultIndex = results.indexOf(result);
return {
results: {
...results,
results: [
result,
...results.slice(0, resultIndex),
...results.slice(resultIndex + 1)
]
}
};
});
};
render() {
return (
<div>
{this.state.results.results.map(result => (
<div key={result.id} onClick={() => this.putResultFirst(result)}>
{result.value} {result.label}
</div>
))}
</div>
);
}
}
To move the selected item to the first position, you can use the Array.filter method to get the unselected items as an array, and build a new results array:
selectedItem = this.state.results.results[clickedIndex]
unselectedItems = this.state.results.results.filter((item, index) => clickedIndex !== index)
Because the "results" in state is an object. To update an object in the state, you should make an copy of the current results object to create a new object, so that the state will be updated.
results = {...this.state.results, results: [selectedItem, ...unselectedItems]}
You just want to move the selected item to the top of the results array, therefore you need just transfer the index of selected item to the event handler, which will call the setState method.
The example codes:
class App extends React.Component {
state = {
results: {
page: 1,
results: [
{value: 'popularity.desc', label: 'Popularity Descending'},
{value: 'popularity.asc', label: 'Popularity Ascending'},
{value: 'new value', label: 'new label'},
{value: 'vote_average.desc', label: 'Rating Descending'}
]
}
};
handelClick = (clickedIndex) => {
if(clickedIndex === 0) {
return;
}
let selectedItem = this.state.results.results[clickedIndex],
unselectedItems = this.state.results.results.filter((item, index) => clickedIndex !== index),
results = {...this.state.results, results: [selectedItem, ...unselectedItems]};
this.setState({results})
};
render() {
return <ul>
{this.state.results.results.map((item, index) =>
<li key={item.label} onClick={this.handelClick.bind('', index)}>{item.label}</li>)
}
</ul>
}
}
const renderFilterList = () => {
ReactDOM.render(<App />, document.getElementById('react-app'));
};
<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="react-app"></div>
Related
My brain froze with this advanced filtering. This task has exceeded my basic knowledge of filter, map etc.
Here I have an array with nested objects with array:
const DATA = [
{
title: 'Spongebob',
data: [
{ id: 1, name: 'Mr Crabs' },
{ id: 2, name: 'Sandy' }
]
},
{
title: 'Dragon Balls Z',
data: [
{ id: 1, name: 'GoKu' },
{ id: 2, name: 'Zamasu' }
]
}
];
You may have seen this sort of style if you've worked with React Native (RN). This question is not for RN. I need to perform a filter on the name property in the nested array and when I get a match, I must return the format as the DATA variable.
const handleFiltering = (value) => {
const _value = value.toLowerCase();
const results = DATA.map(o => {
return o.data.filter(o => o.name.toLowerCase().indexOf(_value) != -1)
});
console.log(results);
};
My limited knowledge of deep filtering returns the basic filtering for the data array but need to retain the structure for DATA. The expected results I'd expect:
// I'm now querying for "ZAMASU"
const handleFiltering = (value='ZAMA') => {
const _value = value.toLowerCase();
const results = DATA.map(o => {
return o.data.filter(o => o.name.toLowerCase().indexOf(_value) != -1)
});
// console.log(results) should now be
// [
// {
// title: 'Dragon Balls Z',
// data: [
// { id: 2, name: 'Zamasu' }
// ]
// }
// ];
};
What comes to mind is the use of {...DATA, something-here } but my brain has frozen as I need to get back the title property. How to achieve this, please?
Another solution would be first use filter to find only objects containing the name in data passed through the argument, subsequently mapping data.
Here is your adjusted filter method
const handleFiltering = (value) => {
const _value = value.toLowerCase();
const results = DATA.filter((obj) =>
obj.data.some((character) => character.name.toLowerCase() === _value)
).map((obj) => ({
title: obj.title,
data: obj.data.filter(
(character) => character.name.toLowerCase() === _value
),
}));
console.log(results);
};
You can use reduce method of array. First find out the object inside data array and then add that to accumulator array as new entry by preserving the original structure.
const DATA = [
{
title: 'Spongebob',
data: [
{ id: 1, name: 'Mr Crabs', where: 'tv' },
{ id: 2, name: 'Sandy' }
]
},
{
title: 'Dragon Balls Z',
data: [
{ id: 1, name: 'GoKu' },
{ id: 2, name: 'Zamasu' }
]
}
];
let handleFiltering = (value='tv') => {
return DATA.reduce((acc,d) => {
let obj = d.data.find(a => a.name?.toLowerCase().includes(value.toLowerCase())
|| a.where?.toLowerCase().includes(value.toLowerCase()));
obj ? acc.push({...d, data:[obj]}) : null;
return acc;
}, []);
}
let result = handleFiltering();
console.log(result);
I have the following object and array that have matching Id's but because of a drag and drop feature a simple map without a find doesn't work, because they are not matching in order. I will explain.
I have this object (which is my updated values after a form submit)
data variable in my code below
{"we98Yt8JLyxkcJorf":"Kanye","yG6JiFaBBut6XgHpj":"2813348004","RcpDcF72K2aN9fub4":"kanye#usa.gov","ShBTee9MNxvRNG3XN":"123 White House Avenue","GSCZMMwscKh3ZaKix":"473 estate drive","Jmr7HoYmfXJWHMwmr":"2813348004"}
and I have this array with objects in them that correspond to the ids
cards variable in my code below
[{"inputType":"shortText","uniId":"we98Yt8JLyxkcJorf","label":"First Name:","value":"Kanye"},{"inputType":"phoneNumber","uniId":"yG6JiFaBBut6XgHpj","label":"Cell Phone Number","value":"2813348004"},{"inputType":"email","uniId":"RcpDcF72K2aN9fub4","label":"Work Email","value":"kanye#usa.gov"},{"inputType":"multipleChoice","uniId":"GSCZMMwscKh3ZaKix","label":"Preferred Method of Contact","value":"2813348004","options":[{"uniId":"poXf65zi8NDko5Je4","label":"Email"},{"uniId":"FmvYT4cbY8JotAaqA","label":"Cell Phone"}]},{"inputType":"Address","uniId":"ShBTee9MNxvRNG3XN","label":"Home Address","value":"123 White House Avenue"},{"inputType":"dropDown","uniId":"Jmr7HoYmfXJWHMwmr","label":"How did you find us?","value":"2813348004","options":[{"uniId":"EQj9MXdnaBjmCkAKZ","label":"Google"},{"uniId":"EbbhhqSd4K6sAsQ3T","label":"Referral"}]}]
Now I want to update the array with the new data, there is a uniqueId field on both of them that match, but I keep failing at trying to match them and update the array so I can submit that to the db in the format it needs ot be which is it's current state.
This is what I have tried thus far it returns undefined
const test = Object.keys(data);
console.log(test);
let testArray = [];
test.map((item) => {
let testArraySingle = cards.find((x) => {
x.uniId === item;
}).value;
testArray.push(testArraySingle);
});
console.log(testArray);
I have tried this also but it returns incorrect because it only matches it loops how many times of the array.length but doesnt keep checking, if that makes sense
const dataArray = Object.entries(data);
let newArray = [];
cards.map((card, i) => {
var checkId = dataArray[i][0];
let matchesArray = [];
if (card.uniId === checkId) {
const new_obj = { ...cards[i], value: dataArray[i][1] };
newArray.push(new_obj);
}
// }
});
Hope somebody can help with this. Thanks!
You could use combination of Array.prototype.filter() and Array.prototype.map() method to get your result. Traverse data keys using map and filter uniId by filter method and again use map to get the all the values.
const data = {
we98Yt8JLyxkcJorf: 'Kanye',
yG6JiFaBBut6XgHpj: '2813348004',
RcpDcF72K2aN9fub4: 'kanye#usa.gov',
ShBTee9MNxvRNG3XN: '123 White House Avenue',
GSCZMMwscKh3ZaKix: '473 estate drive',
Jmr7HoYmfXJWHMwmr: '2813348004',
};
const cards = [
{
inputType: 'shortText',
uniId: 'we98Yt8JLyxkcJorf',
label: 'First Name:',
value: 'Kanye',
},
{
inputType: 'phoneNumber',
uniId: 'yG6JiFaBBut6XgHpj',
label: 'Cell Phone Number',
value: '2813348004',
},
{
inputType: 'email',
uniId: 'RcpDcF72K2aN9fub4',
label: 'Work Email',
value: 'kanye#usa.gov',
},
{
inputType: 'multipleChoice',
uniId: 'GSCZMMwscKh3ZaKix',
label: 'Preferred Method of Contact',
value: '2813348004',
options: [
{ uniId: 'poXf65zi8NDko5Je4', label: 'Email' },
{ uniId: 'FmvYT4cbY8JotAaqA', label: 'Cell Phone' },
],
},
{
inputType: 'Address',
uniId: 'ShBTee9MNxvRNG3XN',
label: 'Home Address',
value: '123 White House Avenue',
},
{
inputType: 'dropDown',
uniId: 'Jmr7HoYmfXJWHMwmr',
label: 'How did you find us?',
value: '2813348004',
options: [
{ uniId: 'EQj9MXdnaBjmCkAKZ', label: 'Google' },
{ uniId: 'EbbhhqSd4K6sAsQ3T', label: 'Referral' },
],
},
];
const ret = Object.keys(data)
.map((x) => {
const tmp = cards
.filter((y) => y.uniId === x)
.map((z) => {
const obj = { ...z };
obj.value = data[x];
return obj;
});
return tmp;
})
.flat();
console.log(ret);
I have this object below. I was wondering how I can select a specific item and update a property. For example. Item 1 I want to add a task in the array.
item: {
'item-1': {
id: 'item-1',
title: 'To do',
task: ['task-1', 'task-2', 'task-3', 'task-4']
},
'item-2': {
id: 'item-2',
title: 'In progress',
task: []
},
I currently have
const getItem = {...state.items['item-1']}
const newTaskList = [...getItem.task, newTask.id]
const newState = {
...state,
items: {
...state.items,
//How do I spread new array correctly in item 1?
//...state.items['item-1'].task
}
};
You need to use the object key i.e item-1 and clone the properties for it and add the new list for the task key. In short you need to clone at each level of the object before overriding the key that you wish to update
const newState = {
...state,
items: {
...state.items,
'item-1': {
...state.items['item-1'],
task: newTaskList
}
}
};
Assuming the starting point:
let state = {
items: {
'item-1': {
id: 'item-1',
title: 'To do',
task: ['task-1', 'task-2', 'task-3', 'task-4']
},
'item-2': {
id: 'item-2',
title: 'In progress',
task: []
},
}
};
If you want to add a task to item-1's task array without modifying things in place (which is important in React state), you have to copy state, items, item-1, and item-1's task:
let newState = {
...state,
items: {
...state.items,
'item-1': {
...state.items['item-1'],
task: [...state.items['item-1'].task, newTask]
}
}
};
Live Example:
let state = {
items: {
'item-1': {
id: 'item-1',
title: 'To do',
task: ['task-1', 'task-2', 'task-3', 'task-4']
},
'item-2': {
id: 'item-2',
title: 'In progress',
task: []
},
}
};
let newTask = "task-4";
let newState = {
...state,
items: {
...state.items,
'item-1': {
...state.items['item-1'],
task: [...state.items['item-1'].task, newTask]
}
}
};
console.log(newState);
In lodadash you can get and set nested object from an object, here is my own implementation of it:
//helper to get prop from object
const get = (object, path, defaultValue) => {
const recur = (object, path) => {
if (object === undefined) {
return defaultValue;
}
if (path.length === 0) {
return object;
}
return recur(object[path[0]], path.slice(1));
};
return recur(object, path);
};
//helper to set nested prop in object
const set = (
state,
statePath,
modifier
) => {
const recur = (result, path) => {
const key = path[0];
if (path.length === 0) {
return modifier(get(state, statePath));
}
return Array.isArray(result)
? result.map((item, index) =>
index === Number(key)
? recur(item, path.slice(1))
: item
)
: {
...result,
[key]: recur(result[key], path.slice(1)),
};
};
const newState = recur(state, statePath);
return get(state, statePath) === get(newState, statePath)
? state
: newState;
};
let state = {
items: {
'item-1': {
id: 'item-1',
title: 'To do',
task: ['task-1', 'task-2', 'task-3', 'task-4'],
},
'item-2': {
id: 'item-2',
title: 'In progress',
task: [],
},
},
};
console.log(
set(
state,
['items','item-1','task'],
(tasks)=>tasks.concat('new task')
)
);
You can put the get and set in a library and it would make setting deeply nested values easier on the eyes of future readers of your code.
I have a few questions in regards to what would be the best approach to do the following:
Call two different API:
axios.get(contents);
axios.get(favorites);
Response will Look like this:
contents: [
{
id: 1,
value: someValue
},
{
id: 2,
value: someValue
}
];
favorites: [
{
id: 1,
contentId: 2
}
];
What would be the best approach to loop through each favorite and add an element to the contens array such as isFavorite: true when the contentId matches the id. It should look as follows:
contents: [
{
id: 1,
value: someValue
{,
{
id: 2,
value: someValue
isFavorite: true
{
];
What would be the best place to do this and is there any ES6 syntax that can easily do this? I currently have the two actions separate, one that gets the contents and one that gets the favorites, I could possibly merge those or combine them at the reducer.
Any suggestions?
You can use a Set to collect all contentId values from favorites and then iterate through your contents array. This has better time complexity than using some on an array because calling .has() on a Set is O(1):
let contents = [{
id: 1,
value: 'someValue1'
},
{
id: 2,
value: 'someValue2'
},
{
id: 3,
value: 'someValue'
}
];
let favorites = [{
id: 1,
contentId: 2
},
{
id: 2,
contentId: 3
}
];
let favoriteContents = new Set(favorites.map(f => f.contentId));
contents.forEach(c => {
if (favoriteContents.has(c.id)) c.isFavorite = true;
});
console.log(contents);
const newContents = contents.map((content) => {
const foundFavorite = favorites.find((favorite) => favorite.contentId === content.id)
if (foundFavorite) {
return {
...content,
isFavorite: true,
}
}
return content
});
You firstly need to have the promises from your API calls, and when both of them are complete you can then carry out the merge of the results.
const contentsApi = () => Promise.resolve([
{
id: 1,
value: 'foo'
},
{
id: 2,
value: 'bar'
}
])
const favouritesApi = () => Promise.resolve([
{
id: 1,
contentId: 2
}
])
let contents;
let favourites;
const contentsApiCall = contentsApi().then(res => {
contents = res;
})
const favouritesApiCall = favouritesApi().then(res => {
favourites = res;
})
Promise.all([contentsApiCall, favouritesApiCall]).then(() => {
const merged = contents.map(content => {
if(favourites.some(favourite => favourite.contentId === content.id)){
return {
...content,
isFavourite: true
}
} else {
return content;
}
})
console.log(merged)
// do whatever you need to do with your result, either return it if you want to chain promises, or set it in a variable, etc.
})
While selecting one option in the Select box at that time rest of the options are become multiple values. How can i prevent this duplicate values ?
import Select from 'react-select';
const dataOptions = [];
class App extends React.Component {
constructor(props) {
super(props);
this.data = [];
this.getData();
}
getData = () => { api.request({url: `/getdata`}).then(res => res.map(el => this.data[el.id] = el.name)) }
addData = () => {
const { selectedId } = this.state;
var datas = this.data;
console.log(datas);
datas.map((name, index) => {
if (!dataOptions.includes(name)) {
console.log('b4 push:', dataOptions)
dataOptions.push({ value: index, label: name });
console.log('aftr push:', dataOptions)
}
});
return (
<Select options={dataOptions}
isMulti
/>
);
}
}
Something is wrong happening in this syntax:
datas.map((name, index) => {
if (!dataOptions.includes(name)) {
dataOptions.push({ value: index, label: name });
}
});
Console Results
[ "data-1", "data-2", "data-3"]
b4 push: [
{value: 1, label: "data-1"}
{value: 2, label: "data-2"}
{value: 3, label: "data-3"}
]
aftr push: [
{value: 1, label: "data-1"}
{value: 2, label: "data-2"}
{value: 3, label: "data-3"}
]
P.S: Here in aftr push i have already selected first option from drop down; so in result if should not be displayed in the array values.
Thanks in advance...!
The destructuring syntax should be like below
datas.map(({name, index}) => {
if (!dataOptions.includes(name)) {
dataOptions.push({ value: index, label: name });
}
});
Moreover you don't need external array to push the data inside map function as the function by default returns an array, you can do simply like below
let expected_data=datas.map(({name, index}) => {
if (!dataOptions.includes(name)) {
return { value: index, label: name };// return a value
}
});
The expected_data will contain the data you need after operation
See the snippet-
let data = [{
"name": 1,
"index": 2
}, {
"name": 11,
"index": 21
}]
console.log(data.map(({
index,
name
}) => {
return {
value: index,
label: name
}
}))
You better use Array.some() for what you are looking
datas.map((name,index) => { // here index is the iterator
if(!dataOptions.some(({value,label})=>label==name ))
{
dataOptions.push({ value: index, label: name });
}
});