I am trying to populate dropdown fields in a form component using Redux data that I have in my store.
The below method is for each of my dropdowns which isn't populating as I don't think I am accessing the data correctly. I am trying to get it to iterate it out for the individual values in each of my field objects. Am I going down the correct with this?
const populateDropdown (option){
this.props.fields.{option}.length > 0 &&
this.props.fields.diveType.map(({diveType}) => {
return (
<MenuItem value={diveType.diveTypeID}>{diveType.diveType}</MenuItem>
)};
}
I have another method with useEffects and object.keys that I don't know would do the same thing.
useEffect(() => {
// dispatch the action to load fields for each field type
// once loaded, the changes will be reflected in the fields variable from the useSelector
Object.keys(fields).forEach(name => dispatch(requireFieldData(name)));
}, []); // <-- empty array
I have faced the same problem while developing the select input using material-UI. for the solution, I created one separate reusable component for generating select input.
PopulateDropdown.js
import React from "react";
import FormControl from "#material-ui/core/FormControl";
import Select from "#material-ui/core/Select";
import InputLabel from "#material-ui/core/InputLabel";
import MenuItem from "#material-ui/core/MenuItem";
/**
* props.dataList: your redux array of object. set default value is empty array
* props.mappingOprions: its content title and value for render the MenuItem title and value
* props.name: form fielname
* props.label: input label
*/
const populateDropdown = ({ dataList = [], mappingOptions, name, label }) => {
const { title, value } = mappingOptions;
return (
<FormControl style={{ width: 200 }}>
<InputLabel id={label}>{label}</InputLabel>
<Select labelId={label} name={name}>
{dataList.map((item) => (
<MenuItem value={item[value]}>{item[title]}</MenuItem>
))}
</Select>
</FormControl>
);
};
export default populateDropdown;
How to use this component
//Custome components
import PopulateDropdown from "./components/PopulateDropdown.js";
// consider this as redux state
const reduxStateData = [
{ diveTypeID: 1, diveType: "type 1" },
{ diveTypeID: 2, diveType: "type 2" },
{ diveTypeID: 3, diveType: "type 3" }
];
// mapping options
/**
* enter keys (which you wan to render as title and value in dropdown list) as value in title and value
*/
const mappingOptions = { title: "diveType", value: "diveTypeID" };
export default function App() {
return (
<div className="App">
<PopulateDropdown
dataList={reduxStateData}
mappingOptions={mappingOptions}
name="feildName"
label="Select div type"
/>
</div>
);
}
Additional
You can add defaultOption props for populating the dropdown default option like none or select option Menuitem
codesandbox link
https://codesandbox.io/s/gifted-bose-q3kb0?file=/src/App.js:0-744
Related
I am working on a form where the user must fill many fields.
Some of those fields have data that comes from entities, so I created a search component where the user clicks on a button, a grid of the entity opens and when selecting I return the selected row as a prop and save in states the values. (this works ok)
Those values that are stored in States, to be shown in diferents TextInput (react-admin)
import React, {useState, useCallback} from "react";
import {Edit, TextInput, SimpleForm} from 'react-admin';
import ProcesoSearchButton from "../proceso/ProcesoBusquedaBtn"
const ProcesoEdit = props => {
const [state, setState] = useState({});
const classes = useStyles();
const {
idProceso = '', proceso = '', d_proceso = '',
} = state;
const updateProceso = useCallback(async (who) => {
setState(state => ({
...state,
idProceso: who.id,
proceso: who.proceso,
d_proceso: who.d_proceso,
}));
})
return (
<Edit {...props} title={<ProcesoStdTitle/>}>
<SimpleForm>
<TextInput source="proceso" label={"N°Proceso"}
defaulValue={proceso}
fullWidth={true}
formClassName={classes.proceso} inputProps={{readOnly: true,}} variant="filled"/>
<TextInput source="d_proceso" label={"Descripción"} fullWidth={true}
defaulValue ={d_proceso}
formClassName={classes.d_proceso} inputProps={{readOnly: true,}}
variant="filled"/>
<ProcesoSearchButton callbackProceso={updateProceso} formClassName={classes.btnBusqueda}/>
....
</SimpleForm>
</Edit>
)
};
export default ProcesoEdit;
With the previously detailed code it works in a first search, States are updated and shown in the TextFields. But if i do a second search, the States are updated but not the displayed value in TextFields.
If I use the value prop it doesn't work.
How can I solve that? I am trying to keep using react-admin because it's what I use throughout the app. This code using MUI TextField works, but it would not be ideal. Thanks
What I'm trying to do
I have a DataTable that I am supplying data to. Columns for the DataTable are created dynamically based on the supplied data. I would like to apply filters to each Column based on what type of data the column contains. For instance, I would like a range filter to be available for numerical data, and a toggle dropdown of unique values to be available for a column that has a list of names. In this question for simplicity I am only assigning DropDown filters in a dynamic fashion.
Problem I am encountering
I am unable to provide the required props and state for the filter to each Column component in a dynamic fashion.
My Question
Without the ability to use custom functional components (see below in What I've Tried) in the table:
How can I dynamically create columns which have filters that require their own state and handlers? I have not been able to find a way to do this where the state behaves properly.
Background on use case
I have a DataTable which accepts Columns as children
https://www.primefaces.org/primereact/showcase/#/datatable
Columns can be configured to use filters (ie a search box, or dropdown of unique values which can be toggled on and off)
A filter requires:
a filterElement function which returns the JSX for the filter type (IE: If the filter is a dropdown, the filterElement contains a MultiSelect)
The filterElement requires
A state to keep track of selectedOptions
An onChange to modify the selectedOptions
The onChange requires
A reference to the DataTable
Example of applying a dropdown filter to a column in a static sense when we know the columns ahead of time
export default function DataTableFilterClassTest() {
const [tableData, setTableData] = useState(data); // Data imported from elsewhere
const dt = useRef(null);
const [selectedOptions, setSelectedOptions] = useState(null)
// Hardcoded some options which are available in the data
const options = [{ option: 1000 }, { option: 1001 }, { option: 1002 }, { option: 1003 }]
const onOptionsChange = (e) => {
// 2nd param 'id' is a reference to the field header this filter applies to
// Hardcoded for testing purposes
dt.current.filter(e.value, 'id', 'in')
setSelectedOptions(e.value)
}
const filterElement = () => {
return (
<React.Fragment>
<MultiSelect
value={selectedOptions}
options={options}
onChange={onOptionsChange}
optionLabel="option"
optionValue="option"
className="p-column-filter"
/>
</React.Fragment>
)
}
return (
<div className="datatable-filter-demo">
<div className="card">
<DataTable ref={dt} value={tableData} paginator rows={10}
className="p-datatable-customers"
emptyMessage="No customers found."
>
<Column field="id" header="id" filter filterElement={filterElement} />
</DataTable>
</div>
</div>
);
}
The Problem
This works fine in a static case in which we know our columns beforehand, but the problem arises when I try to create columns dynamically based on my data. I do not know how to also create the filterElement and the filterElement's required state and onChange for each dynamic column.
// ....
const columnFieldsForMapping = ['id', 'company']
const mappedColumns = columnFieldsForMapping.map((field) => {
// Need to create a new filterElement
// filterElement requires its own onChange to modify selected options,
// and value (a state object, to store the selected options)
return (
<Column key={field} field={field} header={field} filter filterElement={HOW DO I CREATE THIS FOR EACH COLUMN} />
)
})
return (
<div className="datatable-filter-demo">
<div className="card">
<DataTable ref={dt} value={tableData} paginator rows={10}
className="p-datatable-customers"
emptyMessage="No customers found."
>
{mappedColumns}
</DataTable>
</div>
</div>
);
}
What I have tried
Create a CustomColumn component, extending the Column component
My first solution was to create a CustomColumn functional component which extends PrimeReact's column component. The CustomColumn component would contain the filterElement and its required state and functions. It would return a Column component with the applied filterElement prop. Unfortunately I later learned the DataTable can not accept any component except for Column components. It will not accept a react functional component which extends the original Column Component. Here is the github issue which states this is not possible: https://github.com/primefaces/primereact/issues/644#issuecomment-790648272
CustomDropDownColumn.js
import React, { useState, useEffect } from 'react'
import { Column } from 'primereact/column';
import { MultiSelect } from 'primereact/multiselect';
export default function CustomDropDownColumn(props) {
const [state, setState] = useState([])
const onChange = (e) => {
props.dt.current.filter(e.value, props.field, 'in')
setState(e.value)
}
const filterElement = <MultiSelect
value={state}
options={props.options}
onChange={onChange}
optionLabel="option"
optionValue="option"
className="p-column-filter"
/>
return (
<Column field={props.field} header={props.field} filter filterElement={filterElement} />
)
}
Using the custom dropdown column:
// ....
return (
<div className="datatable-filter-demo">
<div className="card">
<DataTable ref={dt} value={tableData} paginator rows={10}
className="p-datatable-customers"
emptyMessage="No customers found."
>
<CustomDropDownColumn dt={dt} options={options} field={field}/>
</DataTable>
</div>
</div>
);
// ....
Create a class based component to encapsulate the filter logic. Expose the filterElement from this class and apply it as a prop in the Column component
Next I tried to create a class for each filter type. The filter class will store the filterElement and all of it's required state and functions. My intent was to instantiate a new DropDownFilter class for each column during mapping, and supply its exposed filterElement method to the Column being mapped. This did not work because creating a class this way in React does not mount the class. Because it is unmounted, I am unable to call setState with the DropDownFilter class.
DropDownFilter.js
import React from 'react'
import { MultiSelect } from 'primereact/multiselect';
class DropDownFilter extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedOptions: null,
}
}
onOptionsChange = (e) => {
this.props.dt.current.filter(e.value, this.props.field, 'in');
this.setState({ selectedOptions: e.value })
}
filterElement = () => {
return (
<React.Fragment>
<MultiSelect
value={this.state.selectedOptions}
options={this.props.options}
onChange={this.onOptionsChange}
optionLabel="option"
optionValue="option"
className="p-column-filter"
/>
</React.Fragment>
)
}
render() {
return (null)
}
}
export default DropDownFilter;
DataTable
// ....
const columnFieldsForMapping = ['id', 'company']
const mappedColumns = columnFieldsForMapping.map((field) => {
// We will map a drop down filter to each column
// Get unique values in column to supply to dropdowns
let options = tableData.map(x => {
const obj = {}
obj.option = x[field]
return obj;
})
options = uniq(options)
// Instantiate instance of DropDownFilter class to use with mapped column
const newFilter = new DropDownFilter({ dt: dt, field: field, options: options })
return (
<Column key={field} field={field} header={field} filter filterElement={newFilter.filterElement} />
)
})
return (
<div className="datatable-filter-demo">
<div className="card">
<DataTable ref={dt} value={tableData} paginator rows={10}
className="p-datatable-customers"
emptyMessage="No customers found."
>
{mappedColumns}
</DataTable>
</div>
</div>
);
}
I have this code:
import React from "react";
import ReactDOM from "react-dom";
import Select from "react-select";
const optionsExample = [
{
code: "CLP",
country: "CLP",
minimumAmount: 10000
},
{
code: "USD",
country: "US",
minimumAmount: 25000
}
];
const handleChange = (newValue, actionMeta) => {
console.log("change newValue", newValue); // object changes but it's not selected the right one
};
const CustomControl = () => (
<Select
defaultValue={optionsExample[0]}
formatOptionLabel={({ code }) => code}
options={optionsExample}
onChange={handleChange}
/>
);
ReactDOM.render(<CustomControl />, document.getElementById("root"));
It doesn't work ok because react-select expect that the value of each object on the data array be named value. How can I pass data like in the example? I need that data has those properties, I can't change country to value.
Actually, I used the props formatOptionLabel to change the default label field from the object to code but I don't know how to do the same but for the value.
Demo with my code, with the problem:
https://codesandbox.io/s/react-select-formatoptionlabel-forked-deuw1?file=/index.js:0-756
This is working because each object has it's value field and that's what is expected by react-select
https://codesandbox.io/s/react-select-formatoptionlabel-forked-d9bdj
If you can't move the mountain, then walk around it.
use inline .map to convert your array of objects
const CustomControl = () => (
<Select
defaultValue={optionsExample[0]}
formatOptionLabel={({ code }) => code}
options={optionsExample.map(x => ({ value: x.country, label: x.code })}
onChange={handleChange}
/>
);
or better still use useMemo to create a new array, if your array changes
const selectOptions = useMemo(() => optionsExample.map(x => ({ value: x.country, label: x.code }),[optionsExample])
<Select
defaultValue={selectOptions[0]}
formatOptionLabel={({ code }) => code}
options={selectOptions}
onChange={handleChange}
/>
Am new to ReactJS. I need to make the "placeholder" which is set to "State" initially to Empty/Null when onClicked or onFocus and then when it's not focused on, it goes back to "State" again. Can someone help me with this, am very new to react so any help will be appreciated.
import React from "react";
import { render } from "react-dom";
import { Container, Button, Modal, Dropdown } from "semantic-ui-react";
const stateOptions = [
{ key: "AL", value: "AL", text: "Alabama" },
{ key: "NY", value: "NY", text: "New York" }
];
const App = () => (
<Dropdown
placeholder="State"
fluid
multiple
search
selection
options={stateOptions}
/>
);
render(<App />, document.getElementById("root"));
From React's perspective, placeholder is a state that needs to be changed according to user's actions (onClick, onBlur)
So create a state to hold placeholder value that need to change.
There are two ways (since v16.8.0 with the introduction of React Hooks).
Using Class Component
class DropDown extends React.Component {
defaultPlaceholderState = "State";
state = { placeholder: this.defaultPlaceholderState };
clearPlaceholder = () => this.setState({ placeholder: "" });
resetPlaceholder = () =>
this.setState({ placeholder: this.defaultPlaceholderState });
render() {
return (
<Dropdown
onClick={this.clearPlaceholder}
onFocus={this.clearPlaceholder}
onBlur={this.resetPlaceholder}
placeholder={this.state.placeholder}
fluid
multiple
search
selection
options={stateOptions}
/>
);
}
}
In the code above, placeholder declared as a state with default value set to this.defaultPlaceholderState.
When a user clicks on the dropdown, onClick clears the placeholder value by setting it to an empty string. Same for onFocus when the Dropdown is on focus.
When a user clicks outside (onBlur), resetPlaceHolder sets the placeholder value to the default this.defaultPlaceholderState.
Using Function Component with useState hook
React v16.8.0 introduces Hooks, which enables Function Components (not a Functional Component, as it refers to Functional Programming) to hold states.
You can use React.useState hook to hold placeholder value.
const DropDownUsingHook = () => {
const defaultPlaceholderState = "State";
const [placeholder, setPlaceholder] = React.useState(defaultPlaceholderState);
const clearPlaceholder = () => setPlaceholder("");
const resetPlaceholder = () => setPlaceholder(defaultPlaceholderState);
return (
<Dropdown
onClick={clearPlaceholder}
onFocus={clearPlaceholder}
onBlur={resetPlaceholder}
placeholder={placeholder}
fluid
multiple
search
selection
options={stateOptions}
/>
);
};
⚠ Note: Unlike the Class version, clearPlaceholder, resetPlaceholder methods and placeholder state don't use this. prefix.
The implementation is similar but you use useState hook to declare the state and the setter (setPlaceholder).
Refer to the Hooks documentation, Using State Hook for more info.
You can play around with the working code on CodeSandbox.
Angular 1.X has ng-options for the choices in a select dropdown, each item being an object. In plain HTML, the value of an option can only be a string. When you select one option in Angular, you can see the actual selected object while in plain html, you can only get that string value.
How do you do the equivalent of that in React (+Redux)?
I came up with a solution that does not use JSON.stringify / parse for the value of the select React element nor does it use the index of the array of choice objects as the value.
The example is a simple select dropdown for a person's gender -- either male or female. Each of those choices is an actual object with id, text, and value properties. Here is the code:
MySelect component
import React, { Component } from 'react';
class MySelect extends Component {
onGenderChange = (event) => {
// Add the second argument -- the data -- and pass it along
// to parent component's onChange function
const data = { options: this.props.options };
this.props.onGenderChange(event, data);
}
render() {
const { options, selectedOption } = this.props;
// Goes through the array of option objects and create an <option> element for each
const selectOptions = options.map(
option => <option key={option.id} value={option.value}>{option.text}</option>
);
// Note that if the selectedOption is not given (i.e. is null),
// we assign a default value being the first option provided
return (
<select
value={(selectedOption && selectedOption.value) || options[0].value}
onChange={this.onGenderChange}
>
{selectOptions}
</select>
);
}
}
App component that uses MySelect
import _ from 'lodash';
import React, { Component } from 'react';
class App extends Component {
state = {
selected: null
}
onGenderChange = (event, data) => {
// The value of the selected option
console.log(event.target.value);
// The object for the selected option
const selectedOption = _.find(data.options, { value: parseInt(event.target.value, 10) });
console.log(selectedOption);
this.setState({
selected: selectedOption
});
}
render() {
const options = [
{
id: 1,
text: 'male',
value: 123456
},
{
id: 2,
text: 'female',
value: 654321
}
];
return (
<div>
<label>Select a Gender:</label>
<MySelect
options={options}
selectedOption={this.state.selected}
onGenderChange={this.onGenderChange}
/>
</div>
);
}
}
Lodash is used to look up the choice object in the array of choice objects inside the onGenderChange function in the App component. Note that the onChange passed to the MySelect component requires two arguments -- an extra data argument is added in order to be able to access the choice objects ("options"). With that, you can just set the state (or call an action creator if using Redux) with the choice object for the selected option.
I run into this same situation while migrating a angular 1 app to react. And I felt it is a much needed feature that I couldn't find so here I leave my implementation of NgOption in react using bootstrap (you can can change that to whatever you are using) for anybody that's missing the goo'old angular 1:
import React from 'react';
import { Form, InputGroup } from 'react-bootstrap';
interface props<T, U>{
options: Array<T>,
selected?: U,
onSelect: (value: T) => any,
as: (value: T) => string,
trackBy: (value: T) => U,
disabled?: boolean
}
type state = {}
export default class NgOptions extends React.Component<props<any, any>, state> {
componentDidMount() {
if (this.props.selected) {
this.props.onSelect(this.props.options.find((o) => this.props.trackBy(o)===this.props.selected))
}
}
public render() {
return ( <InputGroup>
<Form.Control as="select"
disabled={this.props.disabled}
onChange={(e) => this.props.onSelect(this.props.options.find((o) => this.props.trackBy(o)===e.target.value))}>
{this.props.options.map( option =>
<option key={this.props.trackBy(option)}
selected={this.props.selected===this.props.trackBy(option)}
value={this.props.trackBy(option)}>{this.props.as(option)}</option>
)}
</Form.Control>
</InputGroup>
)
}
}