I am using React-Select library in my react project, i am stuck on a point where i want to set default value on the first select option rendered in a loop.
Here is the code below for your understanding
export default function FormSection() {
const options = [
{ value: "Titular", label: "Titular", isDisabled: true },
{ value: "Conjuge", label: "Conjuge" },
{ value: "Filho(a)", label: "Filho(a)" },
];
const formik = useFormik({
initialValues: {
relation: null,
},
onSubmit: (values) => {
console.log(values);
},
});
const calcFormikValuesSelect = (index) => {
if (formik.values.relation == null) {
return null;
} else if (formik.values.relation) {
let allrelation = formik.values.relation;
return allrelation[index];
}
};
const handleChangeRelation = (selectedOption, index) => {
console.log(selectedOption, index);
formik.setFieldValue(`relation[${index}]`, selectedOption);
// formik.setFieldValue('firstSmoker', selectedOption)
};
return (
<div className="secondSection">
<form onSubmit={formik.handleSubmit}>
{Array.apply(null, { length: 3 }).map((item, index) => (
<React.Fragment>
<div className="wrapper-person-2">
<p className="tab-text">Profissão</p>
<Select
value={calcFormikValuesSelect(index)}
name={`relation[${index}]`}
onChange={(selectedOption) =>
handleChangeRelation(selectedOption, index)
}
options={options}
className="select-smoker"
/>
</div>
</React.Fragment>
))}
<button type="submit" className="button-enabled">
CONTINUE
</button>
</form>
</div>
);
}
So for index=0, i want to set default value
{value:'Titular,label:'Titular}
and disabled that Select so that dropdown does not show on click and then for index above 0 I want to show the options as options array has
I tried passing prop to React-Select like
defaultValue:{label:'Titular',value:'Titular}
but that doesn't work
"react-select": "^4.3.1",
Hope someone helps me out, thanks !
To set the default value in formik, you need to provide it in initialValues like this:
const formik = useFormik({
initialValues: {
relation: {0: options[0] } OR [options[0]], // depends on how you put it
},
onSubmit: (values) => {
console.log(values);
},
});
Related
I'm getting used to with redux. My problem is the itemList correctly render the latest value but the value of Checkbox which is from hook state not get the latest value. It should be checked for all item list but it is not. Although I console.log the values in the map func, it still get the latest values and the find func is correct.
export default function Component(props) {
const dispatch = useDispatch();
const { itemList } = useSelector((state) => state.AllCourses);
const [values, setValues] = useState({
all: true,
items: []
});
useEffect(() => {
dispatch(
someActions.getItemList(payload)
); //this will get latest itemList
}, []);
useEffect(() => {
if (itemList.length) {
const newValues = {
all: true,
items: itemList.map((item) => ({
select: true,
id: item.id,
})),
};
setValues(newValues);
}
}, [itemList]);
return (
<Box ml={4}>
{ itemList?.map((item) => {
return (
<Box key={item.id}>
<Checkbox
name={item.name}
value={values?.items?.find((itemVal) => item.id === itemVal.id)?.select}
/>
</Box>
);
})}
</Box>
);
}
`
Tried several solutions but still not correctly
It seems like you are using Material UI. For checkbox component you need to set the checked prop.
import Checkbox from '#mui/material/Checkbox';
const label = { inputProps: { 'aria-label': 'Checkbox demo' } };
export default function Checkboxes() {
return (
<div>
<Checkbox {...label} defaultChecked />
<Checkbox {...label} />
<Checkbox {...label} disabled />
<Checkbox {...label} disabled checked />
</div>
);
}
If you use html input tag with type checkbox, then again you have to set the checked attribute accordingly see below.
<label for="vehicle2"> I have a car</label><br>
<input type="checkbox" name="vehicle3" value="Boat" checked>
And lastly, you don't need a local state in your example and you can remove
const [values, setValues] = useState({
all: true,
items: []
});
and
useEffect(() => {
if (itemList.length) {
const newValues = {
all: true,
items: itemList.map((item) => ({
select: true,
id: item.id,
})),
};
setValues(newValues);
}
}, [itemList]);
and replace with
const values = {
all: true,
items: itemList && itemList.length ? itemList.map((item) => ({
select: true,
id: item.id,
})) : [],
};
In a React vanilla form, I need to solve an issue where users edit manually the value of an option to make the form submit bad values.
To reproduce, the users inspect the element, and manually change the value of an option to an invalid value (domain-wise).
The fix is easy, but I want to create a failing unit test before fixing, in a TDD fashion, and cannot figure how to model this in a test. I use jest and react-testing-library.
Here is the form code:
export const CreateTripForm = ({ countries, onSubmit }) => {
const [countryId, setCountryId] = useState()
const submit = async (e) => {
e.preventDefault()
if (countryId === undefined) return
await onSubmit(countryId)
}
return (
<form onSubmit={submit}>
<legend>Choose a country</legend>
<label htmlFor="countryId">Country</label>
<select name="countryId" required value={countryId} onChange={(e) => setCountryId(e.target.value)}>
<option value=""></option>
{countries.map((country) =>
<option key={country.id} value={country.id}>{country.label}</option>
)}
</select>
<input type="submit" value="Create a trip" />
</form>
)
}
Here is what I tried to do, but the test passes instead of failing:
it('keeps previous countryId if the selected one has been tampered with', () => {
const onSubmit = jest.fn(() => Promise.resolve())
const countries = [
{ id: 'fr', label: 'France' },
{ id: 'en', label: 'England' },
]
const { container } = render(
<CreateTripForm countries={countries} onSubmit={onSubmit} />
)
const select = container.querySelector('select[name=countryId]')
const submitButton = container.querySelector('input[type=select]')
// Select the 'fr' option, it works.
fireEvent.change(select, { target: { value: 'fr' } })
submitButton.click()
expect(onSubmit).toHaveBeenCalledWith('fr')
// Edit an option to have an incorrect value, it should keep the previous value.
elements.unitSelect.options[2].value = 'asgard'
fireEvent.change(select, { target: { value: 'asgard' } })
submitButton.click()
expect(onSubmit).toHaveBeenCalledWith('fr')
})
I've had some bad experiences with expect() like this before.
Have you tried to separate the tests? One for success and one for failure?
From what I can see, the first expect() doesn't reset what he have been called already so I guess that's why your test have passed on the second expect().
Try it like this:
it('keeps previous countryId if the selected one has been tampered with', () => {
const onSubmit = jest.fn(() => Promise.resolve())
const countries = [
{ id: 'fr', label: 'France' },
{ id: 'en', label: 'England' },
]
const { container } = render(
<CreateTripForm countries={countries} onSubmit={onSubmit} />
)
const select = container.querySelector('select[name=countryId]')
const submitButton = container.querySelector('input[type=select]')
// Select the 'fr' option, it works.
fireEvent.change(select, { target: { value: 'fr' } })
submitButton.click()
expect(onSubmit).toHaveBeenCalledWith('fr')
})
it('should prevent sending with tampered select', () => {
const onSubmit = jest.fn(() => Promise.resolve())
const countries = [
{ id: 'fr', label: 'France' },
{ id: 'en', label: 'England' },
]
const { container } = render(
<CreateTripForm countries={countries} onSubmit={onSubmit} />
)
const select = container.querySelector('select[name=countryId]')
const submitButton = container.querySelector('input[type=select]')
elements.unitSelect.options[2].value = 'asgard'
fireEvent.change(select, { target: { value: 'asgard' } })
submitButton.click()
expect(onSubmit).not.toHaveBeenCalled()
})
I am trying to implement a dynamic dropdown menu. Clicking on the add button will show a dropdown menu that allow users to select an item, and each dropdown menu has the same list of options. I have the dropdown options store in an array, and clicking the add button will increment another array of options to the array
The issues I am having now is that, clicking the remove button doesn’t reflect what I have removed on the UI. For example, if I remove the first dropdown, it reflects that the second one is deleted.
import React, { useState } from "react";
const disciplines_fake_data = [
{ name: "discipline1", id: 0 },
{ name: "discipline2", id: 1 },
{ name: "discipline3", id: 2 },
{ name: "discipline4", id: 3 },
{ name: "discipline5", id: 4 },
{ name: "discipline6", id: 5 },
{ name: "discipline7", id: 6 },
{ name: "discipline8", id: 7 }
];
export default function Discipline({
registration,
handleRemoveDisciplineClick,
handleSelectDisciplineClick
// handleInputChange,
}) {
const [disciplinesDropdowns, setDisciplinesDropdowns] = useState([]);
const handleAddDisciplineClick = () => {
setDisciplinesDropdowns((prev) => [...prev, disciplines_fake_data]);
};
const handleRemoveDropdownClick = (index) => {
const newDisciplinesDropdowns = [...disciplinesDropdowns];
newDisciplinesDropdowns.splice(index, 1);
setDisciplinesDropdowns([...newDisciplinesDropdowns]);
handleRemoveDisciplineClick(`otherDisciplines_${index + 1}`);
};
return (
<div>
<div>
{disciplinesDropdowns.length > 0 &&
disciplinesDropdowns.map((disciplines, index) => (
<div style={{ marginTop: "10px" }} key={index}>
<article>
<label htmlFor={`otherDisciplines_${index + 1}`}>
Discipline {index + 1}
</label>
<button
onClick={(e) => {
e.preventDefault();
handleRemoveDropdownClick(index);
}}
>
REMOVE
</button>
</article>
<select
defaultValue="choose from all disciplines"
name={`otherDisciplines_${index + 1}`}
onChange={handleSelectDisciplineClick}
// onChange={handleInputChange}
>
<option disabled value="choose from all disciplines">
-choose from all disciplines-
</option>
{disciplines.map((discipline) => (
<option key={discipline.id} value={discipline.name}>
{discipline.name}
</option>
))}
</select>
</div>
))}
<div style={{ marginTop: "20px" }}>
<button
onClick={(e) => {
e.preventDefault();
handleAddDisciplineClick();
}}
>
<span> add another discipline</span>
</button>
</div>
</div>
</div>
);
}
import React, { useState, useReducer, useEffect } from "react";
import _ from "lodash";
import Discipline from "./Discipline";
const initialState = {
otherDisciplines: []
};
const FORM_ACTION = {
SELECT_DISCIPLINES: "select more disciplines",
REMOVE_DISCIPLINES: "remove disciplines"
};
function registrationReducer(state, action) {
switch (action.type) {
case FORM_ACTION.SELECT_DISCIPLINES:
const name = action.payload.name;
const value = action.payload.value;
const newDisciplines = [
...state.otherDisciplines,
{
[name]: value
}
];
newDisciplines.map((discipline) => {
if (discipline[name]) {
discipline[name] = value;
}
});
return {
...state,
otherDisciplines: _.uniqWith(newDisciplines, _.isEqual)
};
case FORM_ACTION.REMOVE_DISCIPLINES:
return {
...state,
otherDisciplines: state.otherDisciplines.filter(
(discipline) => Object.keys(discipline)[0] !== action.payload
)
};
default:
return { ...state, [action.input]: action.value };
}
}
export default function App() {
const [registration, dispatch] = useReducer(
registrationReducer,
initialState
);
console.log(registration);
const handleInputChange = ({ target }) => {
const { name, value } = target;
const action = {
input: name,
value: value
};
dispatch(action);
};
return (
<form
// onSubmit={handleFormSubmit}
>
<div>
<Discipline
registration={registration}
handleInputChange={handleInputChange}
handleSelectDisciplineClick={(e) => {
const { name, value } = e.target;
dispatch({
type: FORM_ACTION.SELECT_DISCIPLINES,
payload: { name, value }
});
}}
handleRemoveDisciplineClick={(discipline) => {
dispatch({
type: FORM_ACTION.REMOVE_DISCIPLINES,
payload: discipline
});
}}
/>
);
</div>
</form>
);
}
Using the list index as an identifier for the element is not recommended.
Instead of list (disciplinesDropdowns) you can make use of dictionary object to store dropdowns with unique identifiers and pass those unique identifiers to "handleRemoveDropdownClick".
Can have a function, that generates random and unique key before adding dropdowns to "disciplinesDropdowns".
import { CircularProgress, FormControl, Input, InputLabel } from
'#material-ui/core';
function toKey(s) {
return s.split("_").map((s, i) => i > 0 ? s.slice(0,1).toUpperCase() +
s.slice(1, s.length) : s).join("")
}
Function to split the returned json object:
function toLabel(s) {
return s.split("_").map((s, i) => s.slice(0,1).toUpperCase() +
s.slice(1, s.length)).join(" ")
}
My class:
class Reports extends Component {
constructor(props) {
super(props);
this.state = {
report: '',
filename: 'my-data.csv',
isLoading: false,
tableHeaderData: [],
reports: [
{ name: 'C3 Report', id: 1, actOn: 'c3'},
{ name: 'C4 Report', id: 2, actOn: 'c4'},
{ name: 'C5 Report', id: 3, actOn: 'c5'}
],
categories: {name: 'Cat 1'},
catSelection: 'Select a Category',
repSelection: 'Select Report Type',
isReportSelected: false,
c4RptFirstInput: '',
c4RptSecondInput: ''
}
}
Not sure about this but went with convention:
componentDidMount () {
const {dispatch, id} = this.props;
}
handleChange (e) {
// this.setState({ input: e.target.value });
}
This is the plugin that I'm using to convert the page into a csv file:
csvHeader () {
const data = this.reportData()
if(data.length === 0) return []
const keys = Object.keys(data[0])
return keys.map((k) => {
const label = toLabel(k)
const key = toKey(k)
return { label, key }
})
}
csvData () {
const data = this.reportData()
if(data.length === 0) return []
const values = Object.entries(data);
const keys = Object.keys(data[0])
const rows = values.map(entries => {
const record = entries[1];
return keys.reduce((acc, key, i) => {
acc[toKey(key)] = record[key]
return acc
}, {})
});
return rows
}
Checks if report or package:
reportData(){
switch(this.state.report) {
case 'channels':
return this.props.channels
case 'packages':
return this.props.packages
default:
return []
}
}
Not sure about this placeholder function but copied it from somewhere:
placeholder () {
return (
<div>
<h1 className="display-3">Reports</h1>
<p className="lead" cursor="pointer" onClick=
{this.loadChannelData}>Svc Configuration</p>
</div>
);
}
Was experimenting with this function but wasn't sure how to use it:
componentWillReceiveProps() {
}
handleCategorySwitch = (e) => {
const name = e.target.name;
const value = e.target.value;
this.setState({ [name]: value});
console.log(`name ${name}, value ${value}`);
}
This is where the 'subselection' of the second set of drop downs happens:
handleSubselection = (e) => {
this.setState({c4RptSecondInput: e.target.value, })
switch( e.target.value) {
case 'input3':
return this.props.ReportGetAllPackages()
}
}
handleReportSwitch = (e) => {
const selectedAction = e.target.value;
if (selectedAction == 'c3') {
this.setState(prevState => ({
report: 'channels'
,isLoading: true
}), this.props.ReportGetAllChannels)
}
if (selectedAction == 'c4') {
this.setState(prevState => ({
report: 'packages'
}))
}
}
Render function:
render () {
const {filename, reports, catSelection, repSelection, isReportSelected,
c4RptFirstInput, c4RptSecondInput} = this.state;
return (
<div className="reports">
{this.placeholder()}
<div className="flexMode">
<span className="spanFlexMode">
<InputLabel htmlFor="catSelection"></InputLabel>
<Select value={catSelection} name={'catSelection'}
onChange={(e) => this.handleCategorySwitch(e)}>
<MenuItem value="select">Select Category</MenuItem>
<MenuItem value={'Cat1'}>Cat 1</MenuItem>
<MenuItem value={'Cat2'}>Cat 2 </MenuItem>
<MenuItem value={'Cat3'}>Cat 3 </MenuItem>
</Select>
</span>
<span className="spanFlexMode">
<label>Report Name:</label>
<Select value={repSelection} name="repSelection"
onChange={(e) => this.handleReportSwitch(e)}>
<MenuItem defaultValue={'select'}>Select
Report</MenuItem>
{reports && reports.map((report, index) => <MenuItem
key={index} value={report.actOn}>{report.name}</MenuItem>)}
</Select>
</span>
</div>
Below are the second set of drop downs that show up conditionally based on selection of a particular field from above select boxes:
{ this.state.report === 'packages' ? (
<div>
<span>
<label>Input 1:</label>
<Select name="c4RptFirstInput" value={c4RptFirstInput}
placeholder={'Select Provider'} onChange={(e) =>
this.handleSubselection(e)}>
<MenuItem value={'Def'}>Select</MenuItem>
<MenuItem value={'Provider'}>Provider</MenuItem>
<MenuItem value={'Region'}>Region</MenuItem>
<MenuItem value={'Zone'}>Zone</MenuItem>
</Select>
</span>
<span className="spanFlexMode">
<label>Input 2:</label>
<Select name="c4RptSecondInput" defaultValue=
{c4RptSecondInput} value={c4RptSecondInput} onChange={(e) =>
this.handleSubselection(e)}>
<MenuItem value={'Def'}>Select</MenuItem>
<MenuItem value={'input2'}>Input 2</MenuItem>
<MenuItem value={'input3'}>Input 3</MenuItem>
<MenuItem value={'input4'}>Input 4</MenuItem>
</Select>
</span>
</div>
) : null}
<div>
<CSVLink data={this.csvData()} headers={this.csvHeader()}
filename={filename} target={'_blank'}>
<GetAppIcon />
</CSVLink>
Here is where the spinning loader should do it's thing and disappear once the data is loaded - currently it just keeps on spinning and the data never gets loaded even though I can see that the data has successfully come back from the reducer:
{isLoading
? <CircularProgress />
: (
<Table id="t1">
<TableHeaders data={this.csvHeader()} />
<TableContent data={this.csvData()} />
</Table>
)}
</div>
</div>
)
}
}
const mapDispatchToProps = dispatch => {
return {
ReportGetAllChannels: () => dispatch(ReportGetAllChannels()),
ReportGetAllPackages: () => dispatch(ReportGetAllPackages()),
}
}
const defaultState = ({
state: {},
channels: [],
packages: []
,isLoading: false
})
const mapStateToProps = (state=defaultState) => {
return ({
state: state,
channels: state.RptDetailsReducer.data,
packages: state.RptPackagesReducer.data
,isLoading: false
})
}
isLoading variable is not defined in your render method. I see that you defined it in your component's state and inside your reducer. I assume you are referencing one in your state (Since you said it was keep spinning it is probably the case). You set component's isLoading to true in handleSubselection you have this snippet:
if (selectedAction == 'c3') {
this.setState(prevState => ({
report: 'channels',
isLoading: true
}), this.props.ReportGetAllChannels)
}
This code will set isLoading to true than dispatch ReportGetAllChannels. However your component's state won't be updated. I don't know what ReportGetAllChannels does but I am guessing it sets its own isLoading to false. Which is different variable.
Also you may want to read this https://overreacted.io/writing-resilient-components/#principle-1-dont-stop-the-data-flow. Once you map your state to props you usually want to pass them directly to child components.
Edit:
Quick fix: use this.props.isLoading instead of state, and set isLoading to true inside your dispatched action
I have this pseudo code for my form. Where I would like to display just fields with canAccess=true.
const initialValues = {
firstName: { canAccess: true, value: 'Mary' },
surName: { canAccess: false, value: 'Casablanca' }
}
<Form initialValues={initialValues}>
{props =>
<>
<div className="nestedItem">
<Field name="firstName" />
</div>
<Field name="surName" />
</>
}
</Form>
With this code I would like to see rendered just field with firstName.
I know that I can iterate through React.Children.map() but I don't know how to iterate children when using render props.
Also there can be nested elements, so I would like to find specific type of component by name.
Thanks for help.
const initialValues = {
firstName: { canAccess: true, value: 'Mary' },
surName: { canAccess: false, value: 'Casablanca' }
}
<Form initialValues={initialValues}>
{props =>
<>
{
Object.keys(props.initialValues).map(k => (
k.canAccess && <Field name={k} />
));
}
</>
}
</Form>
Edit: Your form can perform some logic and pass back filtered items to your component.
getFilteredItems = items => Object.keys(items).reduce((acc, key) => {
const item = items[key];
const { canAccess } = item;
if(!canAccess) return acc;
return {
...acc,
[key]: item
}
}, {}));
render() {
const { initialValues, children } = this.props;
const filteredItems = this.getFilteredItems(initialValues);
return children(filteredItems);
}
<Form initialValues={initialValues}>
{ props =>
<>
{
Object.keys(props).map(k => <Field name={k} />)
}
</>
</Form>
This is what I was looking for.
const Form = ({initialValues, children}) =>
props =>
<Authorized initialValues={initialValues}>
{typeof children === 'function' ? children(props) : children}
</Authorized>
const Authorized = ({initialValues, children}) => {
// Do check
React.Children.map(chidlren, x => {
if(x.type === Field ) // && initialValues contains x.props.name
return x
return null
... })
}