For example i have some working select component.
It's displays fine in Canvas tab of storybook
but in Docs tab it's doesn't display the options
// some-select-component.stories.tsx
const options = [
{
id: 1,
name: 'option 1',
},
{
id: 2,
name: 'option 2',
},
{
id: 3,
name: 'option 3',
},
];
const Template: ComponentStory<typeof SomeSelectComponent> = args => {
const [formikValue, setFormikValue] = useState({});
return (
<Formik initialValues={{} as any} onSubmit={setFormikValue} >
<SomeSelectComponent name='name'>
{options.map((option => (
<Option
key={option.id}
value={option.name}
>
{option.name}
</Option>
)))}
</SomeSelectComponent >
</Formik>
)
}
export const SimpleSelect = Template.bind({});
// docs.mdx file
<Story id='some-select-component--simple-select' />
how should i handle this case? didn't find this kind of issues
If you had strict root for portal - fix it.
difine the root block for rendering through the props, because, probably, your block for portal rendering wont be displayed in docs-root
Related
I have a reuseable component i created and the value is undefined. I console logged the currentTarget in the Select.tsx and it returns the value correctly. However, the actual component using the select is the one that returns an undefined value. What am i missing here?
This is the code in the select.Tsx
export const Select = (props: any) => {
const [data] = useState(props.data);
const [selectedData, updateSelectedData] = useState('');
function handleChange(event: any) {
updateSelectedData(event.currentTarget.value);
console.log(event.currentTarget.value, 'in select.tsx line 10');
if (props.onSelectChange) props.onSelectChange(selectedData);
}
let options = data.map((data: any) => (
<option key={data.id} value={data.id}>
{data.label}
</option>
));
return (
<>
<select
className={props.className ? props.className : 'float-right rounded-lg w-[50%] '}
onChange={handleChange}>
<option>Select Item</option>
{options}
</select>
</>
);
};
This is the code being used in the actual component...
...
const actionSelectOptions = [
{ id: 1, label: 'Pricing Revised', value: 'Pricing Revised' },
{ id: 2, label: 'Cost Build-up Posted', value: 'Cost Build-up Posted' },
{ id: 3, label: 'Pricing Created', value: 'Pricing Created' },
];
function onSelectChange(event: any) {
console.log(event.currentTarget.value);
}
return (
...
<Select
className="flex justify-center items-center rounded-lg"
data={actionSelectOptions}
onSelectChange={onSelectChange}
/>
...
)
I tried changing between target and currenTarget in the main component. It both get undefined.. the console works in the select component it seems as if the data is not passing on as its suppose to.
I also tried writing an arrow function within the actual called component for example:
<Select
...
onSelectChange={(e)=> console.log(event.currentTarget)}
I am trying to use mui Autocomplete multi select component but can't make it work as per my requirements. I want it to have default selected values which will be passed as a props from Parent component and dropdown options which I will be getting via an API.
Problem 1 :
I am able to render my deafult values in input box which was passed as props but my selected values should not be there in options. Right now I can add duplicate values meaning even though I have few items selected by default in input box but still I could see them in dropdown. I have tried to read the docs it says The value must have reference equality with the option in order to be selected. but can't understand clearly. Is it because I am using 2 different states(arrays) for value and options?.
Problem 2 (Extra requirement)
In addition to the list of usersOptions that I will get from API to populate the dropdown option, I also need to add an option of All.
Whenever few items are already selected and then user clicks ALL in dropdown all the selected items should be removed and only All should be there in input box and when user tries to open the dropdown, all other values should be disabled.
When the user deselect ALL from the dropdown then only he/she should be able to select individual value(user)
Basically the user should be able to select only ALL or Individual user(value).
Link to CodeSanbox(https://codesandbox.io/s/naughty-elbakyan-hi16x5?file=/src/AutoComplete.js:0-1399)
import { useEffect, useState } from "react";
//This is the structure of data for both users & usersOptions
const AutoComplete = ({ users }) => {
const [selectedUsers, setSelectedUsers] = useState(users);
const [usersOptions, setUsersOptions] = useState([]);
useEffect(() => {
// fetch("https://x.com/api/getUsers")
// .then((response) => response.json())
// .then((data) => setUsersOptions(data));
//let's assume API returned this
const usersOptions = [
{ id: 1, name: "User 1" },
{ id: 2, name: "User 2" },
{ id: 3, name: "User 3" },
{ id: 4, name: "User 4" },
{ id: 5, name: "User 5" },
{ id: 6, name: "User 6" }
];
const usersOptionsWithAll = [{ id: 0, name: "All" }, ...usersOptions];
setUsersOptions(usersOptionsWithAll);
}, []);
console.log("selectedUsers", selectedUsers);
console.log("usersOptions", usersOptions);
return (
<Autocomplete
multiple
id="tags-outlined"
options={usersOptions}
getOptionLabel={(option) => option.name}
filterSelectedOptions
renderInput={(params) => (
<TextField {...params} label="" placeholder="" />
)}
value={selectedUsers}
onChange={(event, newValue) => {
setSelectedUsers(newValue);
}}
/>
);
};
export default AutoComplete;
[1]: https://i.stack.imgur.com/83V7d.jpg
1. You should use isOptionEqualToValue prop to determine if the option represents the given value.
2. It is preferable to use filterOptions prop to add 'All' option or any options you want to add.
import { Autocomplete, TextField, createFilterOptions } from "#mui/material";
import { useEffect, useState } from "react";
const AutoComplete = ({ users }) => {
const [selectedUsers, setSelectedUsers] = useState(users);
const [usersOptions, setUsersOptions] = useState([]);
useEffect(() => {
const usersOptions = [
{ id: 1, name: "User 1" },
{ id: 2, name: "User 2" },
{ id: 3, name: "User 3" },
{ id: 4, name: "User 4" },
{ id: 5, name: "User 5" },
{ id: 6, name: "User 6" }
];
setUsersOptions(usersOptions); // Set options without adds
}, []);
return (
<Autocomplete
multiple
id="tags-outlined"
options={usersOptions}
getOptionLabel={(option) => option.name}
filterSelectedOptions
renderInput={(params) => (
<TextField {...params} label="" placeholder="" />
)}
value={selectedUsers}
onChange={(event, newValue) => {
// Check if 'All' option is clicked
if (newValue.find(option => option.id === 0)) {
// Check if all options are selected
if (usersOptions.length === selectedUsers.length) {
setSelectedUsers([]);
} else {
setSelectedUsers(usersOptions);
}
} else {
setSelectedUsers(newValue);
}
}}
// Add These props
isOptionEqualToValue={(option, value) => option.id === value.id}
filterOptions={(options, params) => {
const filtered = createFilterOptions()(options, params);
return [{ id: 0, name: 'All' }, ...filtered];
}}
/>
);
};
export default AutoComplete;
Mui Autocomplete API
I'm receiving the data from api like cites nested into state objects
Example:
const data = [
{
id: 1,
name: 'State One',
cities: [
{
id: 1,
state: 'State One',
name: 'City One'
},
{
id: 2,
state: 'State One',
name: 'City Two'
},
{
id: 3,
state: 'State One',
name: 'City Three'
}
]
},
{
id: 2,
name: 'State 2',
cities: [
{
id: 4,
state: 'State 2',
name: 'City 5'
}
]
}
]
I have to give the Select options of the Cities according to Parent State. Lets say User selected State One in State Select Field then There should be only cities options of the State One in next City Field
How to Configure it ?
Currently; I have created hollow structure of the Select Input Fields but can't find anything how can I configure it. I need little help or any idea to start with.
This is current code;
<Col lg="6" md="12" className="mb-1">
<Label className="form-label py-1" for="state">
State
</Label>{' '}
<Row></Row>
<Select
id="state"
options={stateOptions}
defaultValue={stateOptions[0]}
onChange={(choice) => console.log(choice.value)}
/>
</Col>
<Col lg="6" md="12" className="mb-1">
<Label className="form-label py-1" for="city">
City
</Label>{' '}
<Row></Row>
<Select
id="city"
classNamePrefix="select"
options={cityOptions}
onChange={(choice) => console.log(choice.value)}
/>
</Col>
I have seen some articles but they suggest to use npm libraries but I can't use them because data is a lot different then that I want to handle.
You can keep track of the currently selected state and update the cities that the second Select takes.
Also added some comments to explain what the functions are doing.
export default function App() {
const [state, setState] = useState(null);
const [city, setCity] = useState(null);
const onStateChange = (option) => {
setState(option);
// to remove city if a different state is selected
if (!option || option?.value !== state?.value) setCity(null);
};
const onCityChange = (option) => {
setCity(option);
};
// a separate useState for cities is not needed
// as cities depend on the selected state, it can be determined when state changes
const cities = useMemo(() => {
if (!state) return [];
return getOptions(data.find((el) => el.id === state.value).cities);
}, [state]);
return (
<div>
<Select options={states} value={state} onChange={onStateChange} />
<hr />
<Select options={cities} value={city} onChange={onCityChange} />
</div>
);
}
Whenever I change the value in my select, I have to show different graphs
if I choose bar, it should display bar chart
if I choose line, it should display line chart
Initially the value is zero so it displays bar chart, then when I change to line it works fine, but when I go back to bar it does not.
Code:
const [chart,setChart]=useState(0)
const [filterData, setFilterData] = useState([])
export const ChartTypes =[
{
value: 0,
label: 'Bar'
},
{
value: 1,
label: 'Line'
},
{
value: 2,
label: 'Scatter Plot'
},
// My select Component
const handleChartChange = (event) =>{
setChart(event.target.value)
}
<FormControl variant="filled" className={classes.formControl}>
<InputLabel htmlFor="filled-age-native-simple">Chart</InputLabel>
<Select
native
value={chart}
onChange={handleChartChange}
inputProps={{
name: 'Chart',
id: 'filled-age-native-simple',
}}
>
{ChartTypes.map((e,chart)=>{
return (
<option value={e.value} key={e}>
{e.label}
</option>
)
})}
</Select>
{/* </Col>
<Col> */}
</FormControl>
// conditional rendering the component
{chart === 0 ? <BarChart
graphData={filterData}
filterType={graphFilter}
/> : <LineChart
graphData={filterData}
filterType={graphFilter} />
}
Edit
Thanks, it worked with the support of the below answers
Your issue is that the value of event.target.value is going to be a string "0" instead of a number 0 which you check for in your chart === 0 check.
Your initial value works because you hard-coded a zero as a number.
Option 1
You can either change the check to not include the type by doing chart == 0
OR
Option 2
You can change the value in your ChartTypes array to a string:
export const ChartTypes = [
{
value: '0',
label: 'Bar'
},
{
value: '1',
label: 'Line'
},
{
value: '2',
label: 'Scatter Plot'
}
];
and make your initial value const [chart, setChart] = useState('0')
OR
Option 3
You can change your handleChartChange function to parse the value as a number:
const handleChartChange = (event) => {
setChart(parseInt(event.target.value));
}
try this way, it works for me.
//define the state in this way:
const [state,setSate]=useState({chart:0})
const handleChartChange = (e) => {
setState({...state, chart:e.target.value})
}
<Select
...
value={state.chart}
onChange={handleChartChange}
...
>
</Select>
I relatively new to React and Semantic UI as well.
There is a component called Dropdown with a props multiple and selection, which allows to select multiple items.
On the output my state looks like this:
const selectedItems = [
{key: 1, value: 1, text: 'Item 1'},
{key: 2, value: 2, text: 'Item 2'},
{key: 3, value: 3, text: 'Item 3'},
];
How can I do setup limit of N amount of elements?
Many thanks
The Semantic UI React Dropdown option provides a function called as onAddItem. You will have to use the value data key and do something like this:
const onAddItem = (event, data) => {
1.Fetch the state of the selected values, stored in the value key
2. Check if the limit is greater than 2
3. If the condition is met, then add
4. Else, show an error
}
Documentation Link
Well according to https://react.semantic-ui.com/modules/dropdown#dropdown-example-multiple-selection you need to create controlled component, which means you will bind value={this.state.selectedItems} then you will bind onChange={(e,data) => { this.handleChange(e,data )} and in your code
onChange (e, data) {
const currentItems = this.state.selectedItems
if (currentItems.length <= MAX_SELECTION ) {
currentItems.push(data)
this.setState({
selectedItems: currentItems
})
}
}
this will crate controlled component which will allows you to control state by yourself, and you will limit changing state, probably you will need to also handle removing items from state inside this onChange event.
I would like to suggest another approach.
set useState directly to the dropdown value.
import React, { useState } from 'react';
import { Dropdown } from 'semantic-ui-react';
const MAX_FRUITS_SELECTION = 3;
const FruitsSelect = () => {
const [fruitId, setFruitId] = useState([]);
const optionsFRUITSFake = [
{ key: 1, value: 1, text: 'Orange' },
{ key: 2, value: 2, text: 'Lemon' },
{ key: 3, value: 3, text: 'Apple' },
{ key: 4, value: 4, text: 'Banana' },
{ key: 5, value: 5, text: 'Melon' },
{ key: 6, value: 6, text: 'Pineapple' }
];
const handleDropFilterFruit = (e: any, data?: any) => {
if (data.value.length <= MAX_FRUITS_SELECTION) {
setFruitId(data.value);
}
};
return (
<Dropdown
placeholder="Select Fruits"
onChange={handleDropFilterFruit}
value={fruitId}
fluid
multiple
selectOnNavigation={false}
search
selection
options={optionsFRUITSFake}
/>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<FruitsSelect />
</React.StrictMode>,
rootElement
);
<!DOCTYPE html>
<html lang="en">
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
</body>
</html>
I'm posting my workaround here. It's probably more short and simple.
At first, save the values in the state (preferably redux state) after every onChange. React state also would do fine. Then, make it disabled when a certain array length of the value is reached.
const districtData = ['Dhaka', 'Bagerhat', 'Bandarban',
'Barguna', 'Barishal', 'Bhola']
const [districtValue, setDistrictValue] = useState();
<Dropdown
onChange={async (e, { name, value }) => {setDistrictValue(value)}}
options={districtData.map((currentValue, index) => {
return {
key: `${currentValue}`,
value: `${currentValue}`,
text: `${currentValue}`,
disabled: districtValue.length > 2 ? true : false
}
// Do other things here
// Max 3 values here, then options will be disabled.
// Will be live again if any selected options are less than 3 here
/>