React add a variable name attribute - javascript

What is the cleanest way in React to add a variable name attribute?
The end result I want is: <div data-flag>Hello</div> where the name data-flag is stored in a const variable.
The ways I found now are:
const DATA_FLAG = 'data-flag';
const Hello = ({ name }) => {
const dataAttrs = { [DATA_FLAG]: true }
return <div {...dataAttrs}>Hello {name}</div>;
}
Or this one-line version (but I find it less readable):
const Hello = ({ name }) => <div {...{ [DATA_FLAG]: true }}>Hello {name}</div>;
You can play with it in this JSFiddle
These versions work fine if the attribute was variable (true or false) but in my case it's always true so I find it a bit over-killed and the syntax complex.
I would love to know if there is a cleaner way to achieve it.
I know that the best approach is to apply it directly like: <div data-flag>Hello</div> but I really need to store data-flag in a constant (as shared by other components).

You could create a React element without using the JSX syntax. Something like this should work:
const DATA_FLAG = 'data-flag'
const Hello = ({ name }) => {
const dataAttrs = { [DATA_FLAG]: true }
return React.createElement('div', dataAttrs, `Hello ${name}`)
}
This only really makes the way you're passing in the attributes look easier on the eyes though. Don't think you'll get around defining a dataAttrs object for your props.

Related

bad value includes() react

i'm trying to make a function that add a keyword if is not already in the array, if it is shows a error message, but the problem is that the includes() method doesn't work properly, it shows false first (when a word is not in the array), then true (because it is in the array) and then false again if i don't change the word.
I don't know why this is happening but it happens also with indexOf(). Maybe its a react problem with rendering or something like that.
Between if its another way to take an input value and do this, it is welcome
const [repeatedKeyWord, setRepeatedKeyWord] = useState(false)
let keyWords = []
const addKeyword = () => {
let keyword = document.getElementById('keyword').value;
const exist = keyWords.includes(keyword);
console.log(exist)
if (keyword && !exist){
console.log('in')
keyWords.push(keyword)
setRepeatedKeyWord(false)
}
setRepeatedKeyWord(exist? true : false)
console.log(keyWords)
}
<PlusIcon className="w-6 text-firstColor cursor-pointer mr-2" onClick={addKeyword} />
You must store your keywords in useState, otherwise you lose the value between re-renders of your component. Thereafter, you can use .includes on your array. React ensures that you'll always have the latest value (e.g. 'snapshot' of your state).
Also note that when you are trying to compute a new state value (i.e. updating your array) you are dependent on the previous value of your state. If that is the case, you can pass a function to the setState function. Have a look at this issue where I have included a link to a working codesandbox for updating previous state values.
As a side note, I would suggest to avoid using let to declare your variables. Only use the let keyword if you are certain that you will re-assign a new value to your variable. Otherwise using const might be better to avoid mistakes.
const [keywords, setKeyWords] = useState([])
const addKeyword = () => {
const keyword = document.getElementById('keyword').value;
return setKeywords((prevState) => {
if (prevState.includes(keyword)) {
return [...prevState]
}
return [...prevState, keyword]
})
}
<PlusIcon className="w-6 text-firstColor cursor-pointer mr-2" onClick={addKeyword}

How can I pass innerHTML to an onClick function (Typescript)

I'm trying to pass the Square element's innerHTML to the onClick function. I have also tried to pass in just i but it always is equal to 100. Is there a way to either pass i when it's equal to the same value that goes into the Square or is there a way to pass the innerHTML to the function. Currently, this code generates the error:
[TS: 2532]this is possibly undefined
I'm making a grid of 100 squares, each one is a button, and each one should have it's own ID/number from 1-100 to identify them.
This is what I have currently: Grid of 100 squares arranged in 10x10 formation
export const Square = (props:any) =>{
i += 1;
if(i > 100)
{
i = 1;
}
return(
<DefaultButton styles={factionMapButton} onClick={()=>onSquareClick(this.innerHTML,props.onClick)}>{i}</DefaultButton>
);
}
const onSquareClick = (number:any,isOpen:any) => {
console.log(number);
const panelContent = document.getElementById("panelContent");
if(panelContent !== null)
{
panelContent.innerHTML = number;
}
isOpen();
}
You have quite a few problems.
You should do your best to avoid any in TypeScript, especially not liberally - that defeats the whole purpose of type-checking. If you're trying to fix type problems, you should start by also typing everything properly.
Arrow functions do not have their this altered by the calling context. If there's no enclosing full-fledged function, the this in an arrow function will be the global object or undefined, both of which are useless to you. Either use a function to capture the this, or, even better, use the click event's currentTarget to get a reference to the clicked button.
The .innerHTML of an element returns a string, not an element. If it contains a string that can be coerced to a number, explicitly coerce it to a number instead. (If the HTML content is only the string that can be coerced to the number, you should use .textContent instead - only use .innerHTML when deliberately setting or retrieving HTML markup, not plain text)
A better approach would be to pass down the i to onSquareClick instead of using DOM manipulation - using the closure is much easier
let i = 1;
export const Square = ({ onClick }: { onClick: () => void }) => {
i += 1;
if (i > 100) {
i = 1;
}
return (
<DefaultButton styles={factionMapButton} onClick={(e) => { onSquareClick(i, onClick); }}>{i}</DefaultButton>
);
};
const onSquareClick = (number: number, isOpen: () => void) => {
const panelContent = document.getElementById('panelContent');
if (panelContent !== null) {
panelContent.innerHTML = String(number);
}
isOpen();
};
If you're using React, you should not be using vanilla DOM manipulation like panelContent.innerHTML = number; - instead, set React state that the view uses to determine what should exist in that element. Something like
// Parent component:
const [panelContentText, setPanelContentText] = useState('');
// expand as needed for the other components in your app...
return <div id="panelContent">{panelContentText}</div>
<Square setPanelContentText={setPanelContentText} /* and other props */ />
// ...
// Then call the setPanelContentText prop in onSquareClick
const onSquareClick = (number: number, isOpen: () => void, setPanelContentText: React.Dispatch<React.SetStateAction<string>>) => {
setPanelContentText(String(number));
isOpen();
};
I'd recommend looking into an introductory React tutorial, it looks like you might benefit from learning the process within React's ecosystem, rather than trying to mishmash your understanding of vanilla JS's DOM with React.

Select and deselect values with react and hooks

I am trying to change the state by selecting and deselecting the language option in the code below. So far I can update the state by adding a language, my problem is that, if I click on the same language again, I will add it another time to the array. Can anyone explain me how to add or remove the language from the array when clicked one more time?
export default function Dashboard(props) {
const [language, setLanguage] = useState('');
const handleLanguageChange = changeEvent => {
changeEvent.persist()
setLanguage(prevState => [...prevState, changeEvent.target.value])
};
}
It looks like your only issue is your logic in the place where you are handling update. Usage of hooks is correct
So first of all you need to set proper initial value. As you plan to store your languages in an array.
Second part is updating the array. So you can either find clicked language in the array and if it is exist - then use filter to set your new value or filter and compare length of existing array and new one.
const [language, setLanguage] = useState([]);
const handleLanguageChange = changeEvent => {
changeEvent.persist()
setLanguage(prevState => {
const lang = changeEvent.target.value;
if (prevState.includes(lang) {
return prevState.filter(el => el !== lang);
}
return [...prevState, lang]
})
};
You will need a good old check.
if (!languages.includes(changeEvent.target.value) {
// code to add the language
}
Check the selected value using find() method on language array if it returns undefined, then push into array. Rename the state variable as languages otherwise it's confusing (Naming convention standard).
const [languages, setLanguages] = useState('');
const handleLanguageChange = changeEvent => {
changeEvent.persist()
if (!languages.find(value => value == changeEvent.target.value)) {
setLanguages(prevState => [...prevState, changeEvent.target.value])
}
};
2 Things here
Instead of having
<option value="Deutsch">Deutsch</option>
<option value="Englisch">Englisch</option>
use an languages array of json so it bacomes easy for you to add them like
languages= [{value='Deutsch',name= 'Deutsch',...}]
2.setLanguage sa a direct value
setLanguage(changeEvent.target.value)

How can I give a key in JSX the value of a variable depending on conditions

I'm learning React by implementing a front-end interface for the note app API that I created. I have succeeded in having a list of all the note titles in my database appear. I want to be able to click on a title and have the note expand into the text of the note. The easiest way I've found for this is to give the "key" attribute of the 'li' as a variable and to also declare the same variable in the JSX { } object because they have the same name.
I've been looking for an answer for this for a few days and have been unable to find this exact problem. You can put a variable in a normal JSX expression but I need to do it on the 'li' which means technically in the HTML.
Here's some code to understand what I'm saying.
const NoteData = () => {
const [titles, setTitles] = useState([]);
const [open, setOpen] = useState(false);
useEffect(() => {
//AXIOS CALL
setTitles(response.data[0]);
});
}, []);
//^^^^^add the array there to stop the response.data from repeating WAY TOO MANY TIMES
let listTitles = titles.map(titles => (
<li className="noteTitles" key={titles.title}>
{titles.title}
</li>
));
let showText = titles.map(titles => (
<li className="openText" key= {titles.text_entry}>
{titles.text_entry}
</li>
))
let openNote = () => {
setOpen(open => !open);
if (open) {
return (
<div className="noteContainer">
<ul onClick={openNote} className="titlesList">
{showText}
</ul>
</div>
);
}
if (!open) {
return (
<div className="noteContainer">
<ul onClick={openNote} className="titlesList">
{listTitles}
</ul>
</div>
);
}
};
return { openNote };
};
export default NoteData;
That is the code I currently have. Here's showing a more simplified version of the openNote function that maybe makes more sense and shows what I'm trying to do:
VariableHere = "";
let openNote = () => {
setOpen(open => !open);
open ? (VariableHere = titles.text_entry) : (VariableHere = titles.title);
};
let listNotes = titles.map(titles => (
<li className="noteTitles" key={VariableHere}>
{VariableHere}
</li>
));
return (
<div>
<ul onClick={openNote}>
{listNotes}
</ul>
</div>
);
On click of each element there should be a switch of the key elements so if the element is 'open' the key variable and given variable in the JSX object should be mapped to titles.text_entry and on '(!open)' the key and JSX should be mapped to titles.title.
first of all, you're using a ternary in a weird way:
open ? (VariableHere = titles.text_entry) : (VariableHere = titles.title);
Ternaries are meant to be expressions whose value is conditional, but you're using it like a shorthand if/else. Try something like
VariableHere = open ? titles.text_entry : titles.title;
which is both shorter and more readable.
Second of all, keys in an array of elements are meant to help React determine which elements to update, if an item represents the same object, its key shouldn't change. In this case, regardless of what you're displaying, an item in the array represents the same note. Always using the title as the key should be fine provided items can't have the same title. If they can, use some sort of unique ID instead. If the order of the items doesn't change throughout the life of the component, using the array index as the key is fine.
Lastly, what you seem to want to do is called "conditional rendering". There are many ways to achieve this in react, one such way is to use the pre-cited ternary operator. Here is a minimal working example:
const listNotes = titles.map(note => (
<li className="noteTitles" key={note.title}>
{open ? note.title : note.text_entry}
</li>
));
const openNote = () => {
setOpen(!open);
}
return (
<div className="noteContainer">
<ul onClick={openNote} className="titlesList">
{listNotes}
</ul>
</div>
)
You could also use a ternary in the key expression, but as I talked about above, it's not a good idea to do so.
Given your data-structure, I think you can simplify your code a bit. There is no need to create separate arrays for titles and contents. It sounds like you just want to expand and collapse a note when it is selected.
Here is a really simplified version on how you an do this. I'll use a sample data-set since we don't have access to your API.
const NoteData = () => {
const [titles, setTitles] = useState([]);
const [currentNote, setCurrentNote] = useState({});
useEffect(() => {
//AXIOS CALL
// setTitles(response.data[0]);
let data = [
{ id: 1, title: "a", text_entry: "what" },
{ id: 2, title: "b", text_entry: "is" },
{ id: 3, title: "c", text_entry: "up?" }
];
setTitles(data);
}, []);
const handleClick = noteId => {
let selectedTitle = titles.find(title => title.id == noteId);
//"collapse" if already selected
if (noteId === currentNote.id) {
setCurrentNote({});
} else {
setCurrentNote(selectedTitle);
}
};
let listTitles = titles.map(title => (
<li
className="noteTitles"
key={title.title}
onClick={() => handleClick(title.id)}
>
{title.title}
{title.id === currentNote.id && <div>{title.text_entry}</div>}
</li>
));
return (
<div>
Click on link item
<ul>{listTitles}</ul>
</div>
);
};
See working sandbox: https://codesandbox.io/s/old-silence-366ne
The main updates:
You don't need to have an "open" state. To be more succinct and
accurate, you should have a currentNote state instead, which is
set when clicking on a list item.
Have your handleClick function accept a noteId as an argument.
Then use that noteId to find the corresponding note in your titles
state. Set that found note as the currentNote. If the selected
note was already the currentNote, simply set currentNote to an
empty object {}, thus creating our expanding/collapsing effect.
In the JSX, after the title, use a ternary operator to conditionally
display the currentNote. If the note being mapped matches the
currentNote, then you would display a div containing the
text_entry.

Draft.js - CompositeDecorator: Is there a way to pass information from the strategy to the component?

Lets say my strategy calculates some numbered label. How can I pass this (e.g. via props) to the decorator component.
I know there is a props property in CompositeDecorator but how can I access it from the strategy function?
I'm a bit new to DraftJs, but based on my understanding:
Strategies should be used to identify the range of text that need to be decorated. The rendering of that decoration (which presumably includes calculating what the label should be) should be handled in the component itself, rather than the strategy.
You should be able to access the ContentState via the props object in your component, and calculate the label from that. The constructor of your component could be a good place for executing the logic for calculating the label. This also means that you might have to use a class definition for your decorator components as opposed to a pure function as shown in the examples on the draftjs website.
You can also circumvent the issue by reading the values from the text with regex. The following example is done with #draft-js-plugins:
// How the section is detected.
const strategy = (contentBlock, callback) => {
const text = contentBlock.getText();
const start = text.indexOf('<_mytag ');
const endTag = '/>';
const end = text.indexOf(endTag);
if (start !== -1 && end !== -1) {
callback(start, end + endTag.length);
}
};
// What is rendered for the detected section.
const component = ({ decoratedText }) => {
if (decoratedText) {
const label = decoratedText.match(/label="([a-zA-Z0-9/\s]*?)"/);
if (
label &&
typeof label[1] === 'string'
) {
return <div>{label[1]}</div>;
}
return null;
}
};
export const MyTagPlugin = {
decorators: [
{
strategy,
component,
},
],
};

Categories