Observer does not work for recursive items - javascript

I'm really stuck with the problem. I have a nested menu. After click on the first menu level second level items shows up. After click on one of the children nothing happens. Looks like the observer don't work for deep levels items. I don't know if the "late" type could be a problem. Everything is described in the code:
https://codesandbox.io/s/mobx-state-tree-recursive-deep-menu-p7eqj
Thanks in advance

The problem is that when you are using your recursive TreeItem component it is not actually observer. You wrapping it in observer only when exporting, but TreeItem inside TreeItem is regular component, not observer so it doesn't react to anything.
Basically you just need to move observer decorator:
// Use `observer` right here straight away
const TreeItem = observer((props) => {
const handleClick = () => {
props.item.active ? props.item.deactivate() : props.item.activate();
};
const ItemChildren = ({ children }) => {
return (
<ul>
{children.map((child) => (
<TreeItem key={child.id} item={child} />
))}
</ul>
);
};
return (
<React.Fragment>
<li onClick={handleClick}>{props.item.id}</li>
{props.item.active && (
<ItemChildren children={values(props.item.children)} />
)}
</React.Fragment>
);
});
// Remove it from here
export default TreeItem;
Codesandbox

Related

Is my entire React component re-rendering unnecessarily when the state changes?

I am trying to create an accordion component using React, but the animation is not working.
The basic idea is, I believe, pretty standard, I am giving each item body a max-height of 0 which is affected by adding a show class to an element. I am able to select and show the item I want, but the animation to slide in/out is not working.
With the Chrome dev tools open, when I click on one of the items I can see that the whole "accordion" element is flashing, which leads me to believe that the whole element is being re-rendered. But I am unsure why this would be the case.
Here is the relevant Accordion component:
import React, { useState } from "react";
const Accordion = ({ items }) => {
const [selectedItem, setSelectedItem] = useState(0);
const AccordionItem = ({ item, index }) => {
const isOpen = index === selectedItem;
return (
<div className="accordion-item">
<div
onClick={() => {
setSelectedItem(index);
}}
className="accordion-header"
>
<div>{item.heading}</div>
</div>
<div className={`accordion-body ${isOpen ? "show" : ""}`}>
<div className="accordion-content">{item.body}</div>
</div>
</div>
);
};
return (
<div className="accordion">
{items.map((item, i) => {
return <AccordionItem key={i} item={item} index={i} />;
})}
</div>
);
};
export default Accordion;
And here is a codepen illustrating the problem:
https://codesandbox.io/s/heuristic-heyrovsky-xgcbe
of course, its going to re-render. When ever you call setSelectedIem, state changes and hence react re-renders on state change to exhibit that change.
Now if you place this
const [selectedItem, setSelectedItem] = useState(0);
inside Accordion Item, it would just re-render accordion item, but would mess up your functionality.

How can I pass a prop by child index in React

I'm trying to develop a generic container for React, that would work like this:
<PanelContainer>
<PanelConsole />
<PanelMemory />
<PanelLog />
</PanelContainer>
I want to dynamically create a tab system within the container, this works as follows:
renderTabs = () => {
return (
<ul className="panel_tabs">
{React.Children.map(this.props.children, (child, i) =>
<li key={child.type.display_name} onClick={() => this.handleClickTab(i)}>
{child.type.display_name}
</li>
)}
</ul>
);
}
This allows me to render the tabs with the display_name property within the class. This so far works, but now I'm trying to get the click to work. I want it to work dynamically so I don't have to build specialized containers for each instance of the panel. I'd ideally like to set the property of a child in this.props.children by index, so for example:
this.props.children[0].props.shown = false;
Is this possible?
I think React.Children.map and React.cloneElement works for you:
render() {
const { children } = this.props;
const tabs = this._renderTabs();
const childrenWithProps = React.Children.map(children, (child, id) =>
React.cloneElement(child, { shown: this.state.shows[i] }));
return (
<div>
<div>{tabs}</div>
<div>{childrenWithProps}</div>
</div>
)
}

React: Mapping children of a parent component

So I want to add certain styles to any child that's appended to a component. Let's say the parent component is called Section and children are called Cardin this case. in Section.js I am trying this: -
renderChildren = () =>{
return React.Children.map(this.props.children, (child, i)=>{
let el = React.cloneElement(child,{
style: {opacity:0.5}
})
return el
})
}
render(){
<ScrollView>
{this.renderChildren()}
</ScrollView>
}
The above approach doesn't work for me. And I would like to know why. Also is there a way where I could map across the children and wrap them in a new component? Something like this;
this.props.children.map(Child => <Wrapper> <Child/> </Wrapper> )
To wrap your children into a wrapper just put the call to React.Children.map into the wrapper component:
const OpaqueWrapper = ({ children }) => (
// check that children is defined
// if you do not want your wrapper to be rendered empty
children && (
<Wrapper>
{React.Children.map(children, child => (
React.cloneElement(child, {style: {...child.props.style, opacity: 0.5}})
))}
</Wrapper>
)
);
Also note that you have to merge the styles provided to the original child with the styles injected or you will lose the ability to style the children at all.
See this codesandbox for a working example.
As to why it did not work in your code: Are you sure that your <Card> component does handle the style prop correctly, i.e. applying it to it's children?
EDIT:
The sloution wraps all children components in a single wrapper, but I
would like to wrap each child with the applied wrapper , as shown in
my question.
The just move the wrapper into React.Children.map:
const OpaqueWrapper = ({ children }) => (
React.Children.map(children, child => (
<Wrapper>
{React.cloneElement(child, {style: {...child.props.style, opacity: 0.5}})}
</Wrapper>
)))
);
I think this solution is the simplest for wrap every child. When the children are rendered, you receive an instance of the component, not the component function. And you just need to wrap the instance into the wrapper component as shown below.
this.props.children.map(child => <Wrapper>{child}</Wrapper> )
For TypeScript:
React.Children.map(props.children, child => {
return <Wrapper>{child}</Wrapper>
})
And here the Typescript version when you write properties:
const mapped = Children.map(children, (child, index) => {
if(React.isValidElement(child)) {
return React.cloneElement(child, {
...child.props,
isFirst: index === 0,
isLast: !Array.isArray(children) || index === children.length - 1,
})
}
return null
})
Another variant for TypeScript which I think is clean:
const ChildrenWithProps = Children.map(children, child =>
cloneElement(child as JSX.Element, props),
)
used like:
return (
<div>
{ChildrenWithProps}
</div>
);
Of course, you need to know beforehand that what is passed as children definitely will be a valid child element, or you need to check it with isValidElement as previous answers suggested.

How to measure a rows height in react-virtualized list

I'm a little uncertain as how to achieve dynamic heights of a List using react-virtualized.
I have a component as follows:
import { List } from 'react-virtualized';
<List
height={400}
rowCount={_.size(messages)}
rowHeight={(index) => {
return 100; // This needs to measure the dom.
}}
rowRenderer={({ key, index, style }) => <Message style={style} {...messages[index]} />}}
width={300}
/>
I have looked at using CellMeasurer as per the docs which says it can be used with the List component but I have no idea how this example actually works...
I've also tried to work out how it has been achieved in the demo code but have also reached a dead end.
Can someone please assist me on how I would measure the DOM to get each items height dynamically.
Sorry you found the docs to be confusing. I will try to update them to be clearer. Hopefully this will help:
import { CellMeasurer, List } from 'react-virtualized';
function renderList (listProps) {
return (
<CellMeasurer
cellRenderer={
// CellMeasurer expects to work with a Grid
// But your rowRenderer was written for a List
// The only difference is the named parameter they
// So map the Grid params (eg rowIndex) to List params (eg index)
({ rowIndex, ...rest }) => listProps.cellRenderer({ index: rowIndex, ...rest })
}
columnCount={1}
rowCount={listProps.rowCount}
width={listProps.width}
>
{({ getRowHeight, setRef }) => (
<List
{...listProps}
ref={setRef}
rowHeight={getRowHeight}
/>
)}
</CellMeasurer>
)
}
There are also demos of this component here showing how it's used.

React props.children is not array

According to the react docs, if a component has multiple children, this.props.children should be an array.
I have the following component:
export class Two extends React.Component {
componentDidMount() {
console.log(Array.isArray(this.props.children)); // false
}
render() {
return(
<div>
{this.props.children}
</div>
);
}
};
Which I pass children to in another component's render() method:
<Two>
<Img src="/photos/tomato.jpg"/>
<Img src="/photos/tomato.jpg"/>
</Two>
Why is this.props.children not an array? More importantly, how can I get it to be one?
Found a better solution to this after some digging in the React.Children source. It looks like a .toArray() method has been added in React 0.14, soon to be released.
Once it is out we will be able to simply do something like this:
let children = React.Children.toArray(this.props.children);
It's documented in https://reactjs.org/docs/react-api.html#reactchildren
I found this solution. It will render all children, one or more.
const BigMama = ({ children, styles, className }) => {
return (
<div
styles={{styles}}
className={(className ? className : '')}
>
{
React.Children.map(children, (child) =>
<React.Fragment>{child}</React.Fragment>)
}
</div>)
}
<BigMama
styles={{border: 'solid groove'}}
className='bass-player'
>
<h1>Foo</h1>
<h2>Bar</h2>
<h3>Baz</h3>
<h4>Impossibru!</h4>
<BigMama>
If you want to do something with the results (eg. render the array in a list), this is how you can use children. The React docs talk about how the map function works with children, as it's an opaque structure.
export const List = ({ children }) => {
return (
<ul>
{React.Children.map(children, child => <li>{child}</li>)}
</ul>
)
}
So then you can call
<List>
{item.name}
{item.price}
</List>
There are many ways and some are mentioned above already but if you wanna keep it simple and want to achieve this without any utility then below should work
const content = [
<Img src="/photos/tomato.jpg"/>,
<Img src="/photos/tomato.jpg"/>
];
<Two>
{content}
</Two>
You might find the spread syntax useful in this case. Have you tried it out?
<div>
{ [...this.props.children] }
</div>
Combine with map to manipulate the output.
<div>
{ [...this.props.children].map(obj => <div style="someStyling"> {obj} </div> ) }
</div>
Is it because is it a DOM node? Try console.log(this.props.children) you will notice that it logged a array of objects (note that each object contains information of the child element of the component). I read that node and array are not the same tho they have a same format. Visit Javascript DOMNode List for more information.

Categories