React children count - javascript

i have a component like this
<Slider
Arrows
Autoplay
>
<SliderContentMall />
</Slider>
and in SliderContentMall i make a map like this
const SliderContentMall = (props) =>
{
return (
props.Data.map(DataValue =>
{
return (
<TouchableOpacity style={Styles.ImageContainer} onPress={() => {}>
</TouchableOpacity>
)
})
);
}
this map returns 3 object the things is in the slider component when i do
console.log(React.Children.count(this.props.children))
it only return 1 and i need the count of the 3, i cant make the map in the slider directly the map needs to be in the SliderContentMall

React.Children.count is doing what it's intended to do: there's a single child, and that child is a <SliderContentMall>. It's not going to check how many children are underneath that, and in fact it can't since those children havn't been rendered yet. Rendering goes from top down, so while you're rendering the <Slider>, you have not yet rendered the <SliderContentMall>, and thus the various <TouchableOpacity>'s havn't even been thought of yet.
If the slider needs to know how many pieces of data will be rendered, the most straightforward way to do that is as a prop to the slider. For example:
<Slider
Arrows
Autoplay
count={data.length}
>
<SliderContentMall data={data}/>
</Slider>

You are using map function over props.Data instead of props.children.

Related

How to animate nested routes using react-transition-group's CSSTransition?

I need to do some animations within nested routes (using react-transition-group v5.2.0, for what that is worth).
Following the example, in the react-transition-group docs I am able to get regular routes animation going using CSSTransition, but the same cannot be said for nested routes. They simply do not animate and their expected classes from CSSTransition are not injected in the component as expected (*-enter, *-enter-active, *-exit, *-exit-active).
My current component with nesting routes looks like the following:
function Example () {
const { params } = useRouteMatch() || {}
const history = useHistory()
return (
<>
[EXISTING_CONTENT]
<button onClick={()=> goToTheNextRoute()}>Click me!</button>
<Route path="/example/:nestedId">
{({ location }) => {
const { pathname } = location
return (
<CSSTransition
in={pathname.includes(params?.nestedId)}
timeout={500}
classNames="nested-animation"
mountOnEnter
unmountOnExit
>
<div className="nested-animation">
My nested routes I'd like to animate at every click:
<h3>Current Nested Route: {params?.nestedId}</h3>
</div>
</CSSTransition>
)
}}
</Route>
</>
)
}
Here is also a sandbox app which is also based off the simple route example from the documentation.
What am going for is to have an animation on the div of className nested-animation every time the nested route is updated.
This is a case for the TransitionGroup component in react-transition-group. Simply import the above in the same line as CSSTransition and wrap it around the transition you want to animate.
Change the CSSTransition attributes to remove in and replace it with key. This is similar to an array map, where it needs a unique key to know what it is transitioning (it works with cloned elements). Set the value of key to params?.id since you have that already.
You can remove mountOnEnter and unmountOnExit as the transition group takes care of the mounting.
You don't need to change your style selectors, but you might have to futz with the properties to get it to behave correctly
There is a gotcha. When I mentioned it works with cloned elements, that means you will have 2 routes on screen at once while it is transitioning. This will cause a reflow on mount and unmount. I have toyed with that for a while, and the best I can come up with is to make either the in or out element have position: absolute so it is taken out of the flow, and the elements can overlap during transition. TransitionGroup creates a div, and you can give it a className. Would be easy to make it position relative so you don't lose your content while it's position absolute.
Code
import { CSSTransition, TransitionGroup } from 'react-transition-group'
...
<TransitionGroup>
<CSSTransition
key={params?.nestedId}
timeout={500}
classNames="nested-animation"
>
<div className="nested-animation">
My nested routes I'd like to animate at every click:
<h3>Current Nested Route: {params?.nestedId}</h3>
</div>
</CSSTransition>
</TransitionGroup>

Looping a ReactJS component inside of its parent component

I have this component imported that returns a bunch of cards but I want to style this in a row instead of a column and it seems the way to do that is to loop the component as a <li> and then adding css but I cannot seem to make the component loop correctly.
the component looks like this inside of the parent element:
<div id="newsFeed" className='feed'>
<Feed theme={this.state.theme} articles = {this.state.articles} />
</div>
I have tried solutions such as:
var feedContent = <Feed theme={this.state.theme} articles = {this.state.articles} />
///////////////////////
{feedContent.map(item => <Feed key={item} value={item} />)}
</div>
but cannot seem to find any luck.
Any help would be greatly appreciated
map is a built in array method that is used a bunch in React. You need to use it on an array or else you will throw an error. I am assuming the value for articles is an array here:
//Feed component
class Feed extends React.Component {
constructor(props) {
super(props);
this.state = {
articles = []
};
}
componentDidMount = () => { // maybe call an API to populate the articles array }
render = () => {
return (
<ul className="someClass" >
{this.state.articles.map((item, index) => <li key={index} className="someOtherClass">{item}</li>)}
</ul>
);
}
}
alternatively you could create a li component, perhaps called FeedItem, import it and map it with the value prop from each item in the articles array.
// render method for Feed Component
render = () => {
return(
<ul className="someClass">
{this.state.articles.map((item, index) => <FeedItem key={index} value={item} />)}
</ul>
);
}
// FeedItem component
const FeedItem = ({value}) => {
return(
<li className="someOtherClass">{value}</li>
);
}
so, you are using map to create a list with the items in your articles array, and the map function loops through each article and returns a list component. Hopefully this helps! here's the React docs for reference: https://reactjs.org/docs/lists-and-keys.html
note: React docs advise against using an index as a key, but I don't know if your article array elements contain something unique. If they do (like an id of some kind), use that instead of index.
I think you need to change the approach.
I'd recommend you create an Article component, example:
function Article({ title }) {
<div>
{title}
<div>
}
After that, you might use the articles array to show each one:
//Feed component
this.state.articles.map((article, i) => <Article title={article.title}>)
In that way you can stylize the articles component as you want.
I was able to figure it out with simple CSS believe it or not. Basically <Feed /> was wrapped in a div in the parent component and was not responding to Flexbox CSS code, however, After I used changed the className of the div that was wrapping the component inside of its <div> within its own file the div responded to CSS! Thank you so much for your help everyone, I appreciate it!
Have you tried adding a className to the div that is the parent of the cards? Use that class to apply a flex display for example, and see what that gives you. If Newscard has a fixed width of 100% by chance, of course you'll need to adjust that to a small percentage / width to suit your needs.

prevent remount when component is wrapped in parent

I have a component that may or may not be rendered in a wrapper component:
class Video extends Component {
state = { isFullscreen: false }
render () {
const { isFullscreen } = this.state
return (
<View>
{isFullscreen ? (
<Modal>
<VideoView />
</Modal>
) : (
<VideoView />
)}
<Button title='inline' onPress={() => this.setState({ isFullscreen: false })} />
<Button title='fullscreen' onPress={() => this.setState({ isFullscreen: true })} />
</View>
)
}
}
Every time I press inline or fullscreen, the <VideoView /> remounts. This makes it hard to add initialization logic to the componentWillMount method. I can do other checks in my application to make sure it works well, but it feels better to re-use the component that already exists.
Is there a way to recycle the <VideoView />?
Ps My project is in React Native, so I used some syntax / component from RN, but I assume the question goes for normal React projects as well
Try wrapping the component with a wrapper and now toggle the styles for this wrapper based on state change. I guess that should work.
Also if you are imminent on reusing the modal component only, what you can do is define some CSS under a new className on the modal Component that alters the required styling to give an effect that the modal is toggled. Then add remove the className based on the state change.

Is it ok to render a React element multiple times?

Let's say I have a functional component rendering 5 icons.
This can be done like this:
export default const Icons = () =>
<div>
<Icon/>
<Icon/>
<Icon/>
<Icon/>
<Icon/>
</div>;
But it can also be done like this:
const icon = <Icon/>;
export default const Icons = () =>
<div>
{icon}
{icon}
{icon}
{icon}
{icon}
<div/>;
The difference is that in the second case, only one React element is created using React.createElement.
Is it discouraged to render a React element multiple times?
If so, why is that? If not, are there any performance or size benefits to be had by rendering a React element multiple times?
You're thinking it wrongly. Both are the same. There's no difference between them. In the second case, you're just assigning to component <Icon /> and each time you call {icon} will actually call to the component <Icon />.
I would rather choose to use first. Because, I don't need to assign unnecessarily which calls to the component.
Here's what will happen when using second case:
Look for {icon}
Look for assigned component <Icon />
Render <Icon /> component
Here's what will happen when using first case:
Look for <Icon />
Render <Icon /> component
It is ok. You can also map over some array of data that you can use to pass down to your components as props.
const altTags = ['me', 'dog', 'cat'];
const icon = <Icon/>;
export default const Icons = () =>
<div>
{this.altTags.map(tag => <Icon alt={tag} />)}
</div>;
It is ok but is not really efficient from the architectural point of view.If we follow the principles of SOLID development this break a few rules.
From a maintenability and reusability point of view this can be improved.
If you create a property in the component that specify how many icons you want and in the render method you use a loop to iterate and create the icons this code will be much better.

How to add style - like margin - to react component?

So, expect two simple components that I have built:
import {Input} from 'semantic-ui-react';
import {Select} from 'semantic-ui-react';
const CategoriesDropdown = ({categories, onCategorySelected, selectedCategory}) => {
const handleChange = (e, {value})=>{
onCategorySelected(value);
};
return (
<Select placeholder="Select category" search options={categories} onChange={handleChange} value={selectedCategory} />
);
};
const IdentifiersInput = ({identifiers, onIdentifiersChanged}) => {
return (
<Input placeholder="Enter identifiers..." value={identifiers} onChange={onIdentifiersChanged}/>
);
};
Nothing fancy so far.
But now, I am building another component that displays those two in a flexbox row:
<Box>
<CategoriesDropdown categories={categories} selectedCategory={selectedCategoryId}
onCategorySelected={this.selectCategory}/>
<IdentifiersInput identifiers={identifiers} onIdentifiersChanged={this.changeIdentifiers}/>
</Box>
Unfortunately they are both displayed right next to each other without any margin in between.
Usually, I would just add a margin-left style to the second element, but because it is a React component, that doesn't work. Using style={{marginLeft: '20px'}} doesn't work as well, because the IdentifiersInput component doesn't use it.
I know that I can fix it by doing this: <Input style={style} ... inside the IdentifiersInput component.
However, this seems to be a very tedious way of achieving this goal. Basically, I have to add this to every single component I am writing.
I clearly must be missing something here. How am I supposed to apply such layout CSS properties to React components?
I think I understand.
1) Applying CSS directly to React Components does not work--I can confirm that.
2) Passing props down to the low level elements is tedious, confirmed but viable.
Notice hasMargin prop:
<Box>
<CategoriesDropdown
categories={categories}
selectedCategory={selectedCategoryId}
onCategorySelected={this.selectCategory}
/>
<IdentifiersInput
identifiers={identifiers}
onIdentifiersChanged={this.changeIdentifiers}
hasMargin
/>
</Box>
Possible input:
const IdentifiersInput = ({identifiers, onIdentifiersChanged, className, hasMargin }) => {
return (
<Input
className={className}
placeholder="Enter identifiers..."
value={identifiers}
onChange={onIdentifiersChanged}
style={hasMargin ? ({ marginLeft: '0.8rem' }) : ({})}
/>
);
};
NOTE: I do not like style as much as I like adding an additional class because classes can be adjusted via media queries:
const IdentifiersInput = ({identifiers, onIdentifiersChanged, className, hasMargin }) => {
const inputPosition = hasMargin ? `${className} margin-sm` : className
return (
<Input
className={inputPosition}
placeholder="Enter identifiers..."
value={identifiers}
onChange={onIdentifiersChanged}
/>
);
};
If you find inputPosition too verbose as shown above:
className={hasMargin ? `${className} margin-sm` : className}
3) You could accomplish it using a divider Component, sacreligious yet rapidly effective
<Box>
<CategoriesDropdown
categories={categories}
selectedCategory={selectedCategoryId}
onCategorySelected={this.selectCategory}
/>
<div className="divider" />
<IdentifiersInput
identifiers={identifiers}
onIdentifiersChanged={this.changeIdentifiers}
/>
</Box>
You can use media queries and control padding at any breakpoints if desired.
4) CSS pseudo-elements or pseudo-classes, I don't see any mention of them in answers so far.
MDN: https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes
CSS Tricks: https://css-tricks.com/pseudo-class-selectors/
Usually, when you have a random collection of DOM elements, you can calculate a way using CSS to wrangle them into the correct position. The list of available pseudo-classes is in that MDN link. It honestly helps to just look at them and reason about potential combinations.
My current issue is I don't know what is in <Box /> other than it probably has a div with display: flex; on it. If all we have to go on is that and the div is called <div className="Box">, maybe some CSS like this will fix it:
.Box {
display: flex;
}
.Box:first-child {
margin-right: 0.8rem;
}
This is why it is extremely important to know exactly what the surrounding elements will or can be, and exactly which CSS classes/IDs are nearby. We are basically trying to hook into something and correctly identify the left child in Box and add margin to the right of it, or target the right child and add margin to the left of it (or depending on everything, target both and split the additional margin onto both).
Remember there is also ::before and ::after. You are welcome to get creative and find a solution that involves position:relative and position: absolute and adds no markup.
I will leave my answer like that for now, because I think either you already thought about pseudo-selectors, or you will quickly find something that works :)
That or the divider is actually quite viable. The fact you can use media queries alleviates you from concern of future management or scalability of the components. I would not say the same about <div style={{}} />.
As your component specializes another single component it would be a good practice to pass any props your wrapper does not care for to the wrapped component. Otherwise you will loose the ability to use the api of the original <Input>component including passing styles to it:
const IdentifiersInput = ({identifiers, onIdentifiersChanged, ...props}) = (
<Input
{...props}
placeholder="Enter identifiers..."
value={identifiers}
onChange={onIdentifiersChanged}
/>
);
There may be valid cases where you explicitly want to prevent users to be able to pass props to the wrapped component but that does not look like one of those to me.
I clearly must be missing something here. How am I supposed to apply
such layout CSS properties to React components?
You did not miss something. A react component has no generic way to be styled because it is no DOM element. It can have a very complicated and nested DOM representation or no representation at all. So at some point you as the designer of the component have to decided where the styles, ids and class names should be applied. In your case it is as easy as passing these props down and let the <Input> and <Select>component decide. I find that to be quite elegant rather than tedious.
I see several ways to do it, but the easiest I see would be to pass a className to IdentifiersInput like so:
<IdentifiersInput className="marginLeft" identifiers={identifiers} onIdentifiersChanged={this.changeIdentifiers}/>
Inside IdentifiersInput I would just set that class to the Input:
const IdentifiersInput = ({identifiers, onIdentifiersChanged, className}) => {
return (
<Input className={className} placeholder="Enter identifiers..." value={identifiers} onChange={onIdentifiersChanged}/>
);
};
Semantic UI's Input element can receive a className prop.
I would then just use CSS or SCSS to add styles to that particular class. In this case, the margin you want.

Categories

Resources