How to change react element (li) with onClick to be strikethrough? - reactjs

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>
}

Related

ReactJS - Alternative to passing useState between files

Background
I have a file that presents my main page Dash.js
This presents some data from an API on a "card", from two other files List.js and ListLoading.js
I have an additional "card" which I can trigger open with default useState value of 1, and the onClick works to close, as you will see in the dash.js file.
Current Code
//Dash.js
function Dash(props) {
//control additional card
const [openCard, setopenCard] = React.useState(0);
const closeCard = () => {
setopenCard(0);
}
//set API repo
const apiUrl = (`http://example.com/api/`);
axios.get(apiUrl, {
withCredentials: true }).then((res) =>{
setAppState({ loading: false, repos: res.data.emails });
});
return (
{(openCard>0 &&
<Card>
<Cardheader onClick={() => closeCard()}>
Click here to close
</Cardheader>
<Cardbody>
Some data here
</Cardbody>
</Card>
)
|| null
}
<Card>
<ListLoading isLoading={appState.loading} repost={appState.repos} />
<Card>
);
}
//List.js
const List = (props) => {
const { repos } = props;
if (!repos || repos.length === 0) return <p>No data available</p>;
for (var key in repos) {
return (
{repos.map((repo) => {
return (
<p className='repo-text max-width' >ID:{repo.id}{" "}Value:{repo.value} </p>
);}
)}
);}
};
export default List;
//ListLoading.js
function WithListLoading(Component) {
return function WihLoadingComponent({ isLoading, ...props }) {
if (!isLoading) return <Component {...props} />;
return (
<p style={{ textAlign: 'center', fontSize: '30px' }}>
Fetching data may take some time, please wait
</p>
);
};
}
export default WithListLoading;
Desired Outcome
I want to set the the value for openCard.useState() to the repos.id.
e.g. onClick={() => openCard({repos.id})}
The complication of this is that I need to retrieve that code from List.js and pass it to the useState for the openCard, which is in Dash.js.
I am still fairly new to react so this is proving a little tricky to work out how to do.
What I've tried
I have looked into useContext, but either it has confused me or I am right to think this would not work for what I am trying to do.
I have looked into redux, however this seems like that may be overkill for this solution.
I have tried a series of passing the different constants via import/export however I now understand that useState is not designed to work this way and should really be used within the function/class where it is contained.
So any thoughts to remedy would be greatly appreciated!
So, just to restate what I understood your issue to be:
You have a parent component that renders a list of objects and can render a detail card of one of the object.
You want to have a single item in your list of objects be able to tell the parent "please open card 123".
Now to look at the options you considered:
Redux I agree Redux is overkill for this. Redux is usually only necessary if you need complex, possibly async reading and writing to a single shared datasource across the whole scope of your application. For a little UI interaction like this, it is definitely not worth setting up Redux.
React Context Context relies on a Provider component, which you wrap some chunk of your app in. Any component below that Provider can then use useContext to reach into the memory of that Provider. You can store anything in there that you could store in a component, from a single state variable up to a more complex useReducer setup. So, in a way, this basically does what you were hoping to do with static variables passing the state around. This is the right solution if you were going to be using this state value across a wide variety of components.
Props are probably the right way to go here - since you have a parent who wants to get messages from a child directly you can give the child a callback function. This is the same as the onClick function you can give a button, except here you can pass your list a onShowCard function.
In your Dash:
<ListLoading
isLoading={appState.loading} repost={appState.repos}
onShowCard={(cardId) => setopenCard(cardId)} />
At the end of the List:
{repos.map((repo) => {
return (
<button key={repo.id} className='repo-text max-width' onClick={() => { props.onShowCard(repo.id) }>
ID:{repo.id}{" "}Value:{repo.value}
</button>
);}
)}
You can pass on the function to update state to ListLoading component which will be forwarded to List component assuming it is wrapped by thee HOC WithListLoading.
Inside List you can then attach and onClick on the element to pass on the id of the clicked element
function Dash(props) {
//control additional card
const [openCard, setopenCard] = React.useState(0);
const closeCard = () => {
setopenCard(0);
}
//set API repo
const apiUrl = (`http://example.com/api/`);
axios.get(apiUrl, {
withCredentials: true
}).then((res) =>{
setAppState({ loading: false, repos: res.data.emails });
});
const handleOpen = id => {
setopenCard(id);
}
return (
{(openCard>0 &&
<Card>
<Cardheader onClick={() => closeCard()}>
Click here to close
</Cardheader>
<Cardbody>
Some data here
</Cardbody>
</Card>
)
|| null
}
<Card>
<ListLoading isLoading={appState.loading} repost={appState.repos} handleOpen={handleOpen} />
<Card>
);
}
const List = (props) => {
const { repos, handleOpen } = props;
if (!repos || repos.length === 0) return <p>No data available</p>;
for (var key in repos) {
return (
{repos.map((repo) => {
return (
<p className='repo-text max-width' onClick={() => props.handleOpen(repo.id)} >ID:{repo.id}{" "}Value:{repo.value} </p>
);}
)}
);}
};
export default List;

How to prevent unnecessary re-renders with React Hooks, function components and function depending on item list

List of items to render
Given a list of items (coming from the server):
const itemsFromServer = {
"1": {
id: "1",
value: "test"
},
"2": {
id: "2",
value: "another row"
}
};
Function component for each item
We want to render each item, but only when necessary and something changes:
const Item = React.memo(function Item({ id, value, onChange, onSave }) {
console.log("render", id);
return (
<li>
<input
value={value}
onChange={event => onChange(id, event.target.value)}
/>
<button onClick={() => onSave(id)}>Save</button>
</li>
);
});
ItemList function component with a handleSave function that needs to be memoized.
And there is a possibility to save each individual item:
function ItemList() {
const [items, setItems] = useState(itemsFromServer);
const handleChange = useCallback(
function handleChange(id, value) {
setItems(currentItems => {
return {
...currentItems,
[id]: {
...currentItems[id],
value
}
};
});
},
[setItems]
);
async function handleSave(id) {
const item = items[id];
if (item.value.length < 5) {
alert("Incorrect length.");
return;
}
await save(item);
alert("Save done :)");
}
return (
<ul>
{Object.values(items).map(item => (
<Item
key={item.id}
id={item.id}
value={item.value}
onChange={handleChange}
onSave={handleSave}
/>
))}
</ul>
);
}
How to prevent unnecessary re-renders of each Item when only one item changes?
Currently on each render a new handleSave function is created. When using useCallback the items object is included in the dependency list.
Possible solutions
Pass value as parameter to handleSave, thus removing the items object from the dependency list of handleSave. In this example that would be a decent solution, but for multiple reasons it's not preferred in the real life scenario (eg. lots more parameters etc.).
Use a separate component ItemWrapper where the handleSave function can be memoized.
function ItemWrapper({ item, onChange, onSave }) {
const memoizedOnSave = useCallback(onSave, [item]);
return (
<Item
id={item.id}
value={item.value}
onChange={onChange}
onSave={memoizedOnSave}
/>
);
}
With the useRef() hook, on each change to items write it to the ref and read items from the ref inside the handleSave function.
Keep a variable idToSave in the state. Set this on save. Then trigger the save function with useEffect(() => { /* save */ }, [idToSave]). "Reactively".
Question
All of the solutions above seem not ideal to me. Are there any other ways to prevent creating a new handleSave function on each render for each Item, thus preventing unnecessary re-renders? If not, is there a preferred way to do this?
CodeSandbox: https://codesandbox.io/s/wonderful-tesla-9wcph?file=/src/App.js
The first question I'd like to ask : is it really a problem to re-render ?
You are right that react will re-call every render for every function you have here, but your DOM should not change that much it might not be a big deal.
If you have heavy calculation while rendering Item, then you can memoize the heavy calculations.
If you really want to optimize this code, I see different solutions here:
Simplest solution : change the ItemList to a class component, this way handleSave will be an instance method.
Use an external form library that should work fine: you have powerfull form libraries in final-form, formik or react-hook-form
Another external library : you can try recoiljs that has been build for this specific use-case
Wow this was fun! Hooks are very different then classes. I got it to work by changing your Item component.
const Item = React.memo(
function Item({ id, value, onChange, onSave }) {
console.log("render", id);
return (
<li>
<input
value={value}
onChange={event => onChange(id, event.target.value)}
/>
<button onClick={() => onSave(id)}>Save</button>
</li>
);
},
(prevProps, nextProps) => {
// console.log("PrevProps", prevProps);
// console.log("NextProps", nextProps);
return prevProps.value === nextProps.value;
}
);
By adding the second parameter to React.memo it only updates when the value prop changes. The docs here explain that this is the equivalent of shouldComponentUpdate in classes.
I am not an expert at Hooks so anyone who can confirm or deny my logic, please chime in and let me know but I think that the reason this needs to be done is because the two functions declared in the body of the ItemList component (handleChange and handleSave) are in fact changing on each render. So when the map is happening, it passes in new instances each time for handleChange and handleSave. The Item component detects them as changes and causes a render. By passing the second parameter you can control what the Item component is testing and only check for the value prop being different and ignore the onChange and onSave.
There might be a better Hooks way to do this but I am not sure how. I updated the code sample so you can see it working.
https://codesandbox.io/s/keen-roentgen-5f25f?file=/src/App.js
I've gained some new insights (thanks Dan), and I think I prefer something like this below. Sure it might look a bit complicated for such a simple hello world example, but for real world examples it might be a good fit.
Main changes:
Use a reducer + dispatch for keeping state. Not required, but to make it complete. Then we don't need useCallback for the onChange handler.
Pass down dispatch via context. Not required, but to make it complete. Otherwise just pass down dispatch.
Use an ItemWrapper (or Container) component. Adds an additional component to the tree, but provides value as the structure grows. It also reflects the situation we have: each item has a save functionality that requires the entire item. But the Item component itself does not. ItemWrapper might be seen as something like a save() provider in this scenario ItemWithSave.
To reflect a more real world scenario there is now also a "item is saving" state and the other id that's only used in the save() function.
The final code (also see: https://codesandbox.io/s/autumn-shape-k66wy?file=/src/App.js).
Intial state, items from server
const itemsFromServer = {
"1": {
id: "1",
otherIdForSavingOnly: "1-1",
value: "test",
isSaving: false
},
"2": {
id: "2",
otherIdForSavingOnly: "2-2",
value: "another row",
isSaving: false
}
};
A reducer to manage state
function reducer(currentItems, action) {
switch (action.type) {
case "SET_VALUE":
return {
...currentItems,
[action.id]: {
...currentItems[action.id],
value: action.value
}
};
case "START_SAVE":
return {
...currentItems,
[action.id]: {
...currentItems[action.id],
isSaving: true
}
};
case "STOP_SAVE":
return {
...currentItems,
[action.id]: {
...currentItems[action.id],
isSaving: false
}
};
default:
throw new Error();
}
}
Our ItemList to render all items from the server
export default function ItemList() {
const [items, dispatch] = useReducer(reducer, itemsFromServer);
return (
<ItemListDispatch.Provider value={dispatch}>
<ul>
{Object.values(items).map(item => (
<ItemWrapper key={item.id} item={item} />
))}
</ul>
</ItemListDispatch.Provider>
);
}
The main solution ItemWrapper or ItemWithSave
function ItemWrapper({ item }) {
const dispatch = useContext(ItemListDispatch);
const handleSave = useCallback(
// Could be extracted entirely
async function save() {
if (item.value.length < 5) {
alert("Incorrect length.");
return;
}
dispatch({ type: "START_SAVE", id: item.id });
// Save to API
// eg. this will use otherId that's not necessary for the Item component
await new Promise(resolve => setTimeout(resolve, 1000));
dispatch({ type: "STOP_SAVE", id: item.id });
},
[item, dispatch]
);
return (
<Item
id={item.id}
value={item.value}
isSaving={item.isSaving}
onSave={handleSave}
/>
);
}
Our Item
const Item = React.memo(function Item({ id, value, isSaving, onSave }) {
const dispatch = useContext(ItemListDispatch);
console.log("render", id);
if (isSaving) {
return <li>Saving...</li>;
}
function onChange(event) {
dispatch({ type: "SET_VALUE", id, value: event.target.value });
}
return (
<li>
<input value={value} onChange={onChange} />
<button onClick={onSave}>Save</button>
</li>
);
});

React: Get a list of tags by attribute values

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}/>
)
}

Toggling font awesome Icon using React

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>
)
}
}

Hide all but one tool tip

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.

Resources