I have the following tooltip component:
export interface ITooltipProps {
Title: string;
Visibility: boolean;
Items: any[];
}
export const Tooltip: React.StatelessComponent<ITooltipProps> = (props) => {
if (!props.Visibility) {
return null;
}
return (
<div className={css.toolTip} role="tooltip" style={{margin: props.Margin}} aria-hidden={props.Visibility}>
<h1 className={css.toolTipHeader}>{props.Title}</h1>
<ul className={css.itemList}>
{props.Items.map((o) => {
return (
<li key={o.ID}>{o.Data}</li>
);
})}
</ul>
</div>
);
};
That gets called from another component like:
<div onMouseOver={this.showtooltip} onMouseLeave={this.hidetooltip}>
<Tooltip Title={strings.SecurityGroup_Label_ManagementOffices} ManagementOffices={offices} Visibility={this.state.IsToolTipVisible} />
</div>
private showtooltip = () => {
this.setState({ IsToolTipVisible: true });
}
private hidetooltip = () => {
this.setState({ IsToolTipVisible: false });
}
The problem I'm facing is, since there's one IsToolTipVisible, if I have multiple tooltips in the component, it displays/hides it all the tool tips at once. How do I code this so that it only displays the item being hovered over?
This can go into comments but adding it as an answer since it's big.
I can think of two ways now using your unique id (expecting it to be a id in the object we set to tooltip and not html id attributes)
you can use uniqueid and instead of saving a Boolean of istooltipvisible, save which one is currently visible.
And in the place where you check for istooltipvisible with Boolean, use something like eachuniqueid === currentvisibleid
Using this, you will have only one tooltip at a time which could be a concern.
You can save a property istooltipvisible for each tooltip and whenever you have it visible, set it to true and when you close it, set it to false.
This way you can manage multiple tooltips aswell.
Related
I am trying to make a text which displays some information upon mouse hover. For example, I have three tags with following information
<div class="body main-seq" style="display: inline;">
<span prob="67.8">
Foo
</span>
<span prob="67.8;34.6">
Bar
</span>
<span prob="67.8;34.6;52.7">
Hello
</span>
</div>
On a browser, it will look something like this
FooBarHello
Basically, when user hovers a mouse on first bit of the text (the one that corresponds to "Bar"), I want to bold all the span tags that contain "34.6" in its "prob" attribute. In this case, it would have to bold "BarHello", but leave "Foo" as it is.
After doing some Google search, this task seems pretty trivial in Javascript or jQuery, and can be done by doing something like so,
$("span[prob*='34.6']") (along with onMouseOver event or something similar)
Find an element in DOM based on an attribute value
However, I've seen many posts saying I should absolutely try to avoid using jQuery in React because React and jQuery has conflicting philosophy (React renders DOM every time the data changes whereas jQuery directly manipulates DOM). Please correct me if I am wrong though.
So my question is, how can I achieve this in React?
You could perhaps do something like this in React:
import React, { useState } from 'react';
function MyComponent(props) {
const [ isHovered, setIsHovered ] = useState(false);
const onMouseEnter = () => {
setIsHovered(true);
}
const onMouseLeave = () => {
setIsHovered(false);
}
const spans = [
{ text: 'Foo', probs: [67.8] },
{ text: 'Bar', probs: [67.8, 34.6] },
{ text: 'Hello', probs: [67.8, 34.6, 52.7] }
];
return (
<div
class="body main-seq"
style="display: inline;"
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{spans.map(({ text, probs }) => {
const isBold = isHovered && probs.includes(34.6);
return (
<span style={{ fontWeight: isBold ? 'bold' : 'normal'; }}>
{text}
</span>
);
}}
</div>
);
}
I hope this helps.
I think you're approaching this the wrong way, but it's a great exercise in "thinking in React".
You basically have these span tags that store number values. Each of them have the same responsibilities:
Store a list of number values
When clicked, set the new "criteria" values
let the others know that they should check if they should be bold or not
So let's call this component SpanComponent. We want a structure like this:
export const SpanComponent = ({ values, activeValues, setActiveValues, children }) => {
const isBold = checkForMatchingValues(values, activeValues) // returns true or false
const onMouseEnter = event => {
setActiveValues(values)
}
return (
<span style={{ fontWeight: isBold ? 'bold' : 'normal' }} onMouseEnter={onMouseEnter}>{children}</span>
)
}
Then in our main component we can manage these as so:
export const MainComponent = () => {
const [activeValues, setActiveValues] = useState([]) // empty array as default value
return (
<SpanComponent values={[68.8]} activeValues={activeValues} setActiveValues={setActiveValues}/>
<SpanComponent values={[68.8, 34.6]} activeValues={activeValues} setActiveValues={setActiveValues}/>
<SpanComponent values={[68.8, 34.6, 52.7]} activeValues={activeValues} setActiveValues={setActiveValues}/>
)
}
I have a react table where I am showing and hiding columns individually on a checkbox click. If I remove the checked property from the checkbox input element things work as expected. However if I add it in and track the checked property using state, the check box will click off the first time but not back on but the column state does update and reappears.
Eventually my end goal is to be able to click the "remove all columns" option at the top of this list to show hide all columns. The show/hide piece works but the checking of the boxes does not.
updateColumnShow = fieldName => {
this.setState({ [fieldName]: !fieldName });
this.setState(state => {
const columns = state.columns.map(column => {
if (column.Header === fieldName) {
column.show = !column.show;
return column;
} else {
return column;
}
});
return { columns };
});
};
render() {
return (
<div>
{this.state.columnList.map(listItem => (
<li key={listItem}>
<input
id={listItem}
checked={this.state[listItem]}
className="form-check-input"
type="checkbox"
name={listItem}
onChange={() => this.updateColumnShow(listItem)}
/>
<label>{listItem}</label>
</li>
))}
</div>
);
}
I have created a CodeSandbox to demo the issue.
What am I overlooking?
You're passing a string (fieldName) to updateColumnShow, and then negating it as a boolean; this will always be false.
updateColumnShow = fieldName => {
this.setState({ [fieldName]: !fieldName });
}
The immediate fix is to invert your state value instead:
this.setState({ [fieldName]: !this.state[fieldName] });
I want to toggle a fontAwesome icon class name on click. When clicked, the icon should change the color and also call a service which adds an object to a favorite list in the server (hence, why I use e.currentTarget: I need to remember which icon was clicked). This code works on the first click, but fails to change the class back on the second click (doing an inspect, it says the FA´s classname equals "Object object"). Any idea how I could fix it?
<FontAwesomeIcon onClick={this.ToggleClass} size={"sm"} icon={faHeart} />
ToggleClass = (e) => {
const heart = {
color: "#E4002B",
}
const clicked = {
color: "#E4002B",
background: "red"
}
if (e.currentTarget.className.baseVal != heart && e.currentTarget.className.baseVal != clicked) {
return e.currentTarget.className.baseVal === clicked;
#Callservicehere
}
else if (e.currentTarget.className.baseVal === clicked) {
e.currentTarget.className.baseVal = heart;
#callservicehere
}
}
You're not thinking in React yet :)
Accessing the event target and imperatively manipulating the DOM bypasses React's rendering - you might as well just be using jQuery. Not that there's anything bad about that, but it's not the right way to go about things in React.
In React, if you need to change the DOM in response to user interaction you do it in the render method, i.e. output different JSX based on the component's current state or props.
A couple things that might help here:
clicked and heart are both objects which means that you cannot compare them without using a deep comparison method.
var a = { id: 1 }
var b = { id: 1 }
console.log(a == b) //false
console.log(a === b) //false
If you want to compare them, you can convert them both to strings using the toString() method
heart.toString() === clicked.toString()
In your first if condition, it looks like you're returning a true/false value instead of assigning a desired classname to your target.
return e.currentTarget.className.baseVal === clicked // true/false
e.currentTarget.className.baseVal = clicked // assigned
You could also take the approach of keeping your classnames as strings and adding your styled objects inside of css
class MysteryComponent extends React.Component {
state = {
className: 'heart'
}
toggleClass = (e) => {
if (this.state.className === 'heart') {
this.setState({ className: 'clicked' })
} else if (this.state.className === 'clicked') {
this.setState({ className: 'heart' })
}
}
render() {
return (
<div className={this.state.className}>
<FontAwesomeIcon onClick={this.toggleClass} size={"sm"} icon={faHeart} />
</div>
)
}
}
// css
.heart {
color: "#E4002B";
}
.clicked {
color: "#E4002B";
background: "red";
}
I see. You want to fill/unfill the color of the heart as the user clicks. The reason why the results are not meeting your expectations is because of the event.targets are especially funky with FontAwesome. You may think you're clicking on it, but it manipulates the DOM in a way that when you try extract the className, it's value is often inconsistent.
This is why everyone is recommending that you make use of React's state. The logic that determines how elements are styled is now more controlled by the component itself instead of the FontAwesome library. Consider the code below, we only care about whether the item was clicked, not what class it initially has.
class Example extends React.Component{
state = {
clicked: false
}
handleOnCLick = () => {
this.setState({
clicked: !this.state.clicked
})
}
render(){
var clicked = this.state.clicked
return(
<button onClick={this.handleOnClick}>
<i
class={ clicked ? "fas fa-heart" : "fas fa-circle"}
</i>
</button>
)
}
}
This question is about https://github.com/clauderic/react-sortable-hoc.
I am pretty new to React, so I find the following a bit irritating. Go to
https://jsfiddle.net/7tb7jkz5/. Notice line 4
const SortableItem = SortableElement(({value}) => <li className="SortableItem" onClick={console.debug(value)}>{value}</li>);
When you run the code, the console will log "Item 0" to "Item 99". A click on an item will log the same, but three times. Why does this happen? And is this really necessary or more like a bug?
I expected a behavior similar to an ordinary DOM, so the click event would bubble up from the clicked item and could be caught along the way through its ancestors. The observed behavior looks to me like the click event would be sent down by the list to each list item three times.
Update:
Well, this is actually as clear as crystal, thanks for pointing this out, Shubham. I did not just specify a reference, but an actual call to console.debug, which was executed every time the expression was evaluated. Common mistake.
Still, this means, each list item is rendered (I guess) three times, when I click one of them. I suspect missing optimization or even useless redrawing.
Try to use distance={1} on SortableContainer component.
Check this link https://github.com/clauderic/react-sortable-hoc/issues/461
const ListItemContainer = SortableContainer((props) => {
return <listItem />
})
<ListItemContainer
onSortEnd={this._orderingFolder}
lockAxis='y'
lockToContainerEdges={true}
lockOffset='0%'
distance={1}
/>
Another way you can define and handle click event using react-sortable-hoc:
please check below code
import { SortableContainer, SortableElement, arrayMove } from 'react-sortable-hoc';
const SortableItem = SortableElement(({ value }) => {
return (
<div >
<div id="button_value">{value}</div>
</div >
);
});
const SortableList = SortableContainer(({ items }) => {
return (
<div >
{items.map((value, index) => (
<SortableItem
key={`item-${index}`}
index={index}
value={value}
/>
))}
</div>
);
});
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 'Item 6'],
};
}
onSortEnd = ({ oldIndex, newIndex }) => {
this.setState(({ items }) => ({
items: arrayMove(items, oldIndex, newIndex),
}));
};
shouldCancelStart = (e) => {
var targetEle = e;
if (!targetEle.id) {
targetEle = e.target;
}
if (targetEle.id === 'button_value') {
console.log('Div button click here ');
// perform you click opration here
}
// you can define multiple click event here using seprate uniq id of html element even you can manage delay on click here also using set timeout and sortmove props and sortend props you need to manage just one boolean flag.
}
render() {
return (
<div>
<SortableList
items={this.state.items}
onSortEnd={this.onSortEnd}
onSortStart={(_, event) => event.preventDefault()}
shouldCancelStart={this.shouldCancelStart} />
</div>
);
}
}
export default App;
First define id button_value as in html element and then using this id you can get click event using this props shouldCancelStart
You need to mention the onClick action as a function. Use () => inside the handler call. Try the below method, it works although has a very slow response
const SortableItem = SortableElement(({value}) => <li className="SortableItem" onClick={() => console.debug(value)}>{value}</li>);
I've been trying to set element to become strikethrough when I click on it, but unfortunately I couldn't, nothing happens.
var UserList = React.createClass({
getInitialState: function() {
return {
user: [],
firstName: '',
lastName: '',
createdAt: 0,
isClicked: false,
};
},
handleOnClick: function() {
var isClicked = this.state.isClicked;
var style = {textDecoration: 'none'};
if (isClicked === true) {
style = {textDecoration: 'line-through'}
}
},
render: function() {
return (
<div>
<Users user={this.state.user} onClick={this.handleOnClick}/>
</div>
);
You can do it this way:
A similar example to yours:
const TodoItem = ({item, checkHandler}) => {
const itemCheckHandler = () => {
checkHandler (item.id);
};
return (
<div>
<li
style={{
textDecoration: item.checked ? 'line-through' : 'none',
}}
onClick={itemCheckHandler}
>
{item.text}
</li>
</div>
);
};
and your checkHandler in your App.js where the state resides is like this (items bein an array of items):
checkHandler = id => {
this.setState ({
items: this.state.items.map (item => {
if (item.id === id) {
item.checked = !item.checked;
}
return item;
}),
});
};
Don't try to change the style in a click handler. You should not change the style when a user does an action but rather do it at the time of it being rendered, that's the correct approach.
Store the "strikethrough" value in a flag in the state and do it in the render function.
For example:
getInitialState: function () {
return {
...
isStrikeThrough: false,
...
}
},
onHandleClick: function () {
....
// toggle the strikethrough state
this.setState({isStrikeThrough: !this.state.isStrikeThrough});
....
},
render: function () {
return (
<div>
<User
user={this.state.user}
strikeThrough={this.state.isStrikeThrough}
onClick={this.handleOnClick}
/>
</div>
);
},
You haven't given any details about the User component, so the explanation above is based solely on what we have in the question. That said, there are a couple of ways in which this could be improved.
First, I'm assuming that you can add the strikethrough flag to the User component and render the <strike>...</strike> (or comparable CSS styles) there. That may or may not be true (ie. if the User component is a third-party component, it may be difficult to change it).
Second, the strikethrough state described above looks to me like it ought to be internal to the User component. If all you're doing is changing the markup in the User component based on a click on the User component, then the strikethrough code ought to be in the User component. And, perhaps more importantly, if the strikethrough is supposed to represent something important about the state of a user, something that should be saved as part of the user's state, then the strikethrough flag ought to be part of the user's state (and have a more informative name than isStrikeThrough).
Dodek you can see the above answers but I think you need to change the way you look and think about a react application then it helps you a lot during your coding with react. The code you provided looks like a jQuery approach to me that you directly modify the DOM element when user do an action. Even if there was no issue in your approach still your code does not apply 'line-through' style to an already checked element which you get them from backend unless user clicks on an item.
You should look at your component as a very simple actor in a movie that accepts a very small set of parameters (compared to real word) and based on this input parameters it changes the way it appears in the frame. For example lets say you have a Todo item component (Like the one #Vennessa has provided here) in a vey simple case it accepts only an item text and also whether or not the item is checked;
These parameters may come from internal state or come from props or any other resources but in the end your component is accepting these parameters and all your internal logic that determines how your component should look must only rely and work with these params.
Try this out:
function Item(props){
const [clicked, setIsClicked] = useState(false);
function handleClick(){
setIsClicked((prevValue) => {
return (!prevValue)
});
}
return <li style={{textDecoration: clicked ? "line-through": "none" }} onClick={handleClick}>
{props.text} </li>
}