I'm using React JSX with Hooks, and I have a problem. I'd like to change a p id of all the vector items when I click on one of them.
In particular:
I have a vector and for every item of this vector I call a component:
cards.map((item, index) => (<Card key={index} card={item} index={index} hover={hover} /> ))}
Inside the Card componet I have the div tag with onClick event: <div onClick={TitleTransition}>
The TitleTransition function changes a state of click: function TitleTransition() { setClick(true);}
This is the p tag inside the Card component: <p id={click ? "titleTransition" : ""}>{props.card.p1}</p>
Hence, when I click in one of these item (I have called them card) I'd like to change the p id for all the items (cards).
Anyone could help me? Thank you!
this is more a guess answer than anything else, but i'm unable to comment before 50 reputation.
If i had to guess, I'd say you are trying to change the styling onClick on a Card component and maybe additionally reset the styling of the other cards.
In that case you should try to use instead of the id prop of the p tag either the style or the className prop. I imagine you have a css file with something similar to
#titleTransition {
/* Your styling if element is clicked */
}
If you use the className prop, which is the React equivalent of the html class attribute, you have to change your css line to something like
.titleTransition {
/* Your styling if element is clicked */
}
or
:global(.titleTransition) {
/* Your styling if element is clicked */
}
or whatever way of styling you prefer in react, there are so many.
If you want to change all Card elements, onClick you will probably have to save the dataset for the cards in a state in their parent component (where the .map(...) is used to add the elements) and add an attribute to that data, such as 'isClicked'. Then define a function that takes the index of the and add the function to the props of the Card, like () and call that function in the Card by adding it in the . Then use it in the :
<div onClick={() => props.onChildClickChangeDataState(props.index)} />
This will trigger the function in the parent, where you can iterate over the dataset and change the isClicked attribute to the value you like.
To change the styling you then can use
<p className={props.item.isClicked ? 'titleTransition' : '' />
This an approach that helps you changing all at once as well as avoiding the invalid HTML #CertainPerformance mentioned.
I hope i guessed right and it helps you on the right track. It is hard to help you more at the moment. Maybe you could share the Code of you Card Parent Component, Card, your CSS file, and why you would like to change all id's at once. Then somebody might be able to help you a bit better.
Related
In my function I am using ref, which is binded to link, and when I try to change color (ref.style.color = 'red'), I see a error. Because ref which is binded to nuxt-link is Object, and it hasn't style property. I know that I can use the tag <a></a>, but does someone has ideas how can i make it with nuxt-link?
More info:
I have a link
<nuxt-link
ref="card"
#mousemove.native="move"
#mouseleave.native="leave"
#mouseover.native="over">
Click
</nuxt-link>
In my function i want to change link position, useng transform.
move () {
const card = this.$refs.card
card.style.transform = `perspective(500px)`
}
End i get this message in console
TypeError: Cannot set properties of undefined (setting 'transform')
By selecting nuxt-link using $refs will only return Vue Component instead of node element due to nuxt-link is a component in Nuxt.js.
The correct way to selecting node element is using $el.
Answer referred from here.
Example:
const card = this.$refs.card.$el
card.style.transform = `perspective(500px)`
To be mentioned, I'm not sure what you trying to achieve but assuming you want to modify the style of an element in Vue way, you are supposed to use :style="theElementStyles" then only you update the element style with this.theElementStyles = { transform: 'perspective(500px)' }.
More details about inline style binding can check on Vue documentation.
I have a SVG as a react component called AdultDog. My plan is to make all the tags in this react component that dont contain a certain number in the ID field disappear so that only tags with an id that contains a number are shown. i.e tags that have an ID 402 in it are shown. Ive tried to use document.querySelectorAll(":not([id^='402'])"); but this has two problems.
document.querySelectorAll grabs the whole html page not the AdultDog component, so how can i make it only select AdultDog?
with document.querySelectorAll(":not([id^='402'])"); it only selects ids called '402' but i want ids that have other words in it. i.e 402-tooth and 402-recede
import {ReactComponent as AdultDog} from '../../images/dog-dental-chart-with-treatments_v6.svg';
function DisplayTooth(props) {
const navigate = useNavigate();
console.log(props.toothnumber, 'Display tooth')
useEffect(() => {
var elements = document.querySelectorAll(":not([id^='tooth-402-GH-02'])");
console.log('elements', elements)
}, [props.toothnumber])
return (<div>
<div className='tooth-box'>
<AdultDog></AdultDog>
</div>
</div>
);
}
export default DisplayTooth;
This is quite a lot so any help would be appreciated! i cant find much documentation on SVGs as is.
Typically, you'd be creating all the <AdultDog> components, e.g. {dogs.map(dog => <AdultDogs key={dog.id} dog={dog}/>)} and you could just add a filter e.g. dogs.filter(your filter logic here).map() to drop some of the dogs.
Another approach is to add a class to some elements so you can toggle it on/off.
I have this class.
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
items: []
};
this.add = this.add.bind(this);
this.clear = this.clear.bind(this);
}
add() {
this.setState(prev => {
const n = prev.items.length;
return {
items: [<li key={n}>Hello, World {n}!</li>, ...prev.items]
};
});
}
clear() {
this.setState({ items: [] });
}
render() {
return (
<div>
<div>
<button onClick={this.add}>Add</button>
<button onClick={this.clear}>Clear</button>
</div>
{/* This is wrong, not sure what to do though... */}
<Collapse in={this.state.items.length > 0}>
<ul>{this.state.items}</ul>
</Collapse>
</div>
);
}
}
Sandbox link: https://codesandbox.io/s/material-demo-ggv04?file=/Demo.js
I'm trying to make it so that every time I click the "add" button, a new item gets animated into existence at the top of the list and the existing items get pushed down. Not sure how to proceed though.
Extra Resources
Example of what I'm trying to achieve: https://codeburst.io/yet-another-to-do-list-app-this-time-with-react-transition-group-7d2d1cdf37fd
React Transition Group Transition docs: http://reactcommunity.org/react-transition-group/transition (which seem to be used internally by Collapse)
I updated your Sandbox code to achieve what you wanted, but I don't think MaterialUI is the best library for that (I could be missing a better way to do it).
The challenge is that when you add a new item, that doesn't exist in the DOM yet. And most of those animation libraries/components require the element to be in the DOM and they just "hide" and "show" it with a transition time.
I had a similar situation and after some research, the better library I found that can handle animation for elements that are not yet in the DOM, was the Framer Motion. (You can check their documentation for mount animations)
Anyway, here is the link for the new Code Sandbox so you can take a look. The changes I made:
Removed random key
In the map function that creates your list using the <Collapse /> component, there was a function to get a random integer and assign that as a key to your component. React needs to have consistent keys to properly do its pretenders, so removing that random number fixes the issue where your "Toggle" button wasn't animating properly. (If your list of items doesn't have an unique ID, just use the index of the map function, which is not a good solution, but still better than random numbers).
<Collapse key={i} timeout={this.state.collapseTimeout} in={this.state.open}>
{it}
</Collapse>
Added a new function to control the toggle
The approach here was: add the item in your list and, after the element is in the DOM, close the <Collapse />, wait a little bit and open it again (so you can visually see the animation). In order to do that, we needed a new "toggle" function that can explicit set the value of the collapse.
toggleValue(value) {
this.setState(() => {
return {
open: value
};
});
}
Added a variable timeout for the collapse
The last issue was that, closing the <Collapse /> when the new item is added, was triggering the animation to close it. The solution here was to dynamically change the timeout of the collapse, so you don't see that.
setCollapseTimeout(value) {
this.setState(() => {
return {
collapseTimeout: value
};
});
}
When adding the element to the list, wait to trigger the animation
Again, to work around the issue with elements not yet in the DOM, we need to use a setTimeout or something to wait to toggle the <Collapse />. That was added in your add() function.
add() {
this.toggleValue(false);
this.setCollapseTimeout(0);
this.setState(prev => {
const n = prev.items.length;
return {
items: [<li key={n}>Hello, World {n}!</li>, ...prev.items]
};
});
setTimeout(() => {
this.setCollapseTimeout(300);
this.toggleValue(true);
}, 100);
}
Again, this is a hacky solution to make <Collapse /> from MaterialUI work with elements that are not yet in the DOM. But, as mentioned, there are other libraries better for that.
Good luck :)
Ended up here earlier on and then came back to create a sandbox showing hopefully a simple method for this scenario. The material-ui docs are a bit (lot) light in this area and I was fighting with a very similar situation, but I tried something with TransitionGroup from react-transition-group, crossed my fingers and it seemed to work.
Forked CodeSandbox with TransitionGroup
The gist is that you
wrap all of the components you want to transition in the <TransitionGroup> component
Inside the TransitionGroup, put in the "condition" (logic or loop output) for the data you want to render
Wrap the individual components you want to transition with transition component of your choice - <Collapse> in this example
e.g. In its most simple setup where "items" is an array of unique numbers coming from either props, state or a redux store
<TransitionGroup>
{items.map(item => (
<Collapse key={item}>
I am item {item}
</Collapse>
))}
</TransitionGroup>
With this setup I have found that I didn't need to put any props on the TransitionGroup or Collapse, and the TransitionGroup handled all the mounting and unmounting in the loop rendering. Material UI doesn't produce the lightest of HTML output, but I guess it's all rendered on the fly so maybe that makes it better (unless you have thousands of elements, then things start to drag).
You can even go a step further and wrap the whole thing in another TransitionGroup to cover situations where you want to remove the whole thing without transitioning all of the individual items - in this instance I switched it to a <Slide>. I was absolutely certain that this wouldn't work, but it seemed to not care. You can also try and be semantic and use the "component" property rather than wrapping in another element e.g.
{items.length > 0 && (
<TransitionGroup>
<Slide>
<TransitionGroup component="ul">
{items.map((item) => (
<Collapse component="li" key={item}>I am item {item}</Collapse>
))}
</TransitionGroup>
</Slide>
</TransitionGroup>
)}
I have changed the sandbox in the following ways
Included TransitionGroup from react-transition-group
Changed the "add" logic so that the components aren't part of the "items" array - the array only contains the data required to render the components
I have added a simple "count" and pushed that to the array to give the items a unique index (had originally used Math.random, but I wanted a "prettier" output). Generally your items will probably be coming from a database somewhere where a unique id will already be set.
Rendered the components in a loop based on the data in the array (this could be done in a separate function, but the gist is that the components aren't being stored in the array)
added a "delete" function to show the removal of single items
wrapped the whole group in a second <TransitionGroup> to show that the unmounting can happen in a group level
Put in some simple styling to get a better idea of the effect. You could use Material UI components here, but just wanted to keep it simple.
Hope this helps someone in the future.
UPDATE
Here's are some demos
contentEditable demo - requires double click for H1 to become editable
replace with input demo - adopts event.target styles but makes the UI 'twitch' when rendered
So I have some functional components, let's say:
component1.js
import React from 'react';
const component1 = props => (
<div>
<h1>Title</h1>
</div>
);
export { component1 };
They are variable. event.target could be anything with text, so paragraph, heading, anything. I'm trying to let users edit content inline by clicking on it, so I'll pass a function editMode to these functional components, that'll update parent state with editing info, let's say like this:
<h1 onClick={event => {editMode(event, props.name, props.title, 'title')}}>title</h1>
This changes parent local state to have all the necessary information to grab the value from redux, define a target etc. For this example, props.name is the name of the component, props.title is the value, and 'title' is object key in redux.
So I'll add something to my component1.js and make it look a bit like this:
import React from 'react';
const component1 = props => (
<div>
{props.editState === 'true' &&
<EditLayout
name={props.name}
target={props.target}
value={props.value}
onChange={event => someFunc(event)}
/>
}
<h1>Title</h1>
</div>
);
export { component1 };
Now this works fine, except it doesn't scale. EditLayout, in this case, will just return an input with correct value. What I need it to do is to adapt to whatever is being clicked, get font size, background, padding, margin, position. Am I doing this right? Every way I try, I run into huge issues:
Idea 1 - move EditLayout component outside of the functional component
Issue: positioning
So I'll move EditLayout to parent component that contains both component1.js and EditLayout. This will allow me to manipulate it from inside the functional component, without having to include it everywhere. I'll then grab coordinates and other important information from event.target like so:
const coords = event.target.getBoundingClientRect();
const offsetX = coords.left;
const offsetY = coords.top;
const childHeight = coords.height;
const childWidth = coords.width;
const childClass = event.target.className;
I'll then wrap the EditLayout to return a container which contains an input, and apply size/coordinates to the absolutely positioned container. This'll present an issue of input being offset by a random amount of pixels, depending on how big/where is the event.target.
Idea 2 - pass relevant computed styles to EditLayout
Issue: twitching on render, and I have to add EditLayout for every possible event.target there is, as well as condition its' render
So I'll grab all important computed styles like this:
const computedTarget = window.getComputedStyle(event.target);
const childMargins = computedTarget.marginBottom;
const childPaddings = computedTarget.padding;
const childFontSize = computedTarget.fontSize;
const childTextAlign = computedTarget.textAlign;
And pass it to component1.js, and then pass it to EditLayout component inside the component1.js. I'll then condition theevent.target to hide if it's being edited like this:
<h1 className={ props.target === 'title' ? 'd-none' : ''}>Title</h1>
And condition the EditLayout to show only if it's needed:
{props.target === 'title' && <EditLayout />}
In this example, clicking h1 will show the input, but the layout itself with twitch on render. Input will have the exact same margin and font size as the h1, or event.target, but it'll appear bigger and extend the layout. Demo:
Idea 3 - Use conditional contentEditable
Issue: Requires double click to enable, doesn't work in safari, doesn't let me preselect the value
This is the weirdest of them all. I figured it'd be pretty simple, do something like this inside the functional component render:
<h1 contentEditable={props.target === 'title'} onClick={event => props.setTarget(event)}>Title</h1>
However, I have to double click to enable it. I have no idea why, if I attach a console log every time onClick is fired, I'll get correct outputs, I'll get the correct target value as well. I've tried numerous ways, but it simply requires double click. Even attempted to handle this inside the functional component, as most of the stuff is handled by a parent component, doesn't make a difference.
I have oversimplified the examples, so it's safe to assume/understand the following:
I am passing props in a correct fashion, they aren't undefined
I am using bootstrap
I am using styled components, and EditLayout is a styled component
which accepts props and turns them into CSS like: font-size: ${props
=> props.fontSize};
The values should be correct, I am not manipulating anything I get back from getComputedStyle() or getBoundingClientRect()
I am keen on keeping my functional components functional, and easy to
add. Functional components, in this case, are simple HTML structures,
and I'd like to keep them as simple as possible
So there's a neat solution to contentEditable requiring two clicks instead of one, instead of binding onClick and passing it to enable contentEditable, simply keep contentEditable true and handle the change however you like. Here's a working h1 that doesn't require two clicks to enable contentEditable, unlike the one in the demo
<h1
className="display-4 text-center"
contentEditable
suppressContentEditableWarning
onBlur={event => updateValues(event)}
>
Title
</h1>
The available methods for trigger update could be onBlur or onInput.
I seem to be having some issues in regards to styling the components without using a theme. I just want to change a couple of colors without needing to create a new theme per element.
In this case, I just want to change the color of the bar to a brownish color and right now I have an input class as follows:
import style from './style.scss'
const TextInput = (props) => {
<Input className={style.textInput} {...props} />
}
And in my style.scss file:
.textInput {
.bar {
background-color: #663300;
}
}
Any help would be appreciated.
ClassName doesn't work like that.
You can't pass a Css style to a className, that is wrong.
Either pass the class names you want to apply as a string (in your case I guess it would be className="textInput bar") or you can create a className with classNames library (in any case the final result will be a string).
Just make sure you're styles are included in the page that the component is going to render and react will be smart enough to render the correct css class for each component.
As you can check in here ClassName is a string