I am trying to test a library wrapper component, which generates it's own markup rendered in componentDidMount. Given the following...
// <MyComponent />
componentDidMount() {
transform(this.ref);
}
render() {
return (
<div className='foo' ref={(self) => this.ref = self} />
)
}
where (external lib) transform does something to alter the rendered markup. Assume this to be transformed to the following...
<div class="foo">
<article>
<h2>noms</h2>
<section>
<ul class="list">
<li>pizza</li>
<li>taco</li>
</ul>
</section>
</article>
</div>
How do I actually use the Enzyme API on the rendered markup?
I am trying to mount the component, then to find my .list element, but the result is never actually found with a length of 0. What is wrong with my following test?
let wrapper = Enzyme.mount(<MyComponent />);
let list = wrapper.find('.list'); // nope
I believe my basic setup to be correct, as calling wrapper.html() does actually return the above transformed markup in full. What am I missing here?
Since wrapper is your component, and ref is a property of your component that points to the DIV, this should work:
let wrapper = Enzyme.mount(<MyComponent />);
let list = wrapper.instance().ref;
Related
I am using Preact with hooks. I have following button component:
export function Button(props) {
return (
<button class={props.class}>{props.children}</button>
);
}
I have another parent component where I need to access actual DOM element button for animation purpose.
export function Parent(props) {
const buttonElm = useRef(null);
useEffect(() => {
console.log(buttonElm.current);
// Animate button using popmotion or similar
});
return (
<div>
<Button ref={buttonElm}>Click me to animate</Button>
</div>
);
}
However, there is a problem. The buttonElm.current points to JSX object i.e. Button but not the DOM element button. I need buttonElm to point to actual DOM element. How do I do that?
Should I go ahead and use buttonElm.current.base property? But that does not feel idiomatic with hooks.
Also, I have two questions.
How does ref behave when I am setting it on a Preact component that returns multiple elements using <Fragment />.
Second, is accessing the children's DOM element for animation purpose acceptable/correct practice in Preact/React? (I can wrap my component in another wrapper div but that causes more animation headaches than solving the problem)
You need to pass ref as props to your child component. By doing this buttonElm will point to actual Button DOM element.
export function Button(props) {
return (
<button class={props.class} ref={props.buttonElm}>{props.children}</button>
);
}
export function Parent(props) {
const buttonElm = useRef(null);
useEffect(() => {
console.log(buttonElm.current);
// Animate button using popmotion or similar
});
return (
<div>
<Button buttonElm={buttonElm}>Click me to animate</Button>
</div>
);
}
Essentially I have a web component "x" and I dynamically create a form component inside the "x" which will be appended to "x".
I could just do it in the place I create "x", after creating "x", of course.
Basically this:
class X extends LitElement {
render() {
return html`
<div>
<slot name="form-component">${this.appendFormComponent()}</slot>
</div>
<slot></slot>
`
}
appendFormComponent() {
const formComponent = document.createElement('input')
formComponent.slot = "form-component"
this.append(formComponent)
}
// side note, is running this append inside the render function a terrible
// idea and where should I do it instead? I mean doing it in the render
// function does appear to work...
}
As you suspected, this is definitely a terrible idea because you are mixing imperative paradigm with declarative paradigm. However, if you really need to do this and since you are using LitElement, you can nicely abstract the declarative and imperative UI code using appropriate lifecycle methods:
class X extends LitElement {
render() {
return html`
<div>
<slot name='form-component'></slot>
</div>
<slot></slot>
`;
}
// Executed only once
firstUpdated() {
const formComponent = document.createElement('input');
formComponent.slot = 'form-component';
this.append(formComponent);
}
}
Also, the approach you are attempting is probably problematic. Your problem would be easily solved by render function only:
class X extends LitElement {
render() {
return html`
<div>
<slot name='form-component'>
<!-- Notice the use of INPUT TAG here -->
<input type='text' />
</slot>
</div>
<slot></slot>
`;
}
}
Using something like firstUpdated with document.createElement should be used to create UI components which have offset elements that break the UI as Function of State notion. Such components are date pickers, multi select dropdown, dialog boxes, etc. which directly append DOM elements to the body for managing Z-index and fixed positioning accurately.
Further, as per your comments, if you have a dynamic function which needs to be assigned to the input text, simply create a wrapper function like:
class X extends LitElement {
// Input change event handler
onChange() {
// A guard to check presence of dynamic function
if (this.someDynamicFuction) {
this.someDynamicFuction();
}
}
render() {
return html`
<div>
<slot name='form-component'>
<!-- Notice the use of INPUT TAG here -->
<input type='text' #change=${this.onChange} />
</slot>
</div>
<slot></slot>
`;
}
}
I display a list of foos and when i click on some link more results i keep the existing foos and i append to them the new ones from my api like bellow
const [foos, setFoos] = useState([]);
...
// api call with axios
...
success: (data) => {
setFoos([ ...foos, ...data ])
},
Each <Foo /> component run the animation above
App.js
...
<div className="foos-results">
{ foos.map((foo, index) => <Foo {...{ foo, index }} key={foo.id}/>) }
</div>
...
Foo.js
const Foo = ({ foo, index }) => <div className="circle">...</div>
animation.css
.circle {
...
animation: progress .5s ease-out forwards;
}
The problem is when i append the new ones then the animation is triggered for all the lines of <Foo />.
The behavior expected is that the animation is triggered just for the new ones and not starting over with the existing ones too.
UPDATE
We have found the origin of the problem (it's not related to the uniqueness of key={foo.id})
if we change
const Foo = ({ foo, index }) => <div className="circle">...</div>
to
const renderFoo = ({ foo, index }) => <div className="circle">...</div>
And App.js to
...
<div className="foos-results">
{ foos.map((foo, index) => renderFoo({ foo, index })) }
</div>
...
It works
So why is this behavior like this in react ?
here is a sandbox based on #Jackyef code
This is quite an interesting one.
Let's look at the sandbox provided in the question.
Inside App, we can see this.
const renderItems = () => (
<div>
{items.map((item, index) => (
<div className="item" key={item.id}>
<span>
{index + 1}. {item.value}
</span>
</div>
))}
</div>
);
const Items = () => renderItems();
return (
<div className="App">
<h1>List of items</h1>
<button onClick={addItem}>Add new item</button>
<Items />
</div>
);
Seems pretty harmless right? The problem with this is that Items is declared in the App render function. This means that on each render, Items actually is now a different function, even though what it does is the same.
<Items /> is transpiled into React.createElement, and when diffing, React takes into account each components' referential equality to decide whether or not it is the same component as previous render. If it's not the same, React will think it's a different component, and if it's different, it will just create and mount a new component. This is why you are seeing the animation being played again.
If you declare Items component outside of App like this:
const Items = ({ items }) => (
<div>
{items.map((item, index) => (
<div className="item" key={item.id}>
<span>
{index + 1}. {item.value}
</span>
</div>
))}
</div>
);
function App() { /* App render function */}
You will see everything works as expected. Sandbox here
So, to summarise:
Referential equality matters to React when diffing
Components (function or class that returns JSX) should be stable. If they change between renders, React will have a hard time due to point number 1.
I don't think there is a way to disable this re-rendering animation, but I think there is a workaround that could solve this issue.
As we know that each div's css is reloaded every time, so the solution I can think of, is to create another css class rule (let this class be named 'circle_without_anim') with same css as class 'circle' but without that animation and while appending new div, just before appending change class of all divs that have class name 'circle' to 'circle_without_anim' that would make the changes and css to previous divs but just without that animation and the append this new div with class 'circle' making it the only div that have animation.
Formally the algorithm will be like:
Write another css class(different name for example prev_circle) with same rules as 'circle' but without the animation rule.
In Javascript just before appending new div with class 'circle', change class of all previous divs that have class named 'circle' to newly created class 'prev_circle' that do not have animation rule.
Append the new div with class 'circle'.
Result: It would give an illusion that the CSS of previous divs is not being reloaded as the css is same but without animation, but the new div has different css rule (animation rule) which is going to be reloaded.
With this code:
const Items = () => renderItems();
...
<Items />
React has no chance of knowing that Items in the current render is the same component as Items in the previous render.
Consider this:
A = () => renderItems()
B = () => renderItems()
A and B are different components, so if you have <B /> in the current render and <A /> instead of <B /> in the previous render, React will discard the subtree rendered by <A /> and render it again.
You are invoking React.createElement (since <Items /> is just a JSX syntax sugar for React.createElement(Items, ...)) every render, so React scraps the old <Items /> in the DOM tree and creates it again each time.
Check out this question for more details.
There are two solutions:
create Items component outside of the render function (as Jackyef suggested)
use render function ({ renderItems() } instead of <Items />)
I have a tree structure with a root "Tree" component that has a list of root "TreeNodes", then TreeNodes can have an arbitrary number of children.
So inside of the TreeNode render method I have
childrenHTML = this.state.children.map((child) => {
return (<TreeNode nodeClick ={this.props.nodeClick} parentNode={this}
key={child.childId} node={child} level={this.state.level+1} />);
});
and
const { isDragging, connectDragSource, connectDragPreview} = this.props;
Then the final return for the render method looks like
return connectDragSource(
<div>
<div style={nodeStyle}>
{connectDragPreview(
<div className = {"nodeContainer" + ' ' + this.state.nodeHover} onMouseLeave={this.nodeUnHover} onMouseOver={this.nodeHover} onClick={()=>this.props.nodeClick(this)}>
<img alt = {this.state.titleIcon} className = "titleIcon" src = {Connections.getImageURLByName(this.state.titleIcon)} />
<p className="nodeLabel"> {this.state.nodeName}</p>
{nodeLabelsHTML}
<DescriptiveIcons descriptiveIcons={this.state.icons} />
</div>
)}
</div>
{childrenHTML}
</div>
);
I am exporting:
export default DragSource(DragTypes.STRUCTURE, treeNodeSource, collect)(TreeNode);
Then in the parent Tree file I am exporting
export default DragDropContext(HTML5Backend)(Tree)
and rendering the rootnodes like
rootNodesHTML = rootNodes.map((node) => {
return <TreeNode nodeClick={this.props.nodeClick} key={node.childId} node={node} level={0}/>
});
...
return (
<div className="treeContainer">
<div className="wrapContainer">
{rootNodesHTML}
</div>
</div>
);
This works great but only for the rootnodes, when I try to render the children (the childrenHTML variable is only populated after the parent is clicked on) I get the following error:TypeError: connectDragPreview is not a function
Leading me to believe that those react-dnd props that come from the "collect" function is not being passed to the rootnodes but not the children. It seems like it should to me because the same code should be executed for the parents as for the children as its the same class... really stuck here.
I am relatively new to react, and new to ideas like HOCs so all tips or suggestions are appreciated. Thank you!
I was able to get this working. Check out the example posted at the end of the thread in
https://github.com/react-dnd/react-dnd/issues/332.
Ultimately the solution was to wrap the TreeNode in a "DragContainer" with a very simple render method
render(){
const {...props} = this.props;
return <TreeNode {...props}/>
}
Then in the TreeNode render method, when rendering the child nodes render a DragContainer instead, passing in all the usual props.
childrenHTML = this.state.children.map((child) => {
return <DragNodeContainer modalFunctions = {this.props.modalFunctions} nodeClick ={this.props.nodeClick} parentNode={this} key={child.childId} node={child} level={this.state.level+1} />;
});
I am still unsure as to the technical reason for this, however, the fix seems to work for other people and it works for me!
I am trying to print props of a react component but getting an error. Please help:
Snippet:
<!-- DOCTYPE HTML -->
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react-dom.min.js"></script>
<script src="http://fb. me/JSXTransformer-0.12.1.js"></script>
<!-- gap above is intended as else stackOverflow not allowing to post -->
</head>
<body>
<div id="div1"></div>
<script type="text/jsx">
//A component
var George = React.createClass({
render: function(){
return (
<div> Hello Dear!</div>
<div>{this.props.color}</div>
);
}
});
ReactDOM.render(<George color="blue"/>, document.getElementById('div1'));
</script>
</body>
</html>
I am expecting "Hello Dear!" and then next line "blue". But, I am getting this error instead.
Error:
React v16 and later
As of React v16 React components can return an array. This was not possible prior to v16.
Doing this is simple:
return ([ // <-- note the array notation
<div key={0}> Hello Dear!</div>,
<div key={1}>{this.props.color}</div>
]);
Note that you need to declare a key for each element of the array. According to official sources, this might become unnecessary in future versions of React, but not as of right now. Also don't forget to separate each element in the array with , as you would normally with an array.
React v15.6 and earlier
React Components can only return one expression, but you are trying to return two <div> elements.
Don't forget that the render() function is exactly that, a function. Functions always take in a number of parameters and always return exactly one value (unless void).
It's easy to forget, but you're writing JSX and not HTML. JSX is just a syntactic sugar for javascript. So one element would be translated as:
React.createElement('div', null, 'Hello Dear!');
This gives a React element, which you can return from your render() function, but you cannot return two individually. Instead you wrap them in another element which have these divs as children.
From the official docs:
Caveat:
Components must return a single root element. This is why we added a <div> to contain all the <Welcome /> elements.
Try wrapping these components in another component so that you only return one:
//A component
var George = React.createClass({
render: function(){
return (
<div>
<div> Hello Dear!</div>
<div>{this.props.color}</div>
</div>
);
}
});
ReactDOM.render(<George color="blue"/>, document.getElementById('div1'));
With React 16 we can return multiple components from render as an array (without a parent div).
return ([
<div> Hello Dear!</div>,
<div>{this.props.color}</div>
]);
Issue is you are returning more than one html element from render method, here:
return (
<div> Hello Dear!</div>
<div>{this.props.color}</div>
);
React v16+ solution:
React 16 included a new element React.Fragment, by help of that we can wrap multiple elements, and no dom node will be created for Fragment. Like this:
return (
<React.Fragment>
Hello Dear!
<div>{this.props.color}</div>
</React.Fragment>
);
or return an array:
return ([
<p key={0}>Hello Dear!</p>
<div key={1}>{this.props.color}</div>
]);
React v < 16:
Wrap all the elements in a wrapper div, like this:
return (
<div>
Hello Dear!
<div>{this.props.color}</div>
</div>
);
Reason: A React component can't return multiple elements, but a single JSX expression can have multiple children, You can only return one node, so if you have, a list of divs to return, you must wrap your components within a div, span or any other component.
One more thing, you need to include the reference of babel also, use this reference in header:
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.24.0/babel.js"></script>
Check the working example:
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.24.0/babel.js"></script>
<!-- gap above is intended as else stackOverflow not allowing to post -->
</head>
<body>
<div id="div1"></div>
<script type="text/jsx">
var George = React.createClass({
render: function(){
return (
<div> Hello Dear!
<div>{this.props.color}</div>
</div>
);
}
});
ReactDOM.render(<George color="blue"/>, document.getElementById('div1'));
</script>
</body>
</html>
Wrap your returning DOM in a single html element.
Try this
return (
<div>
<div> Hello Dear!</div>
<div>{this.props.color}</div>
</div>
);
return ( <div>
<div> Hello Dear!</div>
<div>{this.props.color}</div>
</div>
);
Hi, elements inside return should be wrapped by something. Just add as shown above and should work ;)
The Render function should only return one root element try this
//A component
var George = React.createClass({
render: function(){
return (
<div>
<div> Hello Dear!</div>
<div>{this.props.color}</div>
</div>
);
}
});
Enclose everything you are using in return statement inside another div tag.
render: function(){
return (
<div>
<div> Hello Dear!</div>
<div>{this.props.color}</div>
</div>
);
}
In fact your problem is that you try to render several elements at the same time what is not possible in this version of react,
reason
render it is a function and by nature a function returns only one value
but with react-fiber you can do what you do, to correct your problem there are two solutions :
Either use a wrapper for both of your elements
var George = React.createClass ({
render: function () {
return (
<div>
<div> Hello Dear! </div>
<div> {this.props.color} </div>
<div>
);
}
});
ReactDOM.render(<George color = "blue" />, document.getElementById ('div1'));
The second solution is to return a array with both of your elements
var George = React.createClass ({
render: function () {
return ([
<div key='0'> Hello Dear! </div>,
<div key='1'> {this.props.color} </ div>
]);
}
});
ReactDOM.render (<George color = "blue" />, document.getElementById ('div1'));