ReactJs - Conditional Rendering or hiding component - reactjs

What's the de facto approach to choosing between conditional rendering or hiding the component with { display: 'none' }?
For the sake of discussion, let's say that I have a FilterComponent that holds the title of the filter, and a list of FilterItems, with name and amount.
In short, a FilterComponent could be:
Color
Blue (19)
Yellow (17)
Orange (3)
Black (7)
Green (10)
+ Show More
When hitting Show More button, more FilterItems will be displayed, i.e.
Color
Blue (19)
Yellow (17)
Orange (3)
Black (7)
Green (10)
Brown (17)
Pink (88)
White (55)
Red (32)
Purple (17)
- Show Less
Should I hide the FilterItems that are below the Show More? Or should I return null for those below and render them after updating the state with Show More?

I think there are a few ways to accomplish what you need. However, this seems to be the most practised:
{myConditionIsTrue && <MyComponent />}
In your case, it makes sense to use state. I would have a prop inside FilterComponent called showFullList
{this.state.showFullList && (
<React.Fragment>
<All/><The/><Other/><Components/>
</React.Fragment>)}
Just be weary, this mechanism is actually removing/adding to the DOM.

Generally in React it is better to not render something than to render it as hidden. Here is one related discussion:
https://discuss.reactjs.org/t/conditional-rendering-or-toggling-hidden-classes/2535/6

I would go for the "updating state" approach. That way you always have the actual filterItems that is showing in the state. So your components state is in sync and represents the current UI that is showing.
Guess there's no right or wrong in this question though =)

It would make more sense to not render the items that should not be shown until after the Show More has been clicked, and the state has updated. This way you can handle how many items should be shown by default before clicking Show More. This way instead of applying inline styles, or a special class to certain elements, you can use the exact same logic to all FilterItems, but only render X of them.

You can change initial state value of isHidden or something like that . When you click button value will be oppasite of before situtation . And when you wants to render you should give condition ;
{ isHidden &&
...

Generally, there is no significant performance differences between display: none and conditional rendering, because the browser's behaviour in both cases is nearly the same. The main difference is that if you use display: none, then node is not removing from the DOM tree, which forces some CSS pseudo-selectors like :last-child to consider a hidden node as last-child and so on. So, it is not performance-related, but mostly CSS-related. Both of approaches are ok for use, I suppose :)

My prefer two methodes:
#1 Element Variables
const button = <LogoutButton onClick={this.handleLogoutClick} />;
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
2# Inline If with Logical && Operator
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
More details here: https://reactjs.org/docs/conditional-rendering.html

An other approach based on Array.prototype.slice() method
The usage in parent component
import React from "react";
import { ColorList } from "./Color";
export default function App() {
return <ColorList colors={["red", "green", "blue"]} visibleItemsCount={1} />;
}
And the ColorList component looks like this:
import React from "react";
// This is just a placeholder component :)
function Color({ color }) {
return <div style={{ color }}>{color}</div>;
}
export function ColorList({ colors, visibleItemsCount = 0 }) {
const [showMore, setShowMore] = React.useState(false);
// Toggle value on click button
const onClick = () => setShowMore((value) => !value);
// Memoize the color list when props changed
const visibleColors = React.useMemo(() => {
// If show more items, return the whole array
// Otherwise, return a sliced array based on visible items
const count = showMore ? colors.count : visibleItemsCount;
return colors.slice(0, count);
}, [colors, visibleItemsCount, showMore]);
console.log(visibleColors);
return (
<>
<h1>Color list</h1>
<>
{visibleColors.map((color) => (
<Color key={color} color={color} />
))}
</>
<button onClick={onClick}>{showMore ? "Show less" : "Show more"}</button>
</>
);
}
Note: I uploaded the code on CodeSandbox, you can check it here

You could use a library called react-if. This library helps you wether to render or not based on a a condition.
Here is an example:
const Bar = ({ name, age, drinkingAge }) => (
<div>
<Header />
<If condition={ age >= drinkingAge }>
<Then><span className="ok">Have a beer, {name}!</span></Then>
<Else><span className="not-ok">Sorry, {name}, you are not old enough.</span></Else>
</If>
<Footer />
</div> )

Related

I need help to selecting a button from an array of buttons using material ui

So things are like this, I have an array of phone numbers, it's mapped so everynumber has a button to copy the number and make a call. Thing is, we use material-ui, specifically the v4 of it.
What i need to do is that whenever someone clicks a button this button change it's color to idk green let's say so the worker knows they are using that button, avoiding clicking again and those kind of things.
material-ui has it's own way to put a color to the button using the color prop and the createTheme API, is there ANY way to do this using &:active or &:selected in material with the createTheme API, is there a way to do it without it (it's not mandatory to use createTheme).
Any help is appreciated, thanks!
If you are rendering your buttons using a map, store the index of the button that is clicked in some state, and use it to compare the index of the current button with that stored index, if matches change the color
something like:
export default function App() {
const [active, setActive] = useState(null);
return (
<div className="App">
{buttons.map((button, idx) => {
const isActive = active === idx;
return (
<Button
key={`btn-${idx}`}
onClick={() => setActive(idx)}
variant="contained"
color={isActive ? "success" : "primary"}
>
{button}
</Button>
);
})}
</div>
);
}

React: State of a variable changes, but UI Icon dosen't get updated

So I tried creating a simple navbar that would have its buttons controlled by useState. However I have a problem where the button icon color wont update even though the state of the variable that controls it changes.
Now, I did some testing and and added text into the icon component (not show here) and made it so it was controlled by the same state as the color on the icon is now. And for some reason when I did that the state and the text inside the component both changed correctly. Could anyone provide an explanation on why that happens? Because to me it seems like I've misunderstood how react binds things to states and controls them.
Navigation bar component
import NavButton from "./NavButton"
import { useState } from "react";
function NavBar(){
const [buttons, setButtons] = useState([
{id:1, name:"Orders", icon:"bx:bx-dollar-circle", active:false},
{id:2, name:"Menu", icon:"ic:round-restaurant-menu", active:false},
{id:3, name:"Leave", icon:"uil:exit", active:false}
]);
const toggleButton = (id) => {
setButtons(buttons.map(button => (
button.id === id ? {...button, active:!button.active} : {...button, active:false}
)))
}
return (
<div className="h-1/6 bg-white border-b-lebo flex flex-row justify-around">
<>
{buttons.map((button) => (<NavButton button={button} key={button.id} onToggle={toggleButton}/>))}
</>
</div>
)
}
export default NavBar;
Navigation button component
import Icon from "./Icon";
function NavButton({button, onToggle}){
return (
<button onClick={() => onToggle(button.id)} className={`font-bold text-gray-500 flex flex-col items-center justify-center flex-grow w-5 hover:bg-gray-100`}>
<p className="self-center">{button.name}</p>
<Icon icon={button.icon} name={button.name} color={button.active ? "#454545" : "#8b8b8b"}/>
</button>
)
}
export default NavButton;
Icon component
function Icon({icon, color, name}) {
return (
<div>
<span color={color} className="iconify h-10 w-auto self-center" data-icon={icon}></span>
</div>
)
}
export default Icon
I solved my problem by creating 2 different Icon components.
Icon and IconDark and conditionally rendering them inside the NavButton component.
Not sure if it is the "correct" way of doing things but it got the job done.
I'm going to guess the reason why it didn't render the colors correctly earlier is because of the attribute "color" inside the component. I think JSX just took it in as another prop and did nothing with it after the first render of the element.
edit 1: nvm it definitely didn't get the job done. At least not well enough. The icon swap in the render isn't fast enough so it causes the user to see the icon swap.
edit 2: This article held the answer that I needed.
https://dev.to/abachi/how-to-change-svg-s-color-in-react-42g2
It turns out that to change an svg color with react you need to set the initial fill (or for me color) value inside the svg component to "current" and then pass the real value in from the parent element conditionally.
Long story short - Controlling SVG values is a little different to controlling text values in react.

Using Stateful React classes in typescipt

I am trying to create a Stateful class in which you can call methods such as createHeaderButton() where after calling it would update the state and re-render with these new updates in the component.
Im using Material-UI and so most of their styling utilizes Reacts hook API which of course classes cant use. Ive tried to get around this by using;
export default withStyles(useStyles)(HeaderBar)
Which exports the class separately with the Styles(withStyles(useStyles) useStyles as the defined styles) And the class(HeaderBar). Now the only issue is that i need to access the styles in my class. Ive found a JS example online that wont work for me because of the strong typed syntax of TS. Additionally When initializing my Class component in other places i try to get the ref=(ref:any)=>{} And with that call the create button methods when i get a response from my server, Which doesnt work because of this new way of exporting the class component!
Thanks for the help, Heres my component class: https://pastebin.pl/view/944070c7
And where i try to call it: https://pastebin.com/PVxhKFHJ
My personal opinion is that you should convert HeaderBar to a function component. The reason that it needs to be a class right now is so you can use a ref to call a class method to modify the buttons. But this is not a good design to begin with. Refs should be avoided in cases where you can use props instead. In this case, you can pass down the buttons as a prop. I think the cleanest way to pass them down is by using the special children prop.
Let's create a BarButton component to externalize the rendering of each button. This is basically your this.state.barButtons.forEach callback, but we are moving it outside of the HeaderBar component to keep our code flexible since the button doesn't depend on the HeaderBar (the header bar depends on the buttons).
What is a bar button and what does it need? It needs to have a label text and a callback function which we will call on click. I also allowed it to pass through any valid props of the material-ui Button component. Note that we could have used children instead of label and that's just down to personal preference.
You defined your ButtonState as a callback which takes the HTMLButtonElement as a prop, but none of the buttons shown here use this prop at all. But I did leave this be to keep your options open so that you have the possibility of using the button in the callback if you need it. Using e.currentTarget instead of e.target gets the right type for the element.
import Button, {ButtonProps as MaterialButtonProps} from "#material-ui/core/Button";
type ButtonState = (button: HTMLButtonElement) => void;
type BarButtonProps = {
label: string;
callback: ButtonState;
} & Omit<MaterialButtonProps, 'onClick'>
const BarButton = ({ label, callback, ...props }: BarButtonProps) => {
return (
<Button
color="inherit" // place first so it can be overwritten by props
onClick={(e) => callback(e.currentTarget)}
{...props}
>
{label}
</Button>
);
};
Our HeaderBar becomes a lot simpler. We need to render the home page button, and the rest of the buttons will come from props.childen. If we define the type of HeaderBar as FunctionComponent that includes children in the props (through a PropsWithChildren<T> type which you can also use directly).
Since it's now a function component, we can get the CSS classes from a material-ui hook.
const useStyles = makeStyles({
root: {
flexGrow: 1
},
menuButton: {
marginRight: 0
},
title: {
flexGrow: 1
}
});
const HeaderBar: FunctionComponent = ({ children }) => {
const classes = useStyles();
return (
<div className={classes.root}>
<AppBar position="static">
<Toolbar>
<HeaderMenu classes={classes} />
<Typography variant="h6" className={classes.title}>
<BarButton
callback={() => renderModule(<HomePage />)}
style={{ color: "white" }}
label="Sundt Memes"
/>
</Typography>
{children}
</Toolbar>
</AppBar>
</div>
);
};
Nothing up to this point has used state at all, BarButton and HeaderBar are purely for rendering. But we do need to determine whether to display "Log In" or "Log Out" based on the current login state.
I had said in my comment that the buttons would need to be stateful in the Layout component, but in fact we can just use state to store an isLoggedIn boolean flag which we get from the response of AuthVerifier (this could be made into its own hook). We decide which buttons to show based on this isLoggedIn state.
I don't know what this handle prop is all about, so I haven't optimized this at all. If this is tied to renderModule, we could use a state in Layout to store the contents, and pass down a setContents method to be called by the buttons instead of renderModule.
interface LayoutProp {
handle: ReactElement<any, any>;
}
export default function Layout(props: LayoutProp) {
// use a state to respond to an asynchronous response from AuthVerifier
// could start with a third state of null or undefined when we haven't gotten a response yet
const [isLoggedIn, setIsLoggedIn] = useState(false);
// You might want to put this inside a useEffect but I'm not sure when this
// needs to be re-run. On every re-render or just once?
AuthVerifier.verifySession((res) => setIsLoggedIn(res._isAuthenticated));
return (
<div>
<HeaderBar>
{isLoggedIn ? (
<BarButton
label="Log Out"
callback={() => new CookieManager("session").setCookie("")}
/>
) : (
<>
<BarButton
label="Log In"
callback={() => renderModule(<LogInPage />)}
/>
<BarButton
label="Sign Up"
callback={() => renderModule(<SignUpPage />)}
/>
</>
)}
</HeaderBar>
{props.handle}
</div>
);
}
I believe that this rewrite will allow you to use the material-ui styles that you want as well as improving code style, but I haven't actually been able to test it since it relies on so many other pieces of your app. So let me know if you have issues.

Having trouble with onMouseEnter event in React

I'm trying to build a navbar, using React and hooks, where each div will change to a specific color on an onMouseEnter and onMouseLeave. I can't figure out why they're all affected if i hover over one. I guess I'm asking how I could make them independent of one another.
Sorry if this is a really obvious mistake. Still really green. Thanks again!
Here is a link to the CodeSandbox: https://codesandbox.io/s/holy-snowflake-twojb?file=/src/navbar.js
I refactored your code.
You can check it in https://codesandbox.io/s/lucid-lake-u3oox?file=/src/navbar.js
You are using the function setBackground to set the background color in the hoverStyle CSS class which affects to all <div className="hoverStyle"> tags.
There are many options to do that. If you want to do it with React, one way of do it is creating a CSS class like this:
.active {
background-color: #ffac4e;
}
then, create a functional component
const Activable = ({ className, children, bgColor}) => {
const [active, setActive] = useState('#fff');
return (
<div className={`${ className } ${ active }`
onMouseEnter = {() => setActive( bgColor )}
onMouseLeave = {() => setActive('#fff')}
>
{ children }
</div>
)
}
then replace your ` with this new component like this:
<Activable className="hoverStyle" bgColor="#ffac4e">
<div style = {navChildLeft}>2020</div>
<div style = {navChildTop}>
Shy RL <br />
Digital - Album art
</div>
</Activable>
and repeat with the rest of <div>'s
With react, it is important to think about reusable components. How can you create something that you can reuse over and over again in different parts of your project.
Take a look at this sample of your project broken into components.
https://codesandbox.io/s/holy-brook-3jror?fontsize=14&hidenavigation=1&theme=dark
I would recommend reading this to help out: https://reactjs.org/docs/thinking-in-react.html

React tabs - switching destroys component, need to maintain the same component

I am trying to make a multi-tabbed SPA with React and Material-UI. I use code from this demo as an example: https://codesandbox.io/s/qlq1j47l2w
It appears that if I follow the aforementioned example, I end up returning new instance of the component with tab contents each time I navigate between the tabs:
<Tabs
value={value}
onChange={this.handleChange}
indicatorColor="primary"
textColor="primary"
scrollable
scrollButtons="auto"
>
<Tab label="Tab 1" />
<Tab label="Tab 2" />
</Tabs>
</AppBar>
{value === 0 && <Tab1Contents/>}
{value === 1 && <Tab2Contents/>}
As Tab1Contents is a form, I would like its internal state to be retained, instead of loading a new instance of the component, which the code above appears to do.
What is the best way to get React to use only one instance of the component and 'memorise field values'?
EDIT
I have added Redux to the example, but the store corresponding to the form within the Tab is destroyed the moment I switch away. Is there any other way to implement tabs in React that would hide the tab contents, instead of destroying them and re-creating them from scratch each time I navigate away?
The solution to my problem was quite simple! If you don't want to destroy the component (remove it from DOM), you can simply hide it!
Instead of:
{value === 0 && <Tab1Contents/>}
Use:
<div style={{ display: value === 0? 'block': 'none'}}>
<Tab1Contents/>
</div>
It was already mentioned that you have to prevent your component from being removed from the DOM. The easiest solution with MUI5 TabPanel is in fact to just replace
{value === index && children}
with
{children}
that means your Tabpanel would look like that:
import * as React from "react";
const TabPanel= ({ children, value, index, ...other }) => {
return (
<div
role="tabpanel"
hidden={value !== index}
id={`tabpanel-${index}`}
aria-labelledby={`tab-${index}`}
{...other}
>
{children}
{/* {value === index && children} // This was removed */}
</div>
);
};
export default TabPanel;
No additional logic necessary as the hidden prop already takes care of the visibility aspect. This way your components should maintain their state!
You would need to persist the state between the tab changes. I prototyped a form using React forms documentation for Tab1Container and as you play around with it, the value will disappear
https://codesandbox.io/s/material-demo-ojwhr
What you ideally need to use something like Redux, which will use a store to keep the information even between the state changes like Tab clicks.
Hope this helps!

Resources