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;
Related
I want to test a custom created Filter in JEST using react testing library. Here is the code:
Filter.js
import React, { useContext } from "react";
import { FormGroup, List, ListItem } from "#material-ui/core";
import FullAccordion from "../../components/shared/accordion/FullAccordion";
import { Context } from "../../utils/store/context/store";
import RenderTextBox from "../../components/shared/fields/Textbox";
import RenderDropDown from "../../components/shared/fields/Dropdown";
import RenderCheckBox, { RenderCheckBoxList } from "../../components/shared/fields/Checkbox";
import useMatchMaking from "../../utils/hooks/useMatchMaking";
import RadioInput from "../../components/shared/fields/RadioInput";
const Filter = ({ field, classes }) => {
const [state, dispatch] = useContext(Context);
const { handleChangeFilters: handleChange } = useMatchMaking();
const fieldsParam = { field, classes, state, handleChange };
return (
<FullAccordion classes={classes} title={field.title}>
<FormGroup row className={classes.width_100}>
<List className={`${classes.List} ${classes.width_100}`}>
<ListItem disableGutters className={classes.ListItem}>
{
field.html_type === "Email" || field.html_type === "Text" || field.html_type === "Textbox" || field.html_type === "Password"
? RenderTextBox({ ...fieldsParam, value: `reg_${field.id}` }) : null
}
{
field.html_type === "Custom Json Radio"
? RadioInput({ ...fieldsParam, value: `reg_${field.id}` })
: null
}
{
field.html_type === "Custom Json Drop Down" || field.html_type === "Dropdown" || field.html_type === "Custom Json Drop Down with Multi Select Options"
? RenderDropDown({ ...fieldsParam, value: `reg_${field.id}` })
: null
}
{
field.html_type === "Checkbox" || field.html_type === "Single Checkbox" || field.html_type === "Terms & Condition / NewsLetter checkbox"
? RenderCheckBox({ ...fieldsParam, value: `reg_${field.id}` })
: null
}
{
field.html_type === "Checkbox list"
? Object.keys(field.dropdown_options).map((data, index) => {
return <RenderCheckBoxList
key={index} data={data} classes={classes} field={field} handleChange={handleChange} state={state} value={`reg_${data}`}
/>
})
: null
}
</ListItem>
</List>
</FormGroup>
</FullAccordion>
)
}
export default Filter;
and for the time being lets say I am only talking about TextBox field and I want to test it properly if it is working fine what assertions should I make and what would be the test case. My renderTextbox component so far is :
RenderTextBox.js:
import { TextField } from "#material-ui/core";
const RenderTextBox = ({ field: { title }, value, classes, state, handleChange }) => {
const name = value;
return (
<TextField
className={classes.width_100}
color="primary"
id={name}
name={name}
// placeholder={placeholder}
// label={title}
variant="outlined"
value={(Object.keys(state.filters).length > 0 && state.filters[value]) || ""}
onChange={(e) => handleChange(e.target.value, name)}
/>
)
}
export default RenderTextBox;
But how to create its test cases like If the component is rendered and then I want to test the fields such as if it is an email field then it should properly validate email text and not accept any other special character. What would be the possible test code for that?
What I have tried for simple email field if it is getting valid input or not is below:
Filter.test.js
import React from 'react';
import ReactDOM from "react-dom";
import Filter from "../../containers/matchmaking/Filter";
import {cleanup, render} from "#testing-library/react";
import {act, renderHook} from "#testing-library/react-hooks";
import RenderTextBox from "../../components/shared/fields/Textbox";
import renderer from 'react-test-renderer';
import {BrowserRouter} from "react-router-dom";
import {UserProvider} from "../../utils/store/context/UserContext";
import {Store} from "../../utils/store/context/store";
import Attendee from "../../pages/Attendee";
import classes from "*.module.css";
import {matchMakingStyle} from "../../utils/hooks/useApplyStyles";
describe("component: Filter", () => {
afterEach(() => {
cleanup();
});
test('Component: Filter Textbox component renders', () => {
jest.fn(()=>{const styles = matchMakingStyle(); const classes = styles();});
render(<BrowserRouter>
<UserProvider>
<Store>
<Filter field={{html_type:'Email',title:'Textbox'}} classes={classes}/>
</Store>
</UserProvider>
</BrowserRouter>);
});
test('it should check TextBox field is working fine', () => {
let wrapped = render(<BrowserRouter>
<UserProvider>
<Store>
<Filter field={{html_type:'Email',title:'EmailTextbox'}} classes={classes}/>
</Store>
</UserProvider>
</BrowserRouter>);
expect(wrapped.getAllByText("Email").get(0).props.value).toEqual("something#test.com");
});
});
But I am not able to get anything out of this. Basically, I want to test all the fields which are in the code above
im trying to show the p element when the input filed has value "the user writes something in input field" and hides when the input is empty
import React, {useState} from 'react'
function textInput() {
const [isOpen, setIsOpen] = useState(false)
return (
<>
<input type="text" onKeyUp={() => setIsOpen(!isOpen)} />
{
isOpen ?
<p>result</p>
: null
}
</>
)
}
export default textInput
Rather than using an isOpen prop, consider maintaining the text in state. Then, if the text is not empty, show the <p> component:
import React, { useState } from "react";
function textInput() {
const [text, setText] = useState("");
return (
<>
<input
type="text"
value={text}
onChange={(e) => {
setText(e.target.value);
}}
/>
{text && <p>result</p>}
</>
);
}
export default textInput;
Please write code like below. It works.
import React, { useState } from "react";
function textInput() {
const [inputText, setInputText] = useState("")
return (
<>
<input type="text" onChange={ (e) => {
setInputText(e.target.value)
}
}/>
{ (inputText !== "") && <p>result: {inputText}</p> }
</>
)
}
export default textInput;
How to reset a react-select when the options is changed, this happen because im using chaining select, so my second select options will change based on my first select, what im trying to do is reset back the select to "please select" when my second option already picked before, im using react-select with react-hook-form
import React, { useState, useEffect } from 'react';
import { default as ReactSelect } from 'react-select';
import { FormGroup, Label } from 'reactstrap';
import { useFormContext, Controller } from 'react-hook-form';
import { ErrorMessage } from '#hookform/error-message';
export default function Select(props) {
const {
label,
isMulti,
note,
// isDisabled,
// withDefaultValue,
options,
isClearable,
name,
placeholder = 'Pilihan'
} = props;
const rhfContext = useFormContext(); // retrieve all hook methods
const { control, errors } = rhfContext || {};
const [elOptions, setElOptions] = useState([]);
useEffect(() => {
setElOptions(options);
}, [options]);
return (
<FormGroup>
{label && <Label htmlFor={name || ''}>{label}</Label>}
<Controller
as={ReactSelect}
name={name}
control={control}
options={elOptions}
placeholder={placeholder}
styles={customStyles}
{...(isMulti ? { isMulti: true } : {})}
{...(isClearable ? { isClearable: true } : {})}
classNamePrefix="react-select-pw"
className="react-select-container"
/>
{note && <span>{note}</span>}
<ErrorMessage
name={name}
errors={errors}
render={() => {
return <p className="err-msg">pilih salah satu</p>;
}}
/>
</FormGroup>
);
}
Basically you need to handle the onChange of your react-select
const funcComponent = () => {
const [firstOptions, setFirstOptions] = useState({});
const [secondOptions, setSecondOptions] = useState({});
useEffect(() => {
//Here dispatch your defined actions to load first select options
setFirstOptions(response-data)
})
const handleFirstOptions = selectedVal => {
//Here dispatch your defined action to load second select options
setSecondOptions(response-data)
}
const handleSecondOptions = selectedVal => {
//Your action to perform
}
return (
<Label>First Option Field</Label>
<Select
options={firstOptions}
onChange={handleFirstOptions}
/>
Label>Second Option Field</Label>
<Select
options={secondOptions}
onChange={handleSecondOptions}
/>
)}
I would like to implement Algolia search with Ant Design Autocomplete. But I get Cannot read property 'focus' of null error when I try to extract the SearchInput component (without extraction, i. e. when I leave it in the same file, it works fine). Here is the working code:
import React, { useState } from 'react'
import { AutoComplete, Input } from 'antd'
const SearchAutocomplete = connectAutoComplete(
({ hits, currentRefinement, refine }) => {
...
return (
<AutoComplete
options={options}
onSelect={onSelect}
onSearch={handleSearch}
open={open}
>
<Input
value={currentRefinement}
onChange={e => refine(e.currentTarget.value)}
/>
</AutoComplete>
);
}
);
But when I move Input to a separate component like this it doesn't work:
import React, { useState } from 'react'
import { AutoComplete } from 'antd'
import SearchInput from './SearchInput'
const SearchAutocomplete = connectAutoComplete(
({ hits, currentRefinement, refine }) => {
...
return (
<AutoComplete
options={options}
onSelect={onSelect}
onSearch={handleSearch}
open={open}
>
<SearchInput value={currentRefinement} onChange={e => refine(e.currentTarget.value)}/>
</AutoComplete>
);
}
);
And the SearchInput component itself:
import React from 'react'
import { Input } from 'antd'
const SearchInput = props => {
const { value, onChange} = props;
return (
<Input
value={value}
onChange={onChange}
/>
)
}
Here is the link to codesandbox with the extracted component. How can I fix this error?
Adding React.forwardRef() to SearchInput solved the issue:
const SearchInput = React.forwardRef((props, ref) => {
const { onChange } = props;
return (
<Input.Search
onChange={onChange}
ref={ref}
/>
)
})
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.