I'm using react-select along with material-ui to make a autocomplete component that looks and functions like the material ones.
I followed the basic setup here
https://material-ui.com/demos/autocomplete/
And then had to tweak to my setup with the data structure the way our API handles, this all works great but now I'm trying to allow the user to create a new option and I can't seem to get it to display the option back
Here is the component as is
import React, { Component } from 'react';
import { withStyles } from '#material-ui/core/styles';
import styles from "./styles";
import MenuItem from '#material-ui/core/MenuItem';
import Select from 'react-select';
import 'react-select/dist/react-select.css';
import Typography from '#material-ui/core/Typography';
import ArrowDropDownIcon from '#material-ui/icons/ArrowDropDown';
import ArrowDropUpIcon from '#material-ui/icons/ArrowDropUp';
import Input from '#material-ui/core/Input';
import LinearProgress from '#material-ui/core/LinearProgress';
import classNames from 'classnames';
class Option extends React.Component {
handleClick = event => {
this.props.onSelect(this.props.option, event);
};
render() {
const { children, isFocused, isSelected, onFocus } = this.props;
return (
<MenuItem
onFocus={onFocus}
selected={isFocused}
disabled={isSelected}
onClick={this.handleClick}
component="div"
style={{
fontWeight: isSelected ? 500 : 400,
}}
>
{children}
{children === 'LOADING...' &&
<LinearProgress style={{ position: 'absolute',width: '100%',bottom: '0',left: '0',height: '2px', }} />
}
</MenuItem>
);
}
}
class SelectWrapped extends Component {
render() {
const { classes, ...other } = this.props;
return (
<Select
optionComponent={Option}
noResultsText={<Typography>{'No results found'}</Typography>}
clearRenderer={() => {}}
arrowRenderer={arrowProps => {
return arrowProps.isOpen ? <ArrowDropUpIcon /> : <ArrowDropDownIcon />;
}}
valueComponent={valueProps => {
const { children } = valueProps;
console.log(children)
return <div className="Select-value">{children}</div>;
}}
{...other}
/>
);
}
}
class SelectCreatable extends Component {
render() {
const { classes, ...other } = this.props;
console.log(this.props)
return (
<Select.Creatable
optionComponent={Option}
noResultsText={<Typography>{'No results found'}</Typography>}
clearRenderer={() => {}}
arrowRenderer={arrowProps => {
return arrowProps.isOpen ? <ArrowDropUpIcon /> : <ArrowDropDownIcon />;
}}
valueComponent={valueProps => {
const { children } = valueProps;
return <div className="Select-value">{children}</div>;
}}
{...other}
/>
);
}
}
class AutoCompleteComponent extends Component {
state = {
value: null,
};
handleChange = value => {
this.setState({ value: value })
const foundSuggestion = this.props.suggestions.find((s) => s.id === value);
if (this.props.creatable) {
this.props.onChange(foundSuggestion || {
[this.props.labelPropName]: value
})
} else {
this.props.onChange(foundSuggestion)
}
}
onChange = value => {
this.props.onChange(this.props.suggestions.find((s) => s.id === value))
};
render() {
const { classes, labelPropName, creatable } = this.props;
const suggestions = this.props.suggestions.map(suggestion => ({
value: suggestion.id,
label: this.props.labelFunction(suggestion)
}))
return (
<div className={classNames(classes.root,this.props.className)}>
<Input
fullWidth
inputComponent={creatable ? SelectCreatable : SelectWrapped}
value={this.state.value}
onChange={(value) => this.props.showValue ? this.handleChange(value) : this.onChange(value)}
placeholder={this.props.placeholder}
classes={{
input: classes.input,
...this.props.InputClasses
}}
inputProps={{
classes,
simpleValue: true,
options: suggestions
}}
/>
</div>
);
}
}
export default withStyles(styles, { withTheme: true })(AutoCompleteComponent);
I setup a stackblitz with a running example and some options. If you type and select an option you'll see it display the selected option, but if you type a new one and hit enter it doesn't display the option and I'm trying to figure out why, some help on what I'm doing wrong here would be super helpful
https://wmazc4.stackblitz.io
I thinks the bug is with your data conversion id to value messes with your react-select component
I went through a demo from an exact copy of your code (since your example wasn't working)
here is my example: https://codesandbox.io/s/p9j3xz843m
here I used
inputProps={{
classes,
name: "react-select-single",
instanceId: "react-select-single",
simpleValue: true,
options: colourOptions,
valueKey: "id",
labelKey: "label"
}}
find that I used valueKey and labelKey props to convert data you can find more from the live example
hope this will help you. please let me know if you want more clarifications.
Related
I'm trying to use multiple rich text editors in a React form. I built the editor component using draft-js and also I integrated the inline toolbar from draft-js-plugins. Because this is a react-hook-form I wrapped the editor inside a Controller component.
The problem I have is that the InlineToolbar is displayed only for the last editor component in page.
Based on the draft-js-plugins documentation the initialization of the toolbar should happen outside the component so this is what I did:
const inlineToolbarPlugin = createInlineToolbarPlugin();
const { InlineToolbar } = inlineToolbarPlugin;
const plugins = [inlineToolbarPlugin];
function RichTextEditor({ control, name }) {
return (
<div>
<Controller
name={name}
control={control}
render={({ field: { value, onChange } }) => {
const newValue = value || EditorState.createEmpty();
return (
<>
<Editor
editorState={newValue}
onChange={onChange}
plugins={plugins}
/>
<InlineToolbar />
</>
);
}}
/>
</div>
);
}
A complete CodeSandbox example here: CodeSandbox link
Each editor get's its own plugins.
You can solve this issue ether by creating different plugin for each editor instance and pass them to the editor OR with create a function for creating a plugin inside the editor component and every time we init a editor we create a new plugin instance
So, this is the first solution:
const inlineToolbarPlugin1 = createInlineToolbarPlugin();
const { InlineToolbar:Tool1 } = inlineToolbarPlugin1;
const inlineToolbarPlugin2 = createInlineToolbarPlugin();
const { InlineToolbar:Tool2 } = inlineToolbarPlugin2;
And pass them into your custom editor components.
Second solution:
import React from "react";
import { Controller } from "react-hook-form";
import { EditorState } from "draft-js";
import PropTypes from "prop-types";
import Editor from "#draft-js-plugins/editor";
import createInlineToolbarPlugin from "#draft-js-plugins/inline-toolbar";
import "#draft-js-plugins/inline-toolbar/lib/plugin.css";
import "draft-js/dist/Draft.css";
const createtoolbarplugin = () => {
const InlineToolbarPlugin = createInlineToolbarPlugin();
const InlineToolbar = InlineToolbarPlugin.InlineToolbar;
return {
InlineToolbarPlugin,
InlineToolbar
};
};
function AnotherRichTextEditor({ control, aName }) {
const [{ InlineToolbarPlugin, InlineToolbar }] = React.useState(() => {
const { InlineToolbar, InlineToolbarPlugin } = createtoolbarplugin();
return {
InlineToolbarPlugin,
InlineToolbar
};
});
return (
<div
style={{
border: "1px solid #ccc",
minHeight: 30,
padding: 10
}}
>
<Controller
name={aName}
control={control}
render={({ field: { value, onChange } }) => {
const newValue = value || EditorState.createEmpty();
return (
<>
<Editor
editorState={newValue}
onChange={onChange}
plugins={[InlineToolbarPlugin]}
/>
<InlineToolbar />
</>
);
}}
/>
</div>
);
}
AnotherRichTextEditor.propTypes = {
control: PropTypes.object,
aName: PropTypes.string
};
export default AnotherRichTextEditor;
Hope That's help
I am using rsuitejs for components, and in a form I am creating I have a custom slider called MotivationSlider which isn't updating the data in the form it's in when the value is changed. The custom Slider looks like this:
import React, { useState } from 'react';
import { Slider } from 'rsuite';
const MotivationSlider = () => {
const [motivation, setMotivation] = useState(2);
return (
<Slider
min={0}
max={4}
value={motivation}
graduated
progress
className="custom-slider"
onChange={v => setMotivation(v)}
renderMark={mark => {
if ([0, 4].includes(mark)) {
return <span>{mark === 0 ? 'Not Very' : 'Highly!'}</span>;
}
return null;
}}
/>
);
};
export default MotivationSlider;
It is wrapped in a Custom Field which looks like this:
// CustomField.js:
import React from 'react';
import { FormGroup, ControlLabel, FormControl, HelpBlock } from 'rsuite';
const CustomField = ({ name, message, label, accepter, error, ...props }) => {
return (
<FormGroup className={error ? 'has-error' : ''}>
<ControlLabel>{label} </ControlLabel>
<FormControl
name={name}
accepter={accepter}
errorMessage={error}
{...props}
/>
<HelpBlock>{message}</HelpBlock>
</FormGroup>
);
};
export default CustomField;
// FormPage.js:
<CustomField
accepter={MotivationSlider}
name="motivation"
/>
When I change the slider value, the form data does not change, however if I use the CustomField with a normal Slider like this, the form value does change.
<CustomField
accepter={Slider}
name="motivation"
min={0}
max={4}
/>
What am I doing wrong here?
Form custom controls need to implement the attributes onChange, value, and defaultValue.
import React, { useState } from "react";
import { Slider } from "rsuite";
const MotivationSlider = React.forwardRef((props, ref) => {
const { value: valueProp, defalutValue, onChange } = props;
const [motivation, setMotivation] = useState(defalutValue);
const value = typeof valueProp !== "undefined" ? valueProp : motivation;
return (
<Slider
ref={ref}
min={0}
max={4}
value={value}
graduated
progress
className="custom-slider"
onChange={(v) => {
onChange(v);
setMotivation(v);
}}
renderMark={(mark) => {
if ([0, 4].includes(mark)) {
return <span>{mark === 0 ? "Not Very" : "Highly!"}</span>;
}
return null;
}}
/>
);
});
export default MotivationSlider;
I use Two package
slate-react and emoji-mart
I want to when choose an Emoji , it puts on my editor.
import React from "react";
import { render } from "react-dom";
import { Editor } from "slate-react";
import { initialValue } from "./initialValue";
// Define our app...
class MyEditor extends React.Component {
// Set the initial value when the app is first constructed.
state = {
value: initialValue
};
// On change, update the app's React state with the new editor value.
onChange = ({ value }) => {
this.setState({ value });
};
onKeyDown = (event, change) => {
// This used to have stuff in it, but I moved it all to plugins.
};
clickMe=()=>{
this.setState({ value : this.state.value });
};
// Render the editor.
render() {
return (
<div>
<h1 onClick={this.clickMe}>Slate Editor Demo</h1>
<div style={{ border: "1px solid black", padding: "1em" }}>
<Editor
value={this.state.value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
renderNode={this.renderNode}
spellCheck={false}
/>
</div>
</div>
);
}
}
export default MyEditor;
import React,{useState} from 'react';
import 'emoji-mart/css/emoji-mart.css';
import { Picker } from 'emoji-mart';
function Emoji() {
const [emoji,setEmoji] = useState(null);
const addEmoji = (e) => {
setEmoji(e.native)
};
return <Picker onSelect={addEmoji} />
}
export default Emoji;
Try passing the editor ref to picker. Then in Emoji component in addEmoji method, try editorRef.current.InsertText(e.native). After hours of trying to solve this:
const YourTextEditor = props => {
const editor = createEditor();
const addEmoji = async emoji => {
await setTimeout(() => {
editor.focus();
}, 100);
editor.insertText(emoji.native);
};
return (
<>
<Editor
value={initialValue}
/>
<Emoji addEmoji={addEmoji} />
</>
);
};
const Emoji = props => {
return (<Picker onSelect={e => props.addEmoji(e)} />);
};
I'm trying to convert this functional component to class based component. I have tried for several hours but could not find where to place these const variables in component. If someone could write it out in class based component it will highly appreciated.
const useStyles = makeStyles(theme => ({
typography: {
padding: theme.spacing(2),
},
}));
function SimplePopper() {
const classes = useStyles();
const [anchorEl, setAnchorEl] = React.useState(null);
function handleClick(event) {
setAnchorEl(anchorEl ? null : event.currentTarget);
}
const open = Boolean(anchorEl);
const id = open ? 'simple-popper' : null;
return (
<div>
<Button aria-describedby={id} variant="contained" onClick={handleClick}>
Toggle Popper
</Button>
<Popper id={id} open={open} anchorEl={anchorEl} transition>
{({ TransitionProps }) => (
<Fade {...TransitionProps} timeout={350}>
<Paper>
<Typography className={classes.typography}>The content of the Popper.</Typography>
</Paper>
</Fade>
)}
</Popper>
</div>
);
}
export default SimplePopper;
import React, { Component } from "react";
import { createMuiTheme } from "#material-ui/core/styles";
import Typography from "#material-ui/core/Typography";
import Button from "#material-ui/core/Button";
import Fade from "#material-ui/core/Fade";
import Paper from "#material-ui/core/Paper";
import Popper from "#material-ui/core/Popper";
import { withStyles } from "#material-ui/styles";
const theme = createMuiTheme({
spacing: 4
});
const styles = {
typography: {
padding: theme.spacing(2)
}
};
class SimplePopper extends Component {
constructor(props) {
super(props);
this.state = { anchorEl: null, open: false };
}
flipOpen = () => this.setState({ ...this.state, open: !this.state.open });
handleClick = event => {
this.state.ancherEl
? this.setState({ anchorEl: null })
: this.setState({ anchorEl: event.currentTarget });
this.flipOpen();
};
render() {
const open = this.state.anchorEl === null ? false : true;
console.log(this.state.anchorEl);
console.log(this.state.open);
const id = this.state.open ? "simple-popper" : null;
const { classes } = this.props;
return (
<div>
<Button
aria-describedby={id}
variant="contained"
onClick={event => this.handleClick(event)}
>
Toggle Popper
</Button>
<Popper
id={id}
open={this.state.open}
anchorEl={this.state.anchorEl}
transition
>
{({ TransitionProps }) => (
<Fade {...TransitionProps} timeout={350}>
<Paper>
<Typography className={classes.typography}>
The content of the Popper.
</Typography>
</Paper>
</Fade>
)}
</Popper>
</div>
);
}
}
export default withStyles(styles)(SimplePopper);
First thing one need to understand is, how class based and functional components work. Also, when and where you use it.
In short, I can say functional components are Used for presenting static data. And class based are Used for dynamic source of data.
Here are few links for your reference.
Class based component vs Functional components what is the difference ( Reactjs ) and React functional components vs classical components
To answer your specific question.
import React, { Component } from 'react';
import { withStyles, makeStyles } from '#material-ui/styles';
const useStyles = makeStyles(theme => ({
typography: {
padding: theme.spacing(2),
},
}));
class SimplePopper extends Component {
constructor(props){
super(props)
this.state = { anchorEl: null, setAnchorEl: null }; <--- Here see the state creation
this.handleClick= this.handleClick.bind(this);
}
handleClick(event) {
const { anchorEl, setAnchorEl } = this.state; <--- Here accessing the state
setAnchorEl(anchorEl ? null : event.currentTarget);
}
render() {
const { anchorEl, setAnchorEl } = this.state; <--- Here accessing the state
const open = Boolean(anchorEl);
const id = open ? 'simple-popper' : null;
const { classes } = this.props;
return (
<div>
............Rest of the JSX............
</div>
);
}
}
export default withStyles(useStyles)(SimplePopper);
Note that here I've used withStyles to wrap the style to your component. So, that the styles will be available as classNames.
Explore the difference and convert the rest
This is more enough to begin with.
I'm using the official Semantic UI React components to create a web application. I have a form on a search page, which contains an input field.
import React from 'react'
import {Form} from "semantic-ui-react";
import RadiusOfSearchInput from "./RadiusOfSearchInput";
const NearbyShopsSearchForm = (props) => {
const { onSubmit, size, action, onChange, value, style } = props
return (
<Form onSubmit={onSubmit}>
<RadiusOfSearchInput size={size}
action={action}
onChange={onChange}
value={value}
style={style} />
</Form>
)
}
export default NearbyShopsSearchForm
The component that uses the form is as shown below:
import React, { Component } from 'react'
import {Menu, Container, Segment} from 'semantic-ui-react'
import ShopCardList from "../components/ShopCardList";
import axios from 'axios';
import NearbyShopsSearchForm from "../components/NearbyShopsSearchForm";
class NearbyShopsPage extends Component {
constructor(props) {
super(props)
this.state = {
radius: '',
shops: []
}
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
handleChange = (e, { value } ) => {
this.setState({ radius: value })
}
handleSubmit = (e, data) => {
const { radius } = this.state
const url = `/api/shops/#33.846978,-6.775816,${radius}`
axios.get(url)
.then((response) => {
this.setState({ shops: response.data })
})
.catch((error) => {
console.log(error)
})
e.preventDefault()
}
render() {
const { radius } = this.state
return (
<Segment basic>
<Menu fixed='top' size='huge' borderless>
<Menu.Item>
<NearbyShopsSearchForm onSubmit={this.handleSubmit}
size='large'
action={{ color: 'teal', content: 'Search', size: 'small' }}
onChange={this.handleChange}
value={radius}
style={{ width: '17.5em' }} />
</Menu.Item>
</Menu>
<Container style={{ marginTop: '5.5em' }}>
<ShopCardList shops={this.state.shops}/>
</Container>
</Segment>
)
}
}
export default NearbyShopsPage
I want to validate the form so it won't submit values other than decimals, which are valid values to represent a distance. I didn't find validation support in the SUIR official documentation. I'm aware of the redux-form possibility, but i fail to implement it correctly. what's the recommended and simplest way to implement the data validation feature?