Material-UI ListItem Component for Right Toggle - reactjs

This seems so basic that I feel I must be misunderstanding how it works. I have a simple demo component that renders a material-ui List with three ListItems. Each list item has a toggle on the right hand side implemented using the rightToggle prop. For the purposes of demonstration each toggle is generated differently.
The first is a basic material-ui Toggle component. The second is a custom component wrapping a Toggle and the third is generated by a function call.
Some code:
import React from 'react';
import Paper from 'material-ui/Paper';
import { List, ListItem } from 'material-ui/List';
import Toggle from 'material-ui/Toggle';
import MyToggleComponent from './MyToggleComponent';
const myToggleFunction = id => <Toggle onClick={() => console.log(id)} />;
const TestPage = () =>
<div>
<Paper style={{ width: 500, padding: 15, margin: 25 }}>
<List>
<ListItem
primaryText="This is the first list item"
secondaryText="This toggle for this item is directly defined"
rightToggle={<Toggle onClick={() => console.log('1 - clicked')} />}
/>
<ListItem
primaryText="This is the second list item"
secondaryText="This toggle is generated from a component"
rightToggle={<MyToggleComponent text="2 - clicked" />}
/>
<ListItem
primaryText="This is the third list item"
secondaryText="This toggle is generated programatically"
rightToggle={myToggleFunction('3 - clicked')}
/>
</List>
</Paper>
</div>;
export default TestPage;
and the custom component - very basic
import React from 'react';
import PropTypes from 'prop-types';
import Toggle from 'material-ui/Toggle';
const MyToggleComponent = ({ text }) => <Toggle onClick={() => console.log(text)} />;
MyToggleComponent.propTypes = {
text: PropTypes.string.isRequired,
};
export default MyToggleComponent;
Results in:
All three toggles generate the expected console output. The first and third items render as I would expect with a Toggle to the right of the list item. But the second, using a custom component, renders the Toggle above the list item. Can anyone explain why?

Material-UI is cloning these elements under the hood and is adding/injecting a prop style. In the first and third example the actual values are the Material UI defined components that accept a property style as documented here. Your own defined component however only passes the text property and does nothing with style.
So comes down to that all 3 examples get passed a style prop but only the first and third do something with it. To bad this wasn't well documented.
It kinda does say it needs to be a Toggle element and your own component isn't one because it wraps the Toggle component.
pushElement(children, element, baseStyles, additionalProps) {
if (element) {
const styles = Object.assign({}, baseStyles, element.props.style);
children.push(
React.cloneElement(element, { // element is your own defined component
key: children.length,
style: styles, // here the style property is passed
...additionalProps, // your text property is passed here
})
);
}
}
source
So to fix this change:
const MyToggleComponent = ({ text }) => <Toggle onClick={() => console.log(text)} />;
to:
const MyToggleComponent = ({ text, style }) =>
<Toggle style={style} onClick={() => console.log(text)} />;

Related

Creating a Popover responsive component in Typescript

I am creating a Popover component in TypeScript. How can I describe correct the applied style and add function (how to close the popover, because currently I can only open it)?
Taking in account documentation I was trying to add styles using containerStyle inside <Popover>, but it gives me an error.
import { Popover } from 'react-tiny-popover'
import './popover.scss'
import {ReactElement} from "react";
import {Button} from "../Button/Button";
interface PopOverProps {
label?:string
info?:ReactElement | undefined
isPopoverOpen?:boolean
}
export const PopOver = ({
label="Info",
isPopoverOpen=false,
info=(
<>
<p>Hello world!</p>
<p>Example of how does the long text look here - <em>lorem ipsum</em> <strong>lorem ipsum</strong></p>
</>
),
...props
}: PopOverProps) => {
return (
<Popover
isOpen={isPopoverOpen}
content={info}
>
<Button
label={label}
size={"small"}
kind={"outline"}
/>
</Popover>
)
}
What is the error you get ?
Did you read this;
“Your popover content is rendered to the DOM in a single container div. If you'd like to apply style directly to this container div, you may do so here! Be aware that as this div is a DOM element and not a React element, all style values must be strings. For example, 5 pixels must be represented as '5px', as you'd do with vanilla DOM manipulation in Javascript.”?

how to call child component callback event from parent component in react

Hi I am a beginner in React, I am using Fluent UI in my project .
I am planning to use Panel control from Fluent UI and make that as common component so that I can reuse it.I use bellow code
import * as React from 'react';
import { DefaultButton } from '#fluentui/react/lib/Button';
import { Panel } from '#fluentui/react/lib/Panel';
import { useBoolean } from '#fluentui/react-hooks';
export const PanelBasicExample: React.FunctionComponent = () => {
const [isOpen, { setTrue: openPanel, setFalse: dismissPanel }] = useBoolean(false);
return (
<div>
<Panel
headerText="Sample panel"
isOpen={isOpen}
onDismiss={dismissPanel}
// You MUST provide this prop! Otherwise screen readers will just say "button" with no label.
closeButtonAriaLabel="Close"
>
<p>Content goes here.</p>
</Panel>
</div>
);
};
https://developer.microsoft.com/en-us/fluentui#/controls/web/panel#best-practices
I remove <DefaultButton text="Open panel" onClick={openPanel} /> from the example .
So my question is how can I open or close this panel from any other component ?
I would use React useState hook for this.
Make a state in the component that you want render the Panel like
const [openPanel, setOpenPanel] = useState({
isOpen: false,
headerText: ''
})
Lets say for example you will open it from button
<Button onClick={() => setOpenPanel({
isOpen: true,
headerText: 'Panel-1'
})
}> Open me ! </Button>
Then pass the state as props to the Panel component
<PanelBasicExample openPanel={openPanel} setOpenPanel={setOpenPanel} />
in PanelBasicExample component you can extract the props and use it.
export const PanelBasicExample(props) => {
const {openPanel, setOpenPanel} = props
const handleClose = () => {setOpenPanel({isOpen: false})}
return (
<div>
<Panel
headerText={openPanel.headerText}
isOpen={openPanel.isOpen}
onDismiss={() => handleClose}
// You MUST provide this prop! Otherwise screen readers will just say "button" with no label.
closeButtonAriaLabel="Close"
>
<p>Content goes here.</p>
</Panel>
</div>
);
}

List component continually re-renders

I feel like this is likely due to me not understanding the flow of React, but I've been struggling to figure it out. Have an infinite scroll list.
if Row is outside of Scroller it works fine.
However, if Row is inside of Scroller, it results in the component constantly re-rendering. I was hoping to pass a list into Scroller from the parent component with around a thousand items, but in order to do that, I need Row in Scroller to be able to access the props. Whats the best way to go about this?
import React from "react";
import { FixedSizeList as List } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";
const Scroller = (randomArray) => {
const Row = ({ index, style }) => (
<div className={index % 2 ? "ListItemOdd" : "ListItemEven"} style={style}>
{randomArray[index]}
</div>
);
return (
<AutoSizer>
{({ height, width }) => (
<List
className="List"
height={height}
itemCount={1000}
itemSize={35}
width={width}
>
{Row}
</List>
)}
</AutoSizer>
);
};
export default Scroller;
I'm not 100% sure what you're asking, but I think I have an idea...
Render props can be a bit confusing, but they're essentially children components that are functions. Meaning, the direct child of List must be a function that accepts an object of parameters and returns JSX. See this react-window gist for more information regarding passing in data to List and accessing data from within the child function.
Here's a working demo:
By design, this child function is supposed to be re-rendered to add/remove items from the DOM as the user scrolls up/down. To view this behavior, right click on an element, like Season Id, within in the codesandbox window and click on "Inspect" -- you may need to do this twice to focus on the targeted element -- then while the mouse is hovered over the codesandbox render window, scroll down. What you'll notice is that items are dynamically added/removed based upon the scroll direction. So if you're expecting this child function to NOT be re-rendered when the window is scrolled, then you probably shouldn't be using a virtualized list and, instead, should be using pagination.
Example.js
import React from "react";
import { FixedSizeList as List } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";
const Example = ({ dataList }) => (
<AutoSizer>
{({ height, width }) => (
<List
className="List"
height={height}
itemCount={dataList.length}
itemData={dataList}
itemSize={265}
width={width}
>
{({ data, index, style }) => {
const dataItem = data[index];
return (
<div
className={index % 2 ? "ListItemOdd" : "ListItemEven"}
style={style}
>
<h1>Season Id: {dataItem.seasonId}</h1>
<h2>Form Id: {dataItem._id}</h2>
<h2>Start Month: {dataItem.startMonth}</h2>
<h2>End Month: {dataItem.endMonth}</h2>
<h2>Send Reminders: {dataItem.sentReminders.toString()}</h2>
</div>
);
}}
</List>
)}
</AutoSizer>
);
export default Example;
index.js
import React from "react";
import { render } from "react-dom";
import Example from "./Example";
import dataList from "./data.json";
import "./styles.css";
render(<Example dataList={dataList} />, document.getElementById("root"));

How to reuse a Custom Material-ui button inside a react-app?

I'm developing my first React app. I've imported a Material-ui button and I've customized it.
Now I want to reuse this custom button in several components of my app. I want a different text for each time I use this custom button.
Where do I need to write this specific text for each button?
My button is visible when I import it in other components, but I can't see the text I wrote inside the button component. The button stays empty.
My custom button component : MyButton:
import React from "react";
import Button from "#material-ui/core/Button";
import { withStyles } from "#material-ui/core/styles";
const styles = () => ({
button: {
margin: 50,
padding: 10,
width: 180,
fontSize: 20
}
});
function MyButton(props) {
const { classes } = props;
return (
<Button variant="contained" color="primary" className={classes.button}>
<b> </b>
</Button>
);
}
export default withStyles(styles)(MyButton);
The other component where I import MyButton component : Home :
import React from "react";
import "../App.css";
import MyButton from "./Button";
function Header() {
return (
<header className="Header">
{/* background image in css file */}
<h1>Welcome </h1>
<h3> description...</h3>
<MyButton>Play now</MyButton>
</header>
);
}
export default Header;
I expect the button to show "Play now" (expected output) but for now it stays empty (actual output).
Also, I've found another solution that offers the possibility to write directly the text inside each button (children of MyButton), and customize it if needed.
Pass "children" keyword as "props" to MyButton component :
function MyButton(props) {
const { classes, children } = props;
return (
<Button variant="contained" color="primary" className={classes.button}>
<b>{children}</b>
</Button>
);
}
Then write the text of your button inside the button as you will do in html :
<MyButton> Play now </MyButton>
You will get the most flexibility from your custom Button if you pass all of the props along to the wrapped Button. This will automatically take care of children and classes so long as you use class keys in your styles object that match the CSS classes supported for the wrapped component.
import React from "react";
import Button from "#material-ui/core/Button";
import { withStyles } from "#material-ui/core/styles";
const styles = () => ({
root: {
margin: 50,
padding: 10,
width: 180,
fontSize: 20,
fontWeight: "bold"
}
});
function CustomButton(props) {
return <Button variant="contained" color="primary" {...props} />;
}
export default withStyles(styles)(CustomButton);
Notice in the sandbox example, that this allows you to still leverage other Button features like disabled, specify additional styles, or override some properties specified in CustomButton.
If you have a scenario where you need to handle children explicitly (in my example above I used fontWeight CSS instead of the <b> tag), you can use the following syntax to still pass all the props through to the wrapped component:
function CustomButton({children, ...other}) {
return <Button variant="contained" color="primary" {...other}><b>{children}</b></Button>;
}
Pass text of button as props to your button component
<MyButton text="Play now"></MyButton>
Then inside MyButton component you can get it like
function MyButton(props) {
const { classes,text } = props;
return (
<Button variant="contained" color="primary" className={classes.button}>
<b> {text} </b>
</Button>
);
}

How to access name of a button type mui ListItem in react

I have create a material-ui list, in which each ListItem is a button, but when I try to access the name prop of the button it gives undefined.
import React from 'react';
import LoginFront from './login/loginFront';
import {BrowserRouter, Route} from 'react-router-dom';
import {List, ListItem, ListItemText} from '#material-ui/core';
class App extends React.Component {
render(){
return(
<List component='nav'>
<ListItem button name='bName' onClick={event => console.log(event.target.name)} />
<ListItemText primary='item1' />
</List>
)
}
}
export default App;
console.log(event.target.name) gives undefined
onClick={event => console.log(event.target.getAttribute('name'))}
Use the getAttribute method to get the value of name
The html you see on the page is just the rendered representation of the DOM. The properties of nodes on the DOM tree don't match up with the attributes on the html element.
When creating the standard they wouldn't have wanted the interface for the DOM element be the same as the attribute because you might add an attribute that conflicts with an existing property or method however this is just conjecture.
EDIT
onClick={event => console.log(event.currentTarget.getAttribute('name'))}
See here for the difference between target and currentTarget.
The thing is when you define onClick on the topMost parent, you need to use e.currentTarget.id instead of e.target.id since e.target will give you the element on which you clicked rather then the parent on which onClick listener is defined
Material-ui does not have a native way to do it. A simple way to bypass it is to not use name at all and pass your parameter in a preset function.
class App extends React.Component {
clickHandler = name => ev => {
console.log(name)
}
render() {
return (
<List component='nav'>
<ListItem button onClick={this.clickHandler('bName')} />
<ListItemText primary='item1' />
</List>
)
}
}
export default App;

Resources