How can I remove duplicate values ​from my json in React - json

I am developing an application with React and I have a problem. I have made a fetch from a json and the values are repeated. I want only each value to appear once.
I hope that it only returns once each of the elements of "Padre": "CRM", "Permisos" and "Telefonia". The same for "Hijo".
I've tried changing ".concat" to ".reduce" because I've read that it can be done like this, but it does not work. I have seen examples but none of them works on "this.state" so I do not know how to do it.
Can you help me with the correct way to do it?
This is a part of my json file
[
{
"Id":"114",
"Padre":"CRM",
"Hijo":"Argumentarios"
},
{
"Id":"115",
"Padre":"CRM",
"Hijo":"Argumentarios"
},
{
"Id":"116",
"Padre":"CRM",
"Hijo":"Argumentarios"
},
{
"Id":"44",
"Padre":"Permisos",
"Hijo":"root"
},
{
"Id":"45",
"Padre":"Permisos",
"Hijo":"root"
},
{
"Id":"46",
"Padre":"Permisos",
"Hijo":"root"
},
{
"Id":"47",
"Padre":"Permisos",
"Hijo":"root"
},
{
"Id":"50",
"Padre":"Telefonia",
"Hijo":"Audio"
},
{
"Id":"51",
"Padre":"Telefonia",
"Hijo":"Audio"
},
{
"Id":"52",
"Padre":"Telefonia",
"Hijo":"Configuracion"
},
{
"Id":"70",
"Padre":"Telefonia",
"Hijo":"Rutas"
}
]
For confidential reasons I can not show the actual file.
In my next code I change the fetch for a fake json url
I Edit my code with #Avanthika and #blaz code. Currently the code works but shows only the first value it finds of "Father" and "Son". I need to show only once each of the different values ​​of "Father" and "Son". Example result:
Father:
CRM
PERMISOS
Son:
ARGUMENTARIOS
ROOT
Currenly, with my new code i see only: CRM (Padre) and Argumentarios (Son). The rest of the elements are not shown.
import React, { Component } from 'react';
class Nav extends Component{
constructor(props){
super(props)
this.state = {
menuSubmenu:[]
}
}
componentWillMount(){
fetch('http://FAKE.php')
.then(response => response.json())
.then(menuSubmenu =>{
menuSubmenu.forEach(datos => {
let data = {
menu:datos.Padre,
submenu:datos.Hijo,
id:datos.Id
}
//console.log( data )
//console.log (data.menu);
this.setState({
menuSubmenu:this.state.menuSubmenu.concat([data])
})
})
})
}
render() {
const array = [...this.state.menuSubmenu];
const distinctValues = array.filter(
({ Padre, Hijo }, index) => {
return array.findIndex(item => item.Padre === Padre && item.Hijo === Hijo) === index;
});
//console.log(this.state.menuSubmenu);
//console.log(distinctValues);
if (distinctValues.length > 0) {
return(
<div>
{distinctValues.map(datos => <Navegacion key={datos.id} menu={datos.menu} submenu={datos.submenu} />)}
</div>
);
}
return (<p>Cargando usuarios...</p>);
}
}
class Navegacion extends Component{
render(){
return (
<ul className="list">
<li className="list__item">{this.props.menu}
<ul className="list">
<li className="list__item">
{this.props.submenu}
</li>
</ul>
</li>
</ul>
)
}
}
export default Nav;
This image is the result of my json.My json result
I hope you can help me with the "reduce" function.
¡Thanks a lot!

Hi you can use lodash and function uniqBy https://lodash.com/docs#uniqBy
_.uniqBy(menuSubmenu, function (e) {
return e.Padre;
});

You can use new Set to unique your array list by Padre and Hijo.
const array = [{"Id":"114","Padre":"CRM","Hijo":"Argumentarios"},
{"Id":"115","Padre":"CRM","Hijo":"Argumentarios"},
{"Id":"116","Padre":"CRM","Hijo":"Argumentarios"},
{"Id":"44","Padre":"Permisos","Hijo":"root"},
{"Id":"45","Padre":"Permisos","Hijo":"root"},
{"Id":"46","Padre":"Permisos","Hijo":"root"},
{"Id":"47","Padre":"Permisos","Hijo":"root"},
{"Id":"50","Padre":"Telefonia","Hijo":"Audio"},
{"Id":"51","Padre":"Telefonia","Hijo":"Audio"},
{"Id":"52","Padre":"Telefonia","Hijo":"Configuracion"},
{"Id":"70","Padre":"Telefonia","Hijo":"Rutas"}];
const distinctValues = Array.from(new Set(array.map(elem => `${elem.Padre}-${elem.Hijo}`))).map(distinctVal => {
const [ Padre, Hijo ] = distinctVal.split("-");
return ({
Id: array.find(elem => elem.Padre === Padre && elem.Hijo === Hijo).Id,
Padre,
Hijo
});
});
console.log(distinctValues);
Or you can go for lodash as mentioned to extract uniq values. You have to use uniqBy.
const array = [{"Id":"114","Padre":"CRM","Hijo":"Argumentarios"},
{"Id":"115","Padre":"CRM","Hijo":"Argumentarios"},
{"Id":"116","Padre":"CRM","Hijo":"Argumentarios"},
{"Id":"44","Padre":"Permisos","Hijo":"root"},
{"Id":"45","Padre":"Permisos","Hijo":"root"},
{"Id":"46","Padre":"Permisos","Hijo":"root"},
{"Id":"47","Padre":"Permisos","Hijo":"root"},
{"Id":"50","Padre":"Telefonia","Hijo":"Audio"},
{"Id":"51","Padre":"Telefonia","Hijo":"Audio"},
{"Id":"52","Padre":"Telefonia","Hijo":"Configuracion"},
{"Id":"70","Padre":"Telefonia","Hijo":"Rutas"}];
const distintValues = _.uniqBy(array, elem => [elem.Padre, elem.Padre].join());
console.log(distintValues);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
You need to fix the solution this way:
render() {
const array = [...this.state.menuSubmenu];
// Put the solution you like here.
// const distinctValues = the solution you like
if (distinctValues.length > 0) {
return(
<div>
{distinctValues.map(datos => <Navegacion key={datos.id} menu={datos.menu} submenu={datos.submenu} />)}
</div>
);
}
return (<p>Cargando usuarios...</p>);
}

Original solution from Avanthika doesn't work because in your data, some item might possibly have Padre value containing dash character ("-"). Use a character that will never appear in Padre value as separator instead and you will be good.
My solution:
Use Array.prototype.filter
const distinctValues = this.state.menuSubmenu.filter(
({ Padre, Hijo }, index) => {
return this.state.menuSubmenu.findIndex(item => item.Padre === Padre && item.Hijo === Hijo) === index;
});
With Array.prototype.reduce it will look more bulky:
const distinctValues = this.state.menuSubmenu.reduce(
(array, item) => {
if (array.findIndex(i => i.Padre === item.Padre && i.Hijo === item.Hijo) === -1) {
array.push(item);
}
}, []);

Related

React Beautiful DnD, multiple columns inside single droppable

I am trying to have a grid column layout, (2 columns) inside a single droppable container. The project is for an online menu where you can create a menu item, which goes into a droppable container, then you can drag that onto the menu that will be displayed to the user. So there is currently two columns. However the style of the menu demands two columns. Currently I am assigning different classNames to the mapped columns so I can make one of them grid but its pretty messy. Maybe there is a way I can hardcode the droppable instead of map them and run the map on the lists themselves inside each of the hardcoded droppables? Sorry if this is confusing, it sure is for me.
'results' is API data that is initially mapped into savedItems array where newly created menu items will go. Later on menuItems array will pull from the database as well. Right now just trying to have better styling control over the different droppables.
you can see where im assigning different classNames to the droppable during the mapping and its really not a reliable option.
//drag and drop states
const [state, setState] = useState({
menuItems: {
title: "menuItems",
items: []
},
savedItems: {
title: "savedItems",
items: results
}
})
useEffect(() => {
setState({ ...state, savedItems: { ...state.savedItems, items: results } })
}, [results])
// console.log("state", state)
console.log("dummy data", dummyArry)
// updating title graphql mutation
const [elementId, setElementId] = useState(" ");
const updateTitle = async () => {
//api data
const data = await fetch(`http://localhost:8081/graphql`, {
method: 'POST',
body: JSON.stringify({
query: `
mutation {
updateMenu(menuInput: {_id: ${JSON.stringify(elementId)},title: ${JSON.stringify(inputValue)}}){
title
}
}
`
}),
headers: {
'Content-Type': 'application/json'
}
})
//convert api data to json
const json = await data.json();
}
//drag end function
const handleDragEnd = (data) => {
console.log("from", data.source)
console.log("to", data.destination)
if (!data.destination) {
// console.log("not dropped in droppable")
return
}
if (data.destination.index === data.source.index && data.destination.droppableId === data.source.droppableId) {
// console.log("dropped in same place")
return
}
//create copy of item before removing from state
const itemCopy = { ...state[data.source.droppableId].items[data.source.index] }
setState(prev => {
prev = { ...prev }
//remove from previous items array
prev[data.source.droppableId].items.splice(data.source.index, 1)
//adding new item to array
prev[data.destination.droppableId].items.splice(data.destination.index, 0, itemCopy)
return prev
})
}
const columnClass = [
"menuItems-column",
"savedItems-column"
]
let num = 0
return (
<>
<div className='app'>
{results && <DragDropContext onDragEnd={handleDragEnd}>
{_.map(state, (data, key) => {
return (
<div key={key} className='column'>
<h3>{data.title}</h3>
<Droppable droppableId={key}>
{(provided, snapshot) => {
return (
<div
ref={provided.innerRef}
{...provided.droppableProps}
className={columnClass[num]}
// className="droppable-col"
><span className='class-switch'>{num++}</span>
{data.items.map((el, index) => {
return (
<Draggable key={el._id} index={index} draggableId={el._id}>
{(provided) => {
return (
<div className='element-container'
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<div contentEditable="true">
{el.title}
</div>
</div>
)
}}
</Draggable>
)
})}
{provided.placeholder}
</div>
)
}}
</Droppable>
</div>
)
})}
</DragDropContext>}
</div>
</>
)
}

Property does not exist on type 'never' on JSON array

I'm just trying to fetch some JSON data from a url. The JSON data is formatted like so (reduced to two entries for simplicity):
[
{
"id": 1
"name": "Brett",
"gender": "male"
},
{
"id": 2
"name": "Sandra",
"gender": "female"
}
]
I can print profiles using console.log(profiles) and see all the entries in the console, but when i try to access the .name field i get the error
Property 'name' does not exist on type 'never'.
Here is the code for the app:
const URL = 'someurl'
function App() {
const [curId, setId] = useState(0);
//const [curProfile, setCurProfile] = useState(undefined);
const [profiles, setProfiles] = useState([])
useEffect(() => {
fetch(URL)
.then((response) => {
if (response.ok) {
return response.json();
} else {
throw new Error("Something went wrong!");
}
})
.then(
(response) => {
setProfiles(response);
setId(1);
//setCurProfile(profiles[curId - 1]);
})
.catch((error) => {
console.log(error)
})
}, []);
return (
<div className="App">
<p>
{profiles[curId].name}
</p>
</div>
);
}
export default App;
Also as a side question, I'm having some problems storing the current profile in the curProfile variable. Could someone point me in the right direction for that? Thanks!
The initial state of profiles is empty array and curId is 0, so profiles[curId] should be undefined thus profiles[curId].name would be error as initial rendering.
You should always check if profiles is empty or not.
return (
<div className="App">
{profiles.length > 0 &&
<p>
{profiles[curId].name}
</p>
}
</div>
)
You've got to type your state, otherwise Typescript won't know what to expect. You also need to type the response.
Something like:
type Profile = {
id: number,
name: string,
gender: string
}
const [profiles, setProfiles] = useState <Profile[]> ([]);
(...)
setProfiles(response as Profile[]);

<Connect /> re-renders onSubscriptionMsg, but not with new data

I have a really simple component using Connect from aws-amplify-react
const EmployeeExpensesTable = () => {
const [user, setUser] = useState(null)
useEffect(() => { Auth.currentAuthenticatedUser().then(user => setUser(user)) }, [])
console.log(user)
if(!user) { return null }
return (
<Connect
query={graphqlOperation(queries.listTransactions, { owner: user.username })}
subscription={graphqlOperation(subscriptions.onCreateTransaction, { owner: user.username })}
onSubscriptionMsg={(prev, { onCreateTransaction }) => {
console.log('oncreatetransaction:', onCreateTransaction)
console.log('prev', prev)
return onCreateTransaction
}}
>
{
({ data: { listTransactions }, loading, errors }) => {
console.log('transactions', listTransactions)
if (errors.length) return (<h3>Error</h3>)
if (loading || !listTransactions) return (<p>loading...</p>)
return (<core.components.Table data={listTransactions.items} />)
}
}
</Connect>
)
}
export default EmployeeExpensesTable
I have an external ETL process that adds transactions to the DynamoDB table. The subscription is firing and my oncreatetransaction message is being displayed with the new data, and the Connect component re-renders, but it renders using it's original data, not the new data. What am I doing wrong?
Turns out that onSubscriptionMsg is like the reducer for the Connect component's state. You are given the previous value and the new value to the function and you need to return the new state.
onSubscriptionMsg={(prev, data) => ({
listTransactions: {
items: [...prev.listTransactions.items.slice(-9), data.onCreateTransaction ]
}
})
The above works great for me. The .slice(-9) is because I only care about the last 10 rows that were added.

state district json binding react

I want to display display list of districts from the json, receiving the following error
'TypeError: suggestion.districts.slice(...).toLowerCase is not a function'
json file.
How can I get the list of districts details, so that I can perform autocomplete using downshift?
any help appreciated.
json format
{
"states":[
{
"state":"Andhra Pradesh",
"districts":[
"Anantapur",
"Chittoor",
"East Godavari",
]
},
{
"state":"Arunachal Pradesh",
"districts":[
"Tawang",
"West Kameng",
"East Kameng",
]
},
}
component
import React, { Component } from 'react'
import statedist from "./StateDistrict.json";
const suggestions = statedist.states;
/*.... */
function getSuggestions(value, { showEmpty = false } = {}) {
// const StatesSelected=props.StatesSelected;
const inputValue = deburr(value.trim()).toLowerCase();
const inputLength = inputValue.length;
let count = 0;
//console.log(StatesSelected)
return inputLength === 0 && !showEmpty
? []
: suggestions.filter(suggestion => {
const keep =
count < 5 &&
suggestion.districts.slice(0, inputLength).toLowerCase() === inputValue;
if (keep) {
count += 1;
}
return keep;
});
}
function renderSuggestion(suggestionProps) {
const {
suggestion,
index,
itemProps,
highlightedIndex,
selectedItem
} = suggestionProps;
const isHighlighted = highlightedIndex === index;
const isSelected = (selectedItem || "").indexOf(suggestion.districts) > -1;
return (
<MenuItem
{...itemProps}
key={suggestion.districts[0]}
selected={isHighlighted}
component="div"
style={{
fontWeight: isSelected ? 500 : 400
}}
>
{suggestion.districts[0]} -- how can I get all the values instead of one here
</MenuItem>
);
}
class autoCompleteState extends Component {
constructor(props) {
super(props);
this.state = {
SelectedState:'',
}
// this.showProfile = this.showProfile.bind(this)
}
setSelectedDistrict = (newState) => {
this.setState({ SelectedState: newState });
console.log(newState)
this.props.onDistrictSelected(newState);
}
render() {
const { classes, } = this.props;
console.log(this.state.SelectedState)
const StatesSelected=this.props.StateList;
return (
<div>
<DownshiftMultiple
classes={classes}
setSelectedDistrict={this.setSelectedDistrict}
StatesSelected={StatesSelected}
/>
</div>
)
}
}
export default withStyles(Styles)(autoCompleteState);
I want the district details to come as suggestion like state in the below image
Currently, you are doing this:
suggestion.districts.slice(0, inputLength).toLowerCase() === inputValue;
This is throwing an error because .slice is copying inputLength items from your districts array and then trying to call .toLowerCase() on that array.
If I understand correctly, you are trying to filter your districts according to the inputValue. One way of doing this would be to use reduce on the districts array like this:
suggestion.districts.reduce((acc,curr)=>curr.substring(0,inputLength)===inputValue?[...acc,curr.substring(0,inputLength)]:acc, [])
If you only want the first 5 then you can slice the result of this:
suggestion.districts.reduce((acc,curr,index)=>index<5&&curr.substring(0,inputLength)===inputValue?[...acc,curr.substring(0,inputLength)]:acc, [])

Render partial JSON object

I'm having some issue on rendering some JSON data into a React Table (which comes from the library react-table). Here is an exemple of a json I'm working with :
const translateditems = [
{
id: 1,
colonne1: [
{
language: 'en',
content: 'Column n°1'
},
{
language: 'fr',
content: 'Colonne n°1'
}
],
colonne2: [
{
language: 'en',
content: 'Column n°2'
},
{
language: 'fr',
content: 'Colonne n°2'
}
],
dateCreated: '2018 - 02 - 01 30: 00: 00.000'
}
]
For a chosen language, I need to only show one object of the translated texts of each column if needed. (In this example, the date does not need to be translated in any particular language)
Here is how I render my react-table :
constructor () {
super()
this.state = {
data: translateditems
}
}
render () {
const { data } = this.state
const headers = []
// I use all the name fields in the first json object as headers
for (var key in Object.keys(data[0])) {
headers.push(Object({
'content': Object.keys(data[0])[key]
}))
}
return (
<div>
<ReactTable
data={data}
columns={headers.map(header => {
return ({
Header: header.content,
accessor: header.content
})
})
}
defaultPageSize={10}
className='-striped -highlight'
/>
<br />
</div>
)
}
Do you have any clue on how to do this ?
UPDATE
Here's what I did to solve my problem :
return (
<div>
<ReactTable
data={data}
columns={headers.map(header => {
return ({
Header: header.content,
accessor: header.content,
// I added a function to apply on my Cell
Cell: row => (
<div>{this.renderTranslatedItem(data, header.content, row.index)}</div>
)
})
})
}
defaultPageSize={10}
className='-striped -highlight'
/>
<br />
</div>
)
And here's the function I made (assuming lang to be the language I want):
renderTranslatedItem (data, column, rowIndex) {
// I check if the value is an array -> if it's not, then I return null
if (Object.prototype.toString.call(data[rowIndex][column]) !== '[object Array]') {
return null;
}
for (var i in data[rowIndex][column]) {
if (data[rowIndex][column][i].language === lang) {
return data[rowIndex][column][i].content
}
}
}
for (var key in Object.keys(data[0])) {
const headers = []
// I use all the name fields in the first json object as headers
headers.push(Object({
'content': Object.keys(data[0])[key]
}))
}
In my example, I consider lang is passed through your props.
You have to replace it by:
const { lang } = this.props
const headers = Object.keys(data[0]).reduce(
(acc, key)=> {
return key!=='id' && key!=='dateCreated' && data[0][key][lang]
? { ...acc, [key] : data[0][key][lang] }
: { ...acc, [key]:null }
},{})
//console.log(headers) will output this:
{
colonne1:'colonne1',
colonne2:'colonne2'
}