How to access onClick action in Material UI Link through keyboard Tab key without using component="button" or href when I use tabIndex in it - material-ui

After I got the focus on Material UI Link through keyboard tab key using tabIndex, the click action is not working when I hit the enter button. I don't understand why. Can anyone help me with this? Thank you.
sandbox link: https://codesandbox.io/s/material-demo-forked-4evbh?file=/demo.tsx
code:
/* eslint-disable jsx-a11y/anchor-is-valid */
import React from "react";
import { makeStyles, createStyles, Theme } from "#material-ui/core/styles";
import Link from "#material-ui/core/Link";
import Typography from "#material-ui/core/Typography";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
"& > * + *": {
marginLeft: theme.spacing(2)
}
}
})
);
export default function Links() {
const classes = useStyles();
const [state, setState] = React.useState(true);
const handleClick = () => {
setState(!state);
};
return (
<Typography className={classes.root}>
<Link onClick={handleClick} tabIndex={0}>
Link
</Link>
<Typography>Click on above link to display hidden content..</Typography>
{state ? "" : "focus on Link using Tab key?"}
</Typography>
);
}

Set component prop to button as follows:
export default function Links() {
const classes = useStyles();
const [state, setState] = React.useState(true);
const handleClick = () => {
setState(!state);
};
return (
<Typography className={classes.root}>
<Link onClick={handleClick} tabIndex={0} component="button">
Link
</Link>
</Typography>
);
}

I have used onKeyPress attribute to achieve the click action through keyboard tab key without using component="button" or href like below.
function goToDetails(event, id)
{
if(event.key === 'Enter'){
event.preventDefault();
history.push(`/`);
}
else {
event.preventDefault();
history.push(`/`);
}
}
<Link tabIndex={0} onKeyPress={(event) => goToDetails(event, row.id)} onClick={(event) => goToDetails(event, row.id)}>
Go to details
</Link>

Related

React Checkboxes Filtering

I'm struggling to create Checkbox filtering in React. I want my products to be filtered by brand,I have created checkboxes dynamically from database. So each checkbox corresponds to the brand. The quantity of checkboxes equals to quantity of brands.
The problem is when I click on one of the checkboxes, the other products are disappearing from the screen, and this is what I want, but at the same time, the other checkboxes is disappearing as well, and I see only the clicked one.
Also, when I click in that checkbox again I want the products of the brand to be back on the screen.
Any ideas how to do it?
Category.js component:
/** #format */
import { useState, useEffect, createContext } from "react";
import { Link, useParams } from "react-router-dom";
import Header from "../components/Header";
import React from "react";
// Importing styles
import "./styles/Category.css";
import Footer from "../components/Footer";
import Filter from "./Filter";
export const CatgContext = createContext();
export const Category = ({ onAdd }) => {
const [products, setProducts] = useState([]);
const params = useParams();
const getProducts = async () => {
try {
const res = await fetch(`/api/products/${params.type}`);
const data = await res.json();
setProducts(data);
} catch (err) {
console.log(err);
}
};
useEffect(() => {
getProducts();
}, []);
return (
<>
<Header />
<h1>{params.type}</h1>
<aside className="catalogue-aside">
<CatgContext.Provider value={{ setProducts, products }}>
<Filter products={products} />
</CatgContext.Provider>
</aside>
<div className="category-wrapper">
{products.map((product) => {
return (
<div
className="product-card"
key={product.product_id}
id={product.product_id}
>
<Link to={`/product/${product.product_id}`}>
<img className="img-card" src={product.product_image} />
<h3 className="title-card">{product.product_type}</h3>
</Link>
<p>{product.product_brand}</p>
<p>{product.product_name}</p>
<p>{product.product_description}</p>
<p>${product.product_price}</p>
<button onClick={() => onAdd(product)}>Add to Cart</button>
</div>
);
})}
</div>
<Footer />
</>
);
};
Filter.js component:
import { useState, useContext, useEffect } from "react";
import { CatgContext } from "./Category";
const Filter = (props) => {
const { products, setProducts } = useContext(CatgContext);
const handleChange = (e, value) => {
const filteredByBrand = products.filter((product) => {
return product.product_brand === value;
});
setProducts(filteredByBrand);
};
const renderCheckboxLists = () =>
products.map((product) => (
<div className="checkbox-wrapper" key={product.product_id}>
<input
onChange={(e) => handleChange(e, product.product_brand)}
type="checkbox"
/>
<span>{product.product_brand}</span>
</div>
));
return <>{renderCheckboxLists()}</>;
};
export default Filter;
Screenshot 1
Screenshot 2

changing the variant of the Button onClick MUI

I want to change the variant of the Button component whenever it gets clicked, from 'outlined' to 'contained':
<Button variant='outlined'>Click me to change my variant to contained</Button>
Is that possible in MUI? using React refs? or how can I achieve this?
you can achieve it like this:
import React, { useState } from 'react';
function App() {
const [currentButtonVariant, setCurrentButtonVariant] = useState('outlined');
const handleButtonVariantChange = () => {
if (currentButtonVariant === 'outlined') {
setCurrentButtonVariant('contained');
}
else {
setCurrentButtonVariant('outlined');
}
}
return (
<div className="App">
<Button variant={currentButtonVariant} onClick={handleButtonVariantChange}>Click me to change my variant to contained</Button>
</div>
);
}

Toggle text on pressing the button in react native

I am new to react native and I want to toggle text on pressing the button, but the problem is that when I press button the text changed but when I press it again nothing happen. Here is my code:
enter image description here
When you use react hooks, you configure the component to do things after it renders. If you declare a variable outside of a useState hook, it will be reset on every render. This is what happens to your isTrue variable. Read more about the rules of hooks here.
You also don't need to evaluate isTrue == true, you can just call isTrue, it will have the same effect.
export default function App() {
var startingText = "First text"
const [isTrue, setIsTrue] = useState(true)
const [outputText, setOutputText] = useState(startingText)
function textChange() {
setIsTrue(!isTrue)
return isTrue ? startingText : setOutputText("Text Changed")
}
return(
<View>
<Text>{outputText}</Text>
<Button title="Change Text" onPress={textChange}/>
</View>
)
}
You can simplify the code even further by removing the boolean variable and moving the logic of selecting which text value to set in the textChange function directly using a ternary operator.
export default function App() {
var startingText = ""
const [outputText, setOutputText] = useState(startingText)
function textChange() {
setOutputText(outputText === startingText ? "Text Changed" : startingText)
}
return(
<View>
<Text>{outputText}</Text>
<Button title="Change Text" onPress={textChange}/>
</View>
)
}
Try to change the function on:
const [tooggle, setToggle] = useState(false);
function textChange() {
setToggle(!tooggle);
return isTrue === tooggle
? setOutputText('first')
: setOutputText('second');
}
Example of toggle:
import React, { useState } from 'react';
import { Button, View, Text } from 'react-native';
const ToggleFunction = () => {
const [outPutText, setOutputText] = useState('first');
const [tooggle, setToggle] = useState(false);
function textChange() {
setToggle(!tooggle);
return tooggle ? setOutputText('first') : setOutputText('second');
}
return (
<View
style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}
>
<Text>{outPutText}</Text>
<Button title="button" onPress={textChange} />
</View>
);
};
export { ToggleFunction };

Text field with multiple value(image included for reference)

I'm looking for a text field with multiple inputs as:
Here as you can see I can add new text and on press of enter it saves that keyword.
Can someone guide which package to look for.... I found something similar in material ui autocomplete's costomized hook: https://material-ui.com/components/autocomplete/,
but I don't want it to be drop down I want it to be simply be a text field with option as shown in image.
I cannot find with such functionality or I might be looking work as I don't know proper term for it.
Any guidance will be of great help.
I've included material-ui , reactstrap as this is something I have worked with so if there is any other package also let me know
I was also building something like this few days back. This is what I've built till now.
import {
Chip,
FormControl,
Input,
makeStyles,
} from "#material-ui/core";
import React, { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const classes = useStyles();
const [values, setValues] = useState(["test"]);
const [currValue, setCurrValue] = useState("");
const handleKeyUp = (e) => {
console.log(e.keyCode);
if (e.keyCode == 32) {
setValues((oldState) => [...oldState, e.target.value]);
setCurrValue("");
}
};
useEffect(() => {
console.log(values);
}, [values]);
const handleChange = (e) => {
setCurrValue(e.target.value);
};
const handleDelete = ( item, index) =>{
let arr = [...values]
arr.splice(index,1)
console.log(item)
setValues(arr)
}
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<FormControl classes={{ root: classes.formControlRoot }}>
<div className={"container"}>
{values.map((item,index) => (
<Chip size="small" onDelete={()=>handleDelete(item,index)} label={item}/>
))}
</div>
<Input
value={currValue}
onChange={handleChange}
onKeyDown={handleKeyUp}
/>
</FormControl>
</div>
);
}
const useStyles = makeStyles((theme) => ({
formControlRoot: {
display: "flex",
alignItems: "center",
gap: "8px",
width: "300px",
flexWrap: "wrap",
flexDirection: "row",
border:'2px solid lightgray',
padding:4,
borderRadius:'4px',
"&> div.container": {
gap: "6px",
display: "flex",
flexDirection: "row",
flexWrap: "wrap"
},
"& > div.container > span": {
backgroundColor: "gray",
padding: "1px 3px",
borderRadius: "4px"
}
}
}));
Here is the working demo:
One possible way to do this using react-hook-form and Autocomplete using the Chip with renderTags function here is an example:
import {
Box,
TextField,
Autocomplete,
Chip,
} from '#mui/material'
...
const {
handleSubmit,
control,
formState: { errors },
} = useForm()
...
<Box mt={2}>
<Controller
control={control}
name="tags"
rules={{
required: "Veuillez choisir une réponse",
}}
render={({ field: { onChange } }) => (
<Autocomplete
defaultValue={
useCasesData?.tags ? JSON.parse(useCasesData?.tags) : []
}
multiple
id="tags-filled"
options={[]}
freeSolo
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<Chip
variant="outlined"
label={option}
{...getTagProps({ index })}
/>
))
}
onChange={(event, values) => {
onChange(values);
}}
renderInput={(params) => (
<TextField
{...params}
label="Métadonnées"
placeholder="Ecriver les métadonnées"
helperText={errors.tags?.message}
error={!!errors.tags}
/>
)}
/>
)}
/>
</Box>

custom button in inline toolbar that calls custom function

I want to combine the inline toolbar with the example from draft.js to insert a link into the editor.
Thanks to the draft.js plugin FAQ I am able to add a decorator to the draft-js-plugin editor which inserts a link on a button click.
Now I want to put this button inside the inline-toolbar from draft-js-plugins. That doesn't seem to work. This is what I've done so far:
Editor.jsx
...
const inlineToolbarPlugin = createInlineToolbarPlugin({
theme: {buttonStyles, toolbarStyles},
structure: [..., LinkButton]
});
const {InlineToolbar} = inlineToolbarPlugin;
const plugins = [inlineToolbarPlugin];
class RMSEditor extends Component {
...
render() {
return (
<div>
<div className={editorStyles.editor}>
<Editor
editorState={this.state.editorState}
onChange={this.onChange}
plugins={plugins}
decorators={this.decorator}
ref={(element) => {
this.editor = element;
}}
readOnly={this.state.readOnly}
/>
<InlineToolbar />
</div>
</div>
);
}
LinkButton.jsx
import classNames from "classnames/bind";
import React from "react";
import {Glyphicon} from "react-bootstrap";
import styles from "./buttonStyles.less";
const cx = classNames.bind(styles);
const LinkButton = () => {
return (
<div className={cx('buttonWrapper')} onClick={(e) => {
console.log("Div", e);
}}>
<button className={cx('button')} onClick={(e) => {
console.log("Button", e);
}}><Glyphicon glyph="link"/></button>
</div>
)
};
export default LinkButton;
This way, I have managed to get a Button that shows up in the inline toolbar. But when I clicked on that button, nothing happens. I expacted that one of the onClick handlers will fire but that is not the case.
Full source code
Here you can find my full sourcecode as I don't want to put only the relevant parts directly under the questions. Please notice that the code will not work in the run snipped thingy as I have no clue how to get it to work there without setting up the hole wabpack thing.
MentionsEditor.jsx
import {CompositeDecorator, EditorState, RichUtils} from "draft-js";
import {BoldButton, CodeButton, ItalicButton, UnderlineButton, UnorderedListButton} from "draft-js-buttons";
import createInlineToolbarPlugin from "draft-js-inline-toolbar-plugin";
import {defaultSuggestionsFilter} from "draft-js-mention-plugin"; // eslint-disable-line import/no-unresolved
import Editor from "draft-js-plugins-editor"; // eslint-disable-line import/no-unresolved
import React, {Component, PropTypes} from "react";
import {Button} from "react-bootstrap";
import buttonStyles from "./buttonStyles";
import editorStyles from "./editorStyles";
import LinkButton from "./InsertLinkButton";
import toolbarStyles from "./toolbarStyles";
const inlineToolbarPlugin = createInlineToolbarPlugin({
theme: {buttonStyles, toolbarStyles},
structure: [BoldButton, ItalicButton, UnderlineButton, CodeButton, UnorderedListButton, LinkButton]
});
const {InlineToolbar} = inlineToolbarPlugin;
const plugins = [inlineToolbarPlugin];
class RMSEditor extends Component {
constructor(props) {
super(props);
}
decorator = [
{
strategy: function findLinkEntities(contentBlock, callback, contentState) {
contentBlock.findEntityRanges(
(character) => {
const entityKey = character.getEntity();
return (
entityKey !== null &&
contentState.getEntity(entityKey).getType() === 'LINK'
);
},
callback
);
},
component: function (props) {
const {url} = props.contentState.getEntity(props.entityKey).getData();
return (
<a href={url}>
{props.children}
</a>
);
}
}
];
state = {
editorState: EditorState.createEmpty(),
};
onChange = (editorState) => {
this.setState({
editorState,
});
};
editorLink = function (props) {
const {url} = props.contentState.getEntity(props.entityKey).getData();
return (
<a href={url} style={{color: "blue"}}>
{props.children}
</a>
);
};
focus = () => {
this.editor.focus();
};
insertLink = (e) => {
e.preventDefault();
const {editorState} = this.state;
const contentState = editorState.getCurrentContent();
const contentStateWithEntity = contentState.createEntity(
'LINK',
'MUTABLE',
{url: "https://example.com"}
);
const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
const newEditorState = EditorState.set(editorState, {currentContent: contentStateWithEntity});
this.setState({
editorState: RichUtils.toggleLink(
newEditorState,
newEditorState.getSelection(),
entityKey
)
}, () => {
setTimeout(() => {
this.focus()
}, 0);
});
};
render() {
return (
<div>
<Button onClick={this.insertLink}>insert URL</Button>
<div className={editorStyles.editor}>
<Editor
editorState={this.state.editorState}
onChange={this.onChange}
plugins={plugins}
decorators={this.decorator}
ref={(element) => {
this.editor = element;
}}
readOnly={this.state.readOnly}
/>
<InlineToolbar />
</div>
</div>
);
}
}
RMSEditor.propTypes = {
stakeholder: PropTypes.object.isRequired
};
export default RMSEditor;
LinkButton.jsx
import classNames from "classnames/bind";
import React from "react";
import {Glyphicon} from "react-bootstrap";
import styles from "./buttonStyles.less";
const cx = classNames.bind(styles);
const LinkButton = () => {
return (
<div className={cx('buttonWrapper')} onClick={(e) => {
console.log("Div", e);
}}>
<button className={cx('button')} onClick={(e) => {
console.log("Button", e);
}}><Glyphicon glyph="link"/></button>
</div>
)
};
export default LinkButton;
This works for me:
const MyButton = (props) => {
return (
<div className={props.className} onMouseDown={(e)=>e.preventDefault()}>
<button onClick={() => props.createComment('Hello')}>
Comment
</button>
</div>
)
}
const inlineToolbarPlugin = createInlineToolbarPlugin({
structure: [props => <MyButton {...props} createComment={(text) => alert(text)} />]
});
const { InlineToolbar } = inlineToolbarPlugin;
export default class MyComponent extends Component {
constructor(props) {
super(props);
}
render() {
const { editorState } = this.state;
return (
<div className="editor">
<Editor
editorState={editorState}
onChange={this.onChange}
plugins={[inlineToolbarPlugin]}
ref={(element) => { this.editor = element; }}
/>
<InlineToolbar />
</div>
)
}
}
Basically, this is what you need to do:
1) import the inline toolbar plugin (bonus there is a separator in there)
import createInlineToolbarPlugin, {Separator} from 'draft-js-inline-toolbar-plugin'
2) instantciate the plugin like this:
const inlineToolbarPlugin = createInlineToolbarPlugin({
structure: [
BoldButton,
ItalicButton,
UnderlineButton,
CodeButton,
Separator,
],
})
2.1) you can get those components and more from draft-js-buttons
3) add to the array more components (let's add a video button component)
AddVideoButton
this is what it looks like
import React, { Component, PropTypes } from 'react'
import classnames from 'classnames'
import { AtomicBlockUtils, RichUtils } from 'draft-js'
import createVideoPlugin from 'draft-js-video-plugin'
const {types} = createVideoPlugin()
export default class AddVideoButton extends Component {
static propTypes = {
getEditorState: PropTypes.func,
setEditorState: PropTypes.func,
style: PropTypes.shape(),
theme: PropTypes.shape(),
}
// toggleStyle(event){
// const { setEditorState, getEditorState, style } = this.props
//
// event.preventDefault()
// setEditorState(
// RichUtils.toggleInlineStyle(
// getEditorState(),
// style
// )
// )
// }
toggleVideo(event){
const { setEditorState, getEditorState } = this.props
event.preventDefault()
const style = { width: 100}
const editorState = getEditorState()
const currentContent = editorState.getCurrentContent()
const selectionState = editorState.getSelection()
const anchorKey = selectionState.getAnchorKey()
const currentContentBlock = currentContent.getBlockForKey(anchorKey)
const start = selectionState.getStartOffset()
const end = selectionState.getEndOffset()
const src = currentContentBlock.getText().slice(start, end)
if (RichUtils.getCurrentBlockType(editorState) === types.ATOMIC) {
return editorState
}
const contentState = editorState.getCurrentContent()
const contentStateWithEntity = contentState.createEntity(
types.VIDEOTYPE,
'IMMUTABLE',
{ src, style }
)
const entityKey = contentStateWithEntity.getLastCreatedEntityKey()
const newEditorState = AtomicBlockUtils.insertAtomicBlock(editorState, entityKey, ' ')
return setEditorState(newEditorState)
}
render(){
const { theme, style, getEditorState } = this.props
const styleIsActive = () => getEditorState().getCurrentInlineStyle().has(style)
return (
<div
className={theme.buttonWrapper}
onMouseDown={(e)=>e.preventDefault()}
>
<button
className={classnames({
[theme.active]:styleIsActive(),
[theme.button]:true,
})}
onClick={::this.toggleVideo}
>
<svg height="24" viewBox="0 0 120 100" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M111.374,20.922c-3.151-3.159-7.557-5.128-12.374-5.125H29c-4.817-0.002-9.222,1.966-12.375,5.125 c-3.159,3.152-5.128,7.557-5.125,12.375v40.038c-0.002,4.818,1.966,9.223,5.125,12.375c3.152,3.158,7.557,5.129,12.375,5.125h70 c4.817,0.004,9.224-1.967,12.375-5.125c3.159-3.152,5.128-7.557,5.125-12.375V33.296C116.503,28.479,114.534,24.074,111.374,20.922z M104.624,78.959c-1.454,1.447-3.413,2.328-5.624,2.33H29c-2.211-0.002-4.17-0.883-5.625-2.33c-1.447-1.455-2.328-3.414-2.33-5.625 V33.296c0.002-2.211,0.883-4.17,2.33-5.625c1.455-1.447,3.413-2.328,5.625-2.33h70c2.211,0.002,4.17,0.883,5.625,2.33 c1.447,1.455,2.327,3.413,2.329,5.625v40.038C106.952,75.545,106.072,77.504,104.624,78.959z" fill="#232323"/><path d="M77.519,50.744L57.45,39.161c-0.46-0.266-0.971-0.397-1.483-0.397c-0.513,0-1.023,0.131-1.484,0.397 c-0.918,0.528-1.483,1.509-1.483,2.569v23.171c0,1.061,0.565,2.04,1.483,2.57c0.46,0.267,0.971,0.396,1.484,0.396 c0.513,0,1.023-0.13,1.483-0.396l20.069-11.586c0.918-0.531,1.482-1.51,1.482-2.571C79.001,52.253,78.437,51.274,77.519,50.744z" fill="#232323"/>
<path d="M0 0h24v24H0z" fill="none" />
</svg>
</button>
</div>
)
}
}
or
you can imitate anything out of the draft-js-buttons
Let me know if I helped or if you have any other questions