Update: scroll to see my solution, can it be improved?
So I have this issue, I am building a word translator thats translates english to 'doggo', I have built this in vanilla JS but would like to do it React.
My object comes from firebase like this
dictionary = [
0: {
name: "paws",
paws: ["stumps", "toes beans"]
}
1: {
name: "fur",
fur: ["floof"]
}
2: {
name: "what"
what: ["wut"]
}
]
I then convert it to this format for easier access:
dictionary = {
what : ["wut"],
paws : ["stumps", "toe beans"],
fur : ["floof"]
}
Then, I have two text-area inputs one of which takes input and I would like the other one to output the corresponding translation. Currently I am just logging it to the console.
This works fine to output the array of the corresponding word, next I have another variable which I call 'levelOfDerp' which is basically a number between 0 - 2 (set to 0 by default) which I can throw on the end of the console.log() as follows to correspond to the word within the array that gets output.
dictionary.map(item => {
console.log(item[evt.target.value][levelOfDerp]);
});
When I do this I get a "TypeError: Cannot read property '0' of undefined". I am trying to figure out how to get past this error and perform the translation in real-time as the user types.
Here is the code from the vanilla js which performs the translation on a click event and everything at once. Not what I am trying to achieve here but I added it for clarity.
function convertText(event) {
event.preventDefault();
let text = inputForm.value.toLowerCase().trim();
let array = text.split(/,?\s+/);
array.forEach(word => {
if (dictionary[word] === undefined) {
outputForm.innerHTML += `${word} `;
noTranslationArr.push(word);
} else {
let output = dictionary[word][levelOfDerp];
if (output === undefined) {
output = dictionary[word][1];
if (output === undefined) {
output = dictionary[word][0];
}
}
outputForm.innerHTML += `${output} `;
hashtagArr.push(output);
}
});
addData(noTranslationArr);
}
Also here is a link to the translator in vanilla js to get a better idea of the project https://darrencarlin.github.io/DoggoSpk/
Solution, but could be better..
I found a solution but I just feel this code is going against the reason to use react in the first place.. My main concern is that I am declaring variables to store strings inside of an array within the function (on every keystroke) which I haven't really done in React, I feel this is going against best practice?
translate = evt => {
// Converting the firebase object
const dict = this.state.dictionary;
let dictCopy = Object.assign(
{},
...dict.map(item => ({ [item["name"]]: item }))
);
let text = evt.target.value.toLowerCase().trim();
let textArr = text.split(/,?\s+/);
let translation = "";
textArr.forEach(word => {
if (dictCopy[word] === undefined) {
translation += `${word} `;
} else {
translation += dictCopy[word][word][this.state.derpLvl];
}
});
this.setState({ translation });
};
levelOfDerp is not defined, try to use 'levelOfDerp' as string with quotes.
let output = dictionary[word]['levelOfDerp' ];
The problem happens because setState() is asynchronous, so by the time it's executed your evt.target.value reference might not be there anymore. The solution is, as you stated, to store that reference into a variable.
Maybe consider writing another function that handles the object conversion and store it in a variable, because as is, you're doing the conversion everytime the user inputs something.
Related
I know filter questions are covered extensivly on SO - but I'm struggling to implement my idea:
I want to filter through my panels & return an array filteredPanelTabItems for any that include the layouthint: "tab-view-item" and then the remainder of the array (without "tab-view-item") to another const so I can use it elsewhere, can anyone guide where I'm going wrong?
The screenshot above shows what's happening in the console when I log:
panel.panelLinks.links
const hasTabItemViewHint() => {
//check for string
}
const filteredPanelTabItems = panel.panelLinks.links.filter<Node>(
(panelLink) => panelLink.(call hasTabItemViewHint function?)
);
Consider something like this.
var filteredPanelTabItems = $.map(panel.panelLinks.links, function(l, i) {
if (l.LinkModal.layouthint._collections.indexOf("tab-view-item") >= 0) {
return l;
}
});
See more: https://api.jquery.com/jquery.map/
I'm trying to refactor an object into an array to fit an API that I have to use.
function buildObject(data) {
console.log(data) // correct ouput
var jsonLayers = Object.entries({...data}).map(([k, v]) => {
console.log(`value : ${v}`) // correct output
return {
label: k,
pointMap: v,
color: v.layerColor?v.layerColor:tabList[tabList.map(i => i.tabName).indexOf(k)].layerColor?tabList[tabList.map(i => i.tabName).indexOf(k)].layerColor:defaultPOILayerColor,
}}
)
console.log(jsonLayers) // incorrect output
return jsonLayers
}
I have 2 scenarios in which I call this function but with the same data (console.log(data) logs the exact same result).
In the first scenario I obtain the desired result and in the second the pointMap attribute is an empty Array.
In the case where it does not work the console.log(`value : ${v}`) logs the correct value but when I log console.log(jsonLayers) the attribute pointMap is empty.
Any ideas on why this happens ?
Edit: as mentionned below this code works with sample data so I suppose this must comes to how the function is called. Everything runs in a UWA widget with jQuery
So for the context here is an idea of how they are called in both cases :
var data = {
a: { tabName: "tab1", layerColor: 1 },
b: { tabName: "tab2", layerColor: 2 },
};
$('#button1').on('click', function() {
ExternalAPI.Publish('some-external-address', buildObject(data));
});
$('#button2').on('click', function() {
let jsonData = buildObject(data);
//some manipulations of jsonData
ExternalAPI.Publish('some-external-address', jsonData);
});
It works on button1 but not on button2, they are clicked are at a different moment but data contains the same object when both are clicked
Edit2 :
Found the issue,
In the manipulations of jsonData I accidentally use slice(1,1) instead of splice(1,1) which empties the array.
What drove me crazy is that this modification was perform after the log but the var was log with the modification.
At least I learnt that spread operator does not deepcopy
Found the issue,
In the manipulations of jsonData I accidentally use slice(1,1) instead of splice(1,1) which empties the array.
What drove me crazy is that this modification was perform after the log but the var was log with the modification
I have a snippet of code here where i have an array that may or may not have keys in it. When the user presses on a 'friend' they add them to a list (array) where they might start a chat with them (add 3 friends to the array, then start a chatroom). The users selected might be toggled on or off.
Current Behavior:
i can add/remove one person, but i cant add multiple people to the array at the same time. When i add one person, select another - the first person is 'active', when i remove the first person, the second person automatically becomes active
Expected Behavior:
I would like to be able to add multiple people to the array and then remove any of the selected items from the array
onFriendChatPress = (key) => {
console.log(key) // this is my key 'JFOFK7483JFNRW'
let friendsChat = this.state.friendsChat // this is an empty array initially []
if (friendsChat.length === 0) {
friendsChat.push(key)
} else {
// there are friends/keys in the array loop through all possible items in the array to determine if the key matches any of the keys
for (let i = 0; i < this.state.selGame.friends.length; i++) {
// if the key matches, 'toggle' them out of the array
if (friendsChat[i] === key) {
friendsChat = friendsChat.filter(function (a) { return a !== key })
}
else {
return friendsChat.indexOf(key) === -1 ? friendsChat.push(key) :
}
}
}
}
Help please!
From your code, I was quite confused regarding the difference between this.state.selGame.friends and this.state.friendsChat. Maybe I missed something in your explication. However, I felt that your code seemed a bit too overcomplicated for something relatively simple. Here's my take on that task:
class Game {
state = {
friendsChat: [] as string[],
};
onFriendToggle(key: string) {
const gameRoomMembers = this.state.friendsChat;
if (gameRoomMembers.includes(key)) {
this.state.friendsChat = gameRoomMembers.filter(
(member) => member !== key
);
} else {
this.state.friendsChat = [...gameRoomMembers, key];
}
}
}
I used typescript because it makes things easier to see, but your JS code should probably give you a nice type inference as well. I went for readability over performance, but you can easily optimize the script above once you understand the process.
You should be able to go from what I sent you and tweak it to be according to what you need
In my ReactJS application I am getting the mobile numbers as a string which I need to break and generate a link for them to be clickable on the mobile devices. But, instead I am getting [object Object], [object Object] as an output, whereas it should be xxxxx, xxxxx, ....
Also, I need to move this mobileNumbers function to a separate location where it can be accessed via multiple components.
For example: Currently this code is located in the Footer component and this code is also need on the Contact Us component.
...
function isEmpty(value) {
return ((value === undefined) || (value === null))
? ''
: value;
};
function mobileNumbers(value) {
const returning = [];
if(isEmpty(value))
{
var data = value.split(',');
data.map((number, index) => {
var trimed = number.trim();
returning.push(<NavLink to={`tel:${trimed}`} key={index}>{trimed}</NavLink>);
});
return returning.join(', ');
}
return '';
};
...
What am I doing wrong here?
Is there any way to create a separate file for the common constants / functions like this to be accessed when needed?
First question:
What am I doing wrong here?
The issue what you have is happening because of Array.prototype.join(). If creates a string at the end of the day. From the documentation:
The join() method creates and returns a new string by concatenating all of the elements in an array (or an array-like object), separated by commas or a specified separator string. If the array has only one item, then that item will be returned without using the separator.
Think about the following:
const navLinks = [{link:'randomlink'}, {link:'randomlink2'}];
console.log(navLinks.join(','))
If you would like to use concatenate with , then you can do similarly like this:
function mobileNumbers(value) {
if(isEmpty(value)) {
const data = value.split(',');
return data.map((number, index) => {
const trimed = number.trim();
return <NavLink to={`tel:${trimed}`} key={index}>{trimed}</NavLink>;
}).reduce((prev, curr) => [prev, ', ', curr]);
}
return [];
};
Then you need to use map() in JSX to make it work.
Second question:
Is there any way to create a separate file for the common constants / functions like this to be accessed when needed?
Usually what I do for constants is that I create in the src folder a file called Consts.js and put there as the following:
export default {
AppLogo: 'assets/logo_large.jpg',
AppTitle: 'Some app name',
RunFunction: function() { console.log(`I'm running`) }
}
Then simply import in a component when something is needed like:
import Consts from './Consts';
And using in render for example:
return <>
<h1>{Consts.AppTitle}</h1>
</>
Similarly you can call functions as well.
+1 suggestion:
Array.prototype.map() returns an array so you don't need to create one as you did earlier. From the documentation:
The map() method creates a new array populated with the results of calling a provided function on every element in the calling array.
I hope this helps!
I built a custom component that filters an array of objects. The filter uses buttons, sets from active to non-active and allows more than one option on/off at the same time.
StackBlitz of my attempt - https://stackblitz.com/edit/timeline-angular-7-ut6fxu
In my demo you will see 3 buttons/options of north, south and east. By clicking on one you make it active and the result should include or exclude a matching "location" either north, south and east.
I have created my methods and structure to do the filtering, I'm struggling with the final piece of logic.
So far I have created a method to create an array of filtered locations depending on what the user clicks from the 3 buttons.
Next this passes to my "filter array" that gets the logic that should compare this filtered array against the original to bring back the array of results that are still remaining.
Its not quite working and not sure why - I originally got this piece of functionality working by using a pipe, but fore reasons do not want to go in that direction.
//the action
toggle(location) {
let indexLocation = this.filteredLocations.indexOf(location);
if (indexLocation >= 0) {
this.filteredLocations = this.filteredLocations.filter(
i => i !== location
);
} else {
this.filteredLocations.push({ location });
}
this.filterTimeLine();
}
// the filter
filterTimeLine() {
this.filteredTimeline = this.timeLine.filter(x =>
this.contactMethodFilter(x)
);
}
//the logic
private contactMethodFilter(entry) {
const myArrayFiltered = this.timeLine.filter(el => {
return this.filteredLocations.some(f => {
return f.location === el.location;
});
});
}
https://stackblitz.com/edit/timeline-angular-7-ut6fxu
Sorry for my expression but u have a disaster in your code. jajaja!. maybe u lost that what u need but the logic in your functions in so wrong. comparing string with objects. filter a array that filter the same array inside... soo u need make a few changes.
One:
this.filteredLocations.push({location});
Your are pushing object. u need push only the string.
this.filteredLocations.push(location);
Two:
filterTimeLine() {
this.filteredTimeline = this.timeLine.filter(x =>
this.contactMethodFilter(x)
);
}
in this function you filter the timeLine array. and inside of contactMethodFilter you call filter method to timeLine again....
See a functional solution:
https://stackblitz.com/edit/timeline-angular-7-rg7k3j
private contactMethodFilter(entry) {
const myArrayFiltered = this.timeLine.filter(el => {
return this.filteredLocations.some(f => {
return f.location === el.location;
});
});
}
This function is not returning any value and is passed to the .filter
Consider returning a boolean based on your logic. Currently the filter gets undefined(falsy) and everything would be filtered out