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!
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>
);
}
Just starting off with ReactJS and have a project where I am showing an accordion of issues and including a details area that is hidden on the start.
There is a button in the accordion bar that should pass a prop to the child element to hide or show them. I have refs on the button and on the details child compoment and added a function to call the function and pass the ref of the details area. I am just not sure how to dynamically change the class hidden on one of many areas and not all of them.
Not sure if putting a class on each element and then learning how to toggle the particular child's class is better or changing the prop to the child.
I can get to the change function but am drawing a blank from there and all the googling shows how to do one element with a grand change of state but I need individual elements.
Here is what I have so far.
Parent
...
<AccordionItem key={item.id} className={iconClass} title={`${item.area}`} expanded={item === 1}>
{
item.issues.map(issue => {
let trim = (issue.issue.length>21) ? `${issue.issue.substring(0,22)}...`: issue.issue;
return (
<div className="issue-bar container-fluid">
<div className="row issue-bar-row">
<span className="issue-title"><img src={CriticalRed} alt="Critical"/> {trim}</span>
<span className="btns">
<button className="btn btn-details" onClick={() => this.showDetail(`details-${issue.id}`)}>Details</button>
</span>
</div>
<IssuesDetails ref={`details-${issue.id}`} issue={issue} shouldHide={true} />
</div>
)
})
}
<div>
</div>
</AccordionItem>
...
Child
export default class IssuesDetails extends Component{
render(){
let issueDetails = classNames( 'issue-details', { hidden: this.props.shouldHide } )
return(
<div className={issueDetails}>
<div className="issues-details-title">
<h3>{this.props.issue.issue}</h3>
</div>
<div className="issues-details-details">
{this.props.issue.details}
</div>
<div className="issues-details-gallery">
<ImageGallery source={this.props.issue.photos} showPlayButton={false} useBrowserFullscreen={false} />
</div>
<button className="btn btn-success">Resolve</button>
</div>
)
}
}
Thanks for any help you provide or places you can send me!
If i'm understanding correctly, you need to be able to swap out shouldHide={true} in certain circumstances. To do this, you'll want your parent component to have a state object which indicates whether they should be hidden or not.
Exactly what this state object looks like depends on what sort of data you're working with. If the issues is a single array, then perhaps the state could be an array of booleans indicating whether each issue is expanded or not. I suspect you may have a more nested data structure, but i can't tell exactly since some of the code was omitted.
So assuming you have an array, it might look like this (i've omitted some things from the render method for brevity):
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
hidden: (new Array(props.issues.length)).fill(false),
};
}
showDetail(index) {
let newHidden = this.state.hidden.slice();
newHidden[index] = true;
this.setState({
hidden: newHidden
});
}
render() {
return (
<AccordionItem>
{this.props.issues.map((issue, index) => {
<div>
<button onClick={() => this.showDetail(index))}/>
<IssuesDetails issue={issue} shouldHide={this.state.hidden[index]}/>
</div>
})}
</AccordionItem>
);
}
}
Take a look at these:
https://codepen.io/JanickFischr/pen/xWEZOG
style={{display: this.props.display}}
I think it will help with your problem. If you need more information, please just ask.
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;
In the following example WrapperComp needs to get access to the dom node of the divs in line 5 and line 8, without adding logic to PageComp or ItemComp. The only things I could change in PageComp are the div tags. E.g. I could add a ref, prop, data-attribute, etc to them.
The divs don't have to be created inside PageComp. WrapperComp would be allowed to create them too, but they must wrap each of its children (In this case each ItemComp).
Example
class PageComp extends React.Component {
render() {
return (
<WrapperComp>
<div>
<ItemComp/>
</div>
<div>
<ItemComp/>
</div>
</WrapperComp>
);
}
}
class WrapperComp extends React.Component {
render() {
return (
<div>
<h1>A wrapper</h1>
{this.props.children}
</div>
);
}
}
class ItemComp extends React.Component {
render() {
return (
<div>
<h1>An item</h1>
</div>
);
}
}
ReactDOM.render(
<PageComp/>,
document.getElementById('app')
);
JSBIN
What I tried so far:
I already tried to put a ref= on the divs, but that ref would only be available in PageComp not in WrapperComp.
I also tried to create the divs inside WrapperComp and put a ref= on them from there, but that would result in a Refs Must Have Owner Warning
Now I wonder.. what would be an appropriate way in react to solve that problem?
Till now the only solution that came to my mind was to put a data-attribute on each div and search the dom for them after componentDidMount like that: document.querySelectorAll('[data-xxx]'). Perhaps I'm not sure if this is how you do it in react..
Why do I want to get the node inside WrapperComp?
I want to create a component that adjusts the dimensions of its children. In the example that component would be WrapperComp. The adjustments can only be done after the children rendered to the dom, e.g. to get clientHeight.
If you don't restrict that this needs to be solved by how one should get the DOM, pass them down, etc, I would get rid of the puzzle and approach it in a different direction.
Since you are not given much control to <PageComp> whereas <WrapperComp> seems flexible, I would do the wrapping in the later by transforming the passed children to what you need them to be.
class PageComp extends React.Component {
render() {
return (
<WrapperComp>
<ItemComp/>
<ItemComp/>
</WrapperComp>
);
}
}
class WrapperComp extends React.Component {
render() {
const wrappedChldren = React.Children.map(this.props.children, function(child) {
return (
<div ref={function(div) {
this.setState{clientHeight: div.clientHeight}
}}>
<h1>A wrapper</h1>
{ child }
</div>
);
});
return <div>{ wrappedChildren }</div>;
}
}
With this concentrate can be put on the transformation in the <WrapperComp>, which is pretty intuitive as its name suggests.
In the following example this.refs.foo.clientWidth returns undefined and I can't figure out why. How can I get the reference to SomeComp in PageComp to use its width? (using React 15.2.1 or similar)
class PageComp extends React.Component {
componentDidMount() {
console.log(this.refs.foo.clientWidth);
}
render() {
return (
<div>
<p>{this.props.name}</p>
<SomeComp ref="foo" />
</div>
);
}
}
class SomeComp extends React.Component {
render() {
return (
<div>
<h1>I loaded</h1>
</div>
);
}
}
ReactDOM.render(
<PageComp name="Joe Schmoe"/>,
document.getElementById('react_example')
);
JSBIN
this.refs.foo returns React Element.
But if you want to work with DOM element - you need to find this Node
React 15.0.1 Requires this syntax: ReactDOM.findDOMNode(this.refs.foo)
JSBIN:
http://jsbin.com/xabidaquti/1/edit?html,js,console,output
Try like this ReactDOM.findDOMNode(this.refs["foo"].clientWidth)
For better clarity, this.refs.foo.clientWidth will work if the refs is set to html elements like div, input and so on. As refs by itself will return the DOM Node in case of such html elements.
If the refs is set to some React components, then we can access the DOM Node of the Components only using ReactDom.findDOMNode(this.refs.foo) and hence we need to get the clientWidth using ReactDom.findDOMNode(this.refs.foo).clientWidth.
Ref