Access inputValue in <Option /> component in react-select 2.* - reactjs

I'm trying to highlight (underline or bold) the text that was entered within all my displayed options. This was quite straightforward in react-select 1.* but I fail in 2.*. when trying to access the inputValue within the custom component.
This code snippet illustrates my attempt when inputValue would be available as in props:
import React from 'react'
import match from 'autosuggest-highlight/match/index'
import parse from 'autosuggest-highlight/parse/index'
export default props => {
// Extract matching parts from the inputValue (value typed into text field)
const matches = match(props.label, props.inputValue)
const parts = parse(props.label, matches)
return (
<div>
{parts.map((part, index) => {
return !part.highlight ? (
<span>{part.text}</span>
) : (
<strong>{part.text}</strong>
)
})}
</div>
)
}

If you were using a custom Option component then you could access the inputValue from within the props.selectProps.
const Option = props => {
console.log('props', props);
const { innerProps, innerRef, selectProps, data } = props;
return (
<div ref={innerRef} {...innerProps}>
// generate your highlighted Option from data.label here, using
// selectProps.inputValue
</div>
);
};
// ...
<Select {...otherProps} components={{Option}} />

Related

How can we set default value for React RRule Generator in react-admin?

I am using react-admin and react-rrule-generator (https://github.com/Fafruch/react-rrule-generator). Create / Adding records is working fine while using rrule widget. But whenever I try to edit a record, the widget should have its values automatically filled based on the record's values. But the value is always the default one provided by the widget itself. Here is my code:
main_file.jsx
export const JobCreate = (props) => {
return (
<Create {...props}>
<SimpleForm>
<CustomRRuleInput name="recurrency" label="Recurrency" />
</SimpleForm>
</Create>
)
}
recurrency_field.jsx
export const CustomRRuleInput = (props) => {
const {
input: { onChange },
meta: { touched, error },
} = useInput(props)
return (
<Labeled label={props.label}>
<RRuleGenerator
onChange={onChange}
name={props.name}
/>
</Labeled>
)
}
If I add value={props.record.recurrency} in RRuleGenerator component, I can't change values because I kind of fixed / hardcoded its value which is constant even if I try to change them. If this widget had a prop called defaultValue then it would have worked out!
How can I achieve this?
If you check closely the documentation's Inputs/Writing your own input part you will notice that custom input compoenents using either useField or useInput hooks still receive the source prop which is passed inside the input as part of the hook parameters.
Try this:
Inside main_file.jsx
<CustomRRuleInput source="recurrency" label="Recurrency" />
Inside recurrency_field.jsx
const {
input: { name, onChange },
meta: { touched, error },
} = useInput(props)
return (
<Labeled label={props.label}>
<RRuleGenerator
onChange={onChange}
name={name}
/>
</Labeled>
)
Never mind I did it! I can use this for creation as well as updating records. I also used rrule library for converting rrule to human readable text which gets displayed in TextInput field just below RRule widget. The text dynamically changes when you change data in RRule widget.
recurrency_field.jsx
import RRuleGenerator from "react-rrule-generator"
import React, { useState } from "react"
import { useInput, Labeled, TextInput } from "react-admin"
import { rrulestr } from "rrule"
export const CustomRRuleInput = (props) => {
const record = props.record
const {
input: { onChange },
} = useInput(props)
const [state, setState] = useState(record[props.name])
return (
<>
<Labeled label={props.label}>
<RRuleGenerator
onChange={(val) => {
setState(val)
onChange(val)
}}
value={state}
name={props.name}
/>
</Labeled>
<TextInput
fullWidth
disabled
label={"RRule Text"}
value={state ? rrulestr(state).toText() : ""}
/>
</>
)
}
main_file.jsx
<CustomRRuleInput name="recurrency" label="Recurrency(r rule)" />

Remove current index from array in React onClick

I'm fairly new to React. I have built a component that adds an input field onClick. What I need to do is add functionality to a button underneath each new input that deletes that specific input. For example, if 3 inputs are created 1,2,3 and input 2 is deleted 1 and 3 remain.
My code contains a function named onRemoveChild() that has some commented out code of my initial attempt at solving the problem using closest(). The issue with this is that state isn't correctly updated so after an input is removed the field label numbers are out of sync.
Thanks in advance and let me know if more explanation is needed.
import React from 'react'
import {
Button,
TextField,
Typography,
} from '#material-ui/core'
class TextFieldAddNew extends React.Component {
state = {
numChildren: 0
}
render () {
const children = [];
for (var i = 0; i < this.state.numChildren; i += 1) {
children.push(<ChildComponent key={i} number={i+2} removeChild={this.onRemoveChild} />);
};
return (
<ParentComponent addChild={this.onAddChild} theCount={this.state.numChildren}>
{children}
</ParentComponent>
);
}
onAddChild = () => {
this.setState({
numChildren: this.state.numChildren + 1
});
}
onRemoveChild = () => {
document.getElementById('removeInput').closest('div').remove();
}
}
const ParentComponent = (props) => (
<div className="card calculator">
<div id="childInputs">
{props.children}
</div>
{
props.theCount >= 4 ? (
<div className="warning">
We recommend adding no more that 5 opt-in's
</div>
) : ''
}
<Button
className="addInput"
onClick={props.addChild}>
+ Add another option
</Button>
</div>
);
const ChildComponent = (props) => (
<>
<TextField
id={'opt-in-' + props.number}
label={'Opt-in ' + props.number}
name={'opt-in'}
variant="outlined"
size="small"
margin="normal"
color="secondary"
className="halfInput"
/>
<Typography id="removeInput" className="removeInput"
onClick={props.removeChild}>
- Remove option
</Typography>
</>
);
export default TextFieldAddNew;
You can pass the index as part of calling removeChild like below:
children.push(<ChildComponent key={i} number={i+2} removeChild={()=>{this.onRemoveChild(i)}}
Also instead of keeping the numChildren in the state, you should keep the children. This way it would be easy to remove and add nodes to it. Then you can easily do:
children.splice(i,1) and then update the state, this way auto render will update the dom for you.

How to get the component's return value

I have some code like this:
import React from "react";
import "./styles.css";
const t = () => ["test1", "test2"];
const Translate = () => t("myText");
const MyComponent = ({ children }) => {
console.log(children);
return (
children &&
typeof children === "object" &&
Array.isArray(children) &&
children.map(val => (
<>
{`${val}__attached`}
<br />
</>
))
);
};
export default function App() {
return (
<div className="App">
<MyComponent>
<Translate />
</MyComponent>
</div>
);
}
When I check the console, I get a React element printed. But I want its return value.
For example, if I replace this code:
<MyComponent>
<Translate />
</MyComponent>
with
<MyComponent>
{t()}
</MyComponent>
Then I get an array with 2 objects printed on console. I want to see this same output in my first case.
I want it, because I want to loop through the values returned by Translate and generate output based on that.
Thank you.
Here is the codesandbox link:
You cannot achieve what you want. Indeed, when you write {t()} you actually provide an array as a children for MyComponent and can therefore map its values.
However, as soon as you write <Translate/>, children of MyComponent is no longer an array but a React Element (HTML element so to speak) (print children in MyComponent to check) making your test always false (so it prints nothing)
Try to call <Translate/> outside of MyComponent and you will see "test1test2" displayed on your page. It is no longer an array.
Instead of writing this:
<MyComponent>
<Translate />
</MyComponent>
I think you should do the other way around to be able to easily manipulate what comes from Translate and passing it to MyComponent:
<Translate>
{values => <MyComponent values={values}>}
</Translate>
with Translate being:
const Translate = ({ children }) => children(t("myText"));
and MyComponent:
const MyComponent = ({ values }) => {
return (
Array.isArray(values) &&
values.map(val => (
<>
{`${val}__attached`}
<br />
</>
))
);
};
Live demo here

React Select - Multi Select custom way to display multiple options

I am looking to customize the multiselect and the way we create the display of showing selected options.
Right now, with many options selected the select component takes up a prohibitive amount of space for certain UIs. See example:
I'd like to utilize the out of the box chip display for selected options within the input, but I only want to show only a few selected options (like 3/4 max) and then add a "badge" count for the number of selected options that aren't shown in the value container in the input. The options that are selected but are past the max number of chips allowed to show in the input should show as selected within the dropdown list, while the chips that do show's values should not show in our dropdown.
I've implemented part of this with using a custom ValueContainer to show only the first few chip selections, and then adding a count of additional/"overflow" selections. I'm unsure of how I can utilize the prop hideSelectedOptions to achieve this to show selected items in the list only when my max is met without showing all of them since this prop takes a boolean.
Here's what I have so far: https://codesandbox.io/s/custom-react-select-sjtib
import React, { Component } from "react";
import Select, { components } from "react-select";
import { colourOptions } from "./docs/data";
import "./example.css";
class CustomSelect extends Component {
state = {
values: []
};
handleChange = values => {
this.setState({ values });
};
render() {
const { values } = this.state;
return (
<div>
<Select
hideSelectedOptions={values.length < 3 ? true : false}
isMulti
options={colourOptions}
onChange={this.handleChange}
value={values}
components={{ ValueContainer }}
/>
</div>
);
}
}
export default CustomSelect;
const ValueContainer = ({ children, getValue, ...props }) => {
let maxToShow = 3;
var length = getValue().length;
let displayChips = React.Children.toArray(children).slice(0, maxToShow);
let shouldBadgeShow = length > maxToShow;
let displayLength = length - maxToShow;
return (
<components.ValueContainer {...props}>
{!props.selectProps.inputValue && displayChips}
<div className="root">
{shouldBadgeShow &&
`+ ${displayLength} item${length != 1 ? "s" : ""} selected`}
</div>
</components.ValueContainer>
);
};
I would personally keep hideSelectedOptions={false} and go for styles property usage (options property to be more exact) and setting display: 'none' for the ones which shouldn't be visible:
const styles = {
option: (base, value) => {
return (shouldBeShown(value) ? { ...base } : { display: 'none'});
}
};
shouldBeShown(value) is a custom function for checking if the particular option should be shown.
In order to get option data you can use value.data.
Then you can set styles={styles} in Select component:
<Select
hideSelectedOptions={false}
isMulti
styles={styles}
onChange={this.handleChange}
options={options}
value={values}
components={{ ValueContainer }}
/>

How to show a block of collapsible text on click of button

I am trying to implement a collapsible component. I have designed it such as, on click of a button, a block of dynamic text will appear. I made a functional component and using the tags in a class. The name of the component is, CustomAccordion.jsx and using this component in Container.jsx
I have tried to create a button and a function for onClick event.
Part of the CustonAccordion.jsx
const handleToggle = () : string =>{
let content = this.nextElementSibling;
if (content.style.maxHeight){
content.style.maxHeight = null;
}else{
content.style.maxHeight = content.scrollHeight +'px';
}
}
export default function CustomAccordion(props: PropType): React.Component<*> {
const { title, children } = props
return(
<div>
<AccordionButton onClick={() => this.handleToggle()}>{title}</AccordionButton>
<AccordionContent>
<p>{children}
</p>
</AccordionContent>
</div>
)
}
Part of calling Container.jsx
<CustomAccordion title = {this.props.name}>
<p>This is the text passed to component.</p>
</CustomAccordion>
<br />
This does not show the expanded text and it seems that the click event does not work properly. I am very new in react, guessing the syntax might be incorrect.
In react you should generally try to avoid touching DOM directly unless you really have to.
Also you are accessing the handleToggle function wrongly. It should be onClick={() => handleToggle()} because this in your case is window/null and so it has no handleToggle method.
Instead you can use a stateful class component to achieve the same thing.
export default class CustomAccordion extends React.Component {
state = {show: false};
toggle = () => this.setState({show: !this.state.show});
render() {
const {title, children} = this.props;
const {show} = this.state;
return (
<div>
<AccordionButton onClick={this.toggle}>{title}</AccordionButton>
{show && (
<AccordionContent>
<p>{children}</p>
</AccordionContent>
)}
</div>
)
}
}
If you want to have some kind of animation, you can set different className based on the show state instead of adding/removing the elements.

Resources