I have a custom component of
const Search = (props) => {
const { type: TYPE, name: NAME, label: LABEL, onSelect, filter, value } = props;
const [data, setData] = useState([]);
const [select, setSelect] = useState(value || -1);
const applyFilter = (data) => {
let result = data;
if (filter) {
result = filter(data);
}
return result;
};
useEffect(() => {
getLookupData(TYPE)
.then((response) => {
setData(response);
})
.catch((error) => {
if (error === HttpStatus.NOT_FOUND) {
setData([]);
} else {
throw error;
}
});
}, [TYPE]);
useEffect(() => {
if (value) {
setSelect(value);
}
}, [value]);
const options = applyFilter(data).map((item) => (
<MenuItem value={item.id} key={item.id}>
{item[NAME]}
</MenuItem>
));
const handleChange = (event) => {
setSelect(event.target.value);
onSelect && onSelect(event);
};
const { classes } = props;
const labelProps = { ...(props.required && { required: props.required }) };
return (
<FormControl className={classes.formControl} id={NAME} error={props.error}>
<FormControlLabel control={<InputLabel htmlFor={NAME} {...labelProps}>{LABEL}</InputLabel>} />
<Select
name={TYPE}
value={select}
onChange={handleChange}
disabled={props.disabled || options.length === 0}
input={<Input name={TYPE} id={NAME} />}
>
<MenuItem value=''>
<em>None</em>
</MenuItem>
{options}
</Select>
</FormControl>
);
};
It takes in a menu item by default of None, however i want to pass this None menuitem as a prop and call it when i want too, as for some fields i want the option of 'none' and for some fields i do not want the option of none, currently the system is hard coded to have none appear on every drop down and i dont wish for this to happen
<Search
required
type="contractOptionToExtend"
name="optionToExtend"
label="Option To Extend"
id="contractOptionToExtend"
onSelect={change.bind(null, 'contractOptionToExtend')}
error={touched.contractOptionToExtend && Boolean(errors.contractOptionToExtend)}
value={values.contractOptionToExtend}
/>
On the above field i want to say wether or not i want the None option, I can solve this by passing it as a prop to the Search component but im not totally sure how, I have done something similar for props.required as you can see but currently i cannot do the same for the menu item.
{props.showNone
? <MenuItem value=''>
<em>None</em>
</MenuItem>
: null
}
then within the desired field
showNone={true}
credit to #Abhey Sehgal
Related
Re-render problem
I have two components:
MainForm
CFNumericInput
The values coming from CFNumericInput are correct, but setValue won't render the old one.
Do I have to use an useEffect?
MainForm
const { control, watch, setValue, register } = useFormContext();
return (
<CFNumericInput
name="number1"
control={control}
setValueOnChange={(nextValue: string, oldValue: string) => {
let nextValidValue = checkNumericType(nextValue, "f6.2");
if (nextValidValue !== "") setValue("number1", nextValidValue);
else if (oldValue) setValue("number1", oldValue);
}}
/>;
)
CFNumericInput
export const CFNumericInput: React.FC<any> = ({
name,
control,
setValueOnChange,
}) => {
return control ? (
<Controller
name={name}
control={control}
render={({ field }) => {
return (
<NumericInput
{...field} // onChange, onBlur, value, name, ref
title={title}
onChange={(e) => {
field.onChange(e);
setValueOnChange && setValueOnChange(e, field.value);
}}
/>
);
}}
/>
) : (
<></>
);
};
Working but heavy solution
This solution it's working, but it's really heavy.
const [number1] = watch(["number1"]);
const [old, setOld] = useState("");
useEffect(() => {
let nextValidValue = checkNumericType(number1, "f6.2");
if (nextValidValue !== "") {
setValue("number1", nextValidValue);
setOld(nextValidValue);
} else if (old) setValue("number1", old);
}, [number1]);
Isn't possible that you call checkNumericType on onSubmit?
Also you can try on looking to use Yup (take a look at: https://react-hook-form.com/advanced-usage/#CustomHookwithResolver)
let me know what have you tried
I am building a swipeable card in React. The card contains 4 slides. The values displayed in the card rely on the user input.
First I am defining a sample object like this:
const initialState = { id: '', title: '', name: '', image: ''};
Inside my component, I am defining the array state like:
const [card, setCard] = useState([initialState]);
I am displaying the card side by side along with the user input fields for users to view the cards as they compose. So whenever the user adds/edits a specific value of the card he can view it live on the card.
We can set the state of an object for each field like this:
<Input id='title' name='title' placeholder="Enter Title" type='text' value={card.title} onChange={handleChange}/>
Handle Change function:
const handleChange = (e) => {
setCard({ ...card, [e.target.name]: e.target.value });
}
But this is not possible for the above-mentioned array of objects. So how to handle this situation?
Whenever a user swipes the previous/next card the fields must be populated with the appropriate values so that he can edit them. Simply, a user must be able to edit any field at any time. Whenever a user adds a new card a new object must be pushed to the array state.
Full code:
const initialState = { id: '', title: '', name: '', image: ''};
const Home = () => {
const [card, setCard] = useState([initialState]);
const isdisabled = true;
const handleChange = (e) => {
setCard({ ...card, [e.target.name]: e.target.value });
}
const handleAdd = () => {
//TODO
}
return (
<Flex>
<Center>
<Flex bg="white" w="lg" h="420" borderRadius="lg" m="7" p="2" alignItems="center">
<Box w="48" align="center">
<IconButton aria-label='Go to previous' disabled borderRadius="full" bg='gray.200' color='black' icon={<ChevronLeftIcon w={6} h={6}/>} />
</Box>
<Box>
<Image src={card[0].image} w="full" h="44" objectFit="cover" objectPosition="0 0" borderRadius="lg" />
<Heading color="black" size='lg'>{card[0].title}</Heading>
<Text color="black" size='40'>{card[0].namee}</Text>
</Box>
<Box w="48" align="center">
<IconButton aria-label='Go to previous' disabled borderRadius="full" bg='gray.200' color='black' icon={<ChevronRightIcon w={6} h={6}/>} />
</Box>
</Flex>
</Center>
<Flex direction="column" w="lg" gap="4" m="7">
<Input placeholder="Enter Title" value={card[0].title} onChange={handleChange}/>
<Input placeholder="Enter Name" value={card[0].name} onChange={handleChange}/>
<Button onClick={handleClick}>Upload Image</Button>
<Button onClick={handleAdd}>Add another slide</Button>
<Button colorScheme="blue">Done</Button>
</Flex>
</Flex>
)
}
export default Home
How to seamlessly do this? Any help would be appreciated. Thank you.
your card state is array of objects need to update array first object
const handleChange = (e) => {
const arr = [...card]
arr[0] = {...arr[0], [e.target.name]: e.target.value }
setCard(arr);
}
#Gabriele Petrioli's answer is the perfect solution to my problem except it needs a little tweaking:
Add activeCardIndex to both navigation handlers' dependency list:
const handleGotoNext = useCallback(() => {
// you need to also handle not allowing to go beyond the max
if(activeCardIndex < cards.length-1){
setActiveCardIndex(prevActive => prevActive + 1);
}
}, [activeCardIndex]);
const handleGotoPrevious = useCallback(() => {
// you need to also handle not allowing to go below 0
if(activeCardIndex > 0){
setActiveCardIndex(prevActive => prevActive - 1);
}
}, [activeCardIndex]);
And the handleChange function:
const handleChange = useCallback((e) => {
setCards(prevCards => prevCards.map((card, index) => {
if (index === activeCardIndex) {
return { ...card,
[e.target.name]: e.target.value
}
}else {
return card;
}
}));
}, [activeCardIndex]);
You would likely need an additional state variable, specifying the active card
something like
const [cards, setCards] = useState([initialState]);
const [activeCardIndex, setActiveCardIndex] = useState(0);
handleGotoNext = useCallback(() => {
// you need to also handle not allowing to go beyond the max
setActiveCardIndex(prevActive => prevActive + 1);
}, []);
const handleGotoPrevious = useCallback(() => {
// you need to also handle not allowing to go below 0
setActiveCardIndex(prevActive => prevActive - 1);
}, []);
const handleChange = useCallback((e) => {
setCards(prevCards => prevCards.map((card, index) => {
if (index === activeCardIndex) {
return { ...card,
[e.target.name]: e.target.value
}
}
return card;
}));
}, [activeCardIndex]);
const handleAdd = useCallback(() => {
const newCards = [...cards, { ...initialState
}];
setCards(newCards);
setActiveCardIndex(newCards.length - 1);
}, [cards]);
const activeCard = cards[activeCardIndex];
// for the rendering you should use the activeCard constant, instead of cards[n]
return (
<Flex>
...
<Image src={activeCard.image} w="full" h="44" objectFit="cover" objectPosition="0 0" borderRadius="lg" />
...
</Flex>
)
I am trying to use reac-window with Autocomplete.
This is the code base:
function renderRow(props) {
const { data, index, style } = props;
return React.cloneElement(data[index], {
style: {
...style,
top: style.top,
},
});
}
const ListboxComponent = React.forwardRef(function ListboxComponent(props, ref) {
const { children, role, ...other } = props;
const itemData = React.Children.toArray(children);
return (
<div ref={ref} {...other}>
<FixedSizeList
width="100%"
height={150}
itemSize={50}
itemCount={itemData.length}
itemData={itemData}
role={role}>
{renderRow}
</FixedSizeList>
</div>
);
});
export default function VirtualizedAutocomplete() {
const accountNumberData = useSelector((state) => state.commAccountNumber.data);
const isLoadingAccountNumber = useSelector((state) => state.commAccountNumber.isLoading);
return (
<Autocomplete
id="virtualize-demo1"
multiple
disableListWrap
ListboxComponent={ListboxComponent}
options={accountNumberData}
renderInput={params => (
<TextField
{...params}
variant="outlined"
label="virtualize-demo1"
fullWidth
/>
)}
/>
);
According the documentation I need to make sure that the container has the role attribute set to listbox.
I assume that this is the issue but I am not sure what is wrong with my code?
Thank you
I am building an where it has few dropdowns. Here the problem is when user make selection in dropdown and if they refresh dropdown selection value reset back to default value. Instead I want to keep the value persisted so that when user make refresh it gets value from last selected dropdown value.
For that i tried using localstorage and it does save the value. But inside useEffect I try to get value and pass it to make api call it gives empty string for few times. Since the server gives me error.
Here is my code
const DispensingIncidents = (props) => {
const classes = useStyles();
const {
getFilterData,
dispensingData, // data from server side
getOverviewData,
location,
history,
getAnalysis,
clearAnalysis,
getDuration,
} = props;
const [timeSpan, setTimeSpan] = React.useState("");
const [year, setYear] = React.useState(2020);
const [tabValue, setTabValue] = React.useState(0);
const [spanData, setSpanData] = React.useState([]);
const [dataType, setDataType] = React.useState("");
const [durationLabel, setDurationLabel] = React.useState("");
const [dataTo, setDataTo] = React.useState("");
const [dataFrom, setDataFrom] = React.useState("");
// incidencesSection hide and show
const [incidencesSection, setIncidencesSection] = React.useState(false);
// handle overview select state
const [overViewSelect, setOverViewSelect] = React.useState("");
// stay selected tab on refresh
// eslint-disable-next-line
const [selectTab, setSelectTab] = React.useState(0);
const {
loading,
duration,
period,
type,
_reference,
dispensingOverviewData,
overviewDataLoading,
incidenceAnalysisData, // bottom accordion data,
incidenceAnalysisDataArray, // bottom accordion data array
analysisDataLoading,
} = dispensingData;
const { count } = dispensingOverviewData;
useEffect(() => {
history.replace({
pathname: location.pathname,
search: `?year=${year}&period=${timeSpan}&type=${dataType}&duration=${durationLabel}&overview=${overViewSelect}`,
});
setYear(year);
setTimeSpan(timeSpan);
window.localStorage.setItem(
"incidenceState",
JSON.stringify({
year,
timeSpan,
dataType,
durationLabel,
dataTo,
dataFrom,
})
);
return () => {
window.localStorage.removeItem("incidenceState");
};
// eslint-disable-next-line
}, [
year,
timeSpan,
dataType,
durationLabel,
overViewSelect,
dataTo,
dataFrom,
]);
/**
* This updates on Year given
*/
useEffect(() => {
getFilterData(year);
getDuration(year);
}, [getFilterData, year, getDuration]);
const [once, setOnce] = React.useState(0);
useEffect(() => {
if (duration !== "" && type !== "") {
if (
type !== "" &&
once === 0 &&
duration.monthly.length > 0 &&
_reference !== ""
) {
setOnce(1);
console.log("OverviewPeriod", Object.keys(period));
getOverviewData(
Object.keys(period)[3],
duration.monthly[0].period.to,
duration.monthly[0].period.from,
Object.keys(type)[0],
_reference
);
setTimeSpan(Object.keys(period)[3]);
setDataFrom(duration.monthly[0].period.from);
setDataTo(duration.monthly[0].period.to);
setDataType(Object.keys(type)[0]);
}
}
}, [duration, type]);
/**
* GET query from url search param
* #usage query.get("year")
*/
function useQuery() {
return new URLSearchParams(location.search);
}
const query = useQuery();
const time = query.get("period");
useEffect(() => {
if (time === "yearly") {
const yearlyData = duration["yearly"];
setSpanData(yearlyData);
} else if (time === "weekly") {
const weeklyData = duration["weekly"];
setSpanData(weeklyData);
} else if (time === "quarterly") {
const quarterlyData = duration["quarterly"];
setSpanData(quarterlyData);
} else if (time === "monthly") {
const monthlyData = duration["monthly"];
setSpanData(monthlyData);
} else if (time === "6 months") {
const halfYearlyData = duration["half-yearly"];
setSpanData(halfYearlyData);
}
}, [time, duration]);
/**
*
* #param {*} event
* #param {*} newValue
* on tab change
*/
// eslint-disable-next-line
const handleTabChange = (event, newValue) => {
setTabValue(newValue);
};
useEffect(() => {
if (duration !== "") {
getOverviewData(
timeSpan,
duration.monthly[0].period.to,
duration.monthly[0].period.from,
dataType,
_reference
);
}
}, [duration]);
/**
* Year change
* #param {*} event
*/
const handleYearChange = (event) => {
const v = event.target.value;
setYear(v);
setTimeSpan(query.get("period"));
getDuration(v);
};
/**
* Span change
* #param {*} event
*/
const handleSpanChange = (event) => {
const value = event.target.value;
const reValue = value === "6 months" ? "half-yearly" : value;
setTimeSpan(value);
getOverviewData(
value,
duration[reValue][0].period.to,
duration[reValue][0].period.from,
dataType,
_reference
);
setDataTo(duration[reValue][0].period.to);
setDataFrom(duration[reValue][0].period.from);
setIncidencesSection(false);
setOverViewSelect("");
};
const handleSpanTabChange = (data, i) => {
setSelectTab(i);
setDataTo(data.period.to);
setDataFrom(data.period.from);
getOverviewData(
time,
data.period.to,
data.period.from,
dataType,
_reference
);
setDurationLabel(data.label);
setOverViewSelect("");
setIncidencesSection(false);
};
const handleDataTypeChange = (event) => {
setDataType(event.target.value);
getOverviewData(time, dataTo, dataFrom, event.target.value, _reference);
setIncidencesSection(false);
setOverViewSelect("");
};
const handleOverViewClick = (data) => {
clearAnalysis();
setOverViewSelect(data);
const value = time === "6 months" ? "half_yearly" : time;
getAnalysis(data, value, dataFrom, dataTo, dataType, 1, _reference);
setIncidencesSection(true);
};
const handlePageNext = (pageNo) => {
getAnalysis(
overViewSelect,
time,
dataFrom,
dataTo,
dataType,
pageNo,
_reference
);
};
// useEffect(() => {
// const localValue = JSON.parse(
// window.localStorage.getItem("incidenceState")
// );
// console.log("localValue", localValue);
// }, []);
return (
<div className={classes.dispenseRoot}>
<Paper
elementType="div"
elevation={5}
square={true}
variant="elevation"
className={classes.topContainer}
>
<div className={classes.topContainerDiv}>
<div className={classes.topContainerLeft}>
<div className={classes.headerTextDiv}>
<p>Period</p>
</div>
<FormControl variant="outlined" className={classes.formControl}>
<Select
className={classes.containerSelect}
id="demo-simple-select-outlined"
value={timeSpan}
onChange={handleSpanChange}
>
{Object.values(period).map((span, i) => {
return (
<MenuItem key={i} value={span.toLowerCase()}>
{span.toUpperCase()}
</MenuItem>
);
})}
</Select>
</FormControl>
<FormControl variant="outlined" className={classes.formControl}>
<Select
className={classes.containerSelect}
id="demo-simple-select-outlined"
value={year}
onChange={handleYearChange}
>
<MenuItem value={2020}>2020</MenuItem>
<MenuItem value={2019}>2019</MenuItem>
<MenuItem value={2018}>2018</MenuItem>
<MenuItem value={2017}>2017</MenuItem>
</Select>
</FormControl>
<div className={classes.typeHeading}>
<p>Type</p>
</div>
<FormControl variant="outlined" className={classes.formControl}>
<Select
className={classes.containerSelect}
id="demo-simple-select-outlined"
value={dataType}
onChange={handleDataTypeChange}
>
{Object.keys(type).map((key) => {
return (
<MenuItem key={key} value={key}>
{key.toUpperCase()}
</MenuItem>
);
})}
</Select>
</FormControl>
</div>
</div>
</Paper>
<div className={classes.dispensingIncidentsTabDiv}>
<Paper square>
<Tabs
value={tabValue}
indicatorColor="primary"
textColor="primary"
onChange={handleTabChange}
variant="scrollable"
scrollButtons="on"
aria-label="scrollable auto tabs example"
>
<Tab
className={classes.dispensingTab}
label="INCIDENCES"
{...a11yProps(0)}
/>
</Tabs>
</Paper>
</div>
{incidencesSection ? (
<div className={classes.incidencesAccordionDiv}>
<IncidenceAccordion
handlePageNext={handlePageNext}
incidenceAnalysisDataArray={incidenceAnalysisDataArray}
data={incidenceAnalysisData}
title={"Incidences"}
/>
</div>
) : null}
</div>
);
};
Every time when you call setX() useState() hook, try to save the value in the localStorage. What I'd do in short is:
const [Value, setValue] = useState("");
const handleSetValue = (newValue) => {
setValue(newValue);
window.localStorage.setItem("Value", newValue);
}
And I use the handleSetValue() instead of setValue. And the same way, in the useEffect() hook, I'll try to load all the values this way:
useEffect(() => {
// While loading, load the state from the localStorage.
if (window.localStorage.getItem("Value"))
setValue(window.localStorage.getItem("Value"));
}, [])
The above code will help you to persist your data from reloads.
could you please tell me how to set value in dropdown in react js ?
I am getting dropdown data after few seconds 3000 and then I need to set value on dropdown
const App = ({ children }) => {
const val = "ax";
const [state, setState] = useState([]);
setTimeout(() => {
setState(countryOptions);
}, 2000);
return (
<Container style={{ margin: 20 }}>
<Example countryOptions={state} />
</Container>
);
};
https://codesandbox.io/s/semantic-ui-example-utev4
expected output
Aland Islands should be selected.
{ key: "ax", value: "ax", text: "Aland Islands" },
as after three second I want to select this element
const val = "ax";
As stavros answer suggested; it may be better to keep state in App component and pass the setVal to the dropdown:
App:
const App = ({ children }) => {
const [state, setState] = useState([]);
//added val and setVal in App state
const [val,setVal]=useState('ax');
setTimeout(() => {
setState(countryOptions);
}, 2000);
return (
<Container style={{ margin: 20 }}>
//pass val and setVal so dropdown can set val on change
<Example countryOptions={state} val={val} onChange={setVal}/>
</Container>
);
};
Dropdown:
const DropdownExampleClearableMultiple = ({ countryOptions,val,onChange }) => (
<Dropdown
clearable
fluid
search
closeOnChange
selection
options={countryOptions}
//set value to passed in val
value={val}
//use setVal that was passed in as onChange
onChange={(_,i)=>onChange(i.value)}
placeholder="Select Country"
/>
);
You should update your question because only after visiting the codesandbox was I able to get enough info for an answer..
In your index.js you should update setState(countryOptions) to :
setState({countryOptions:countryOptions},()=>setState({val:"ax"})
Then line 39 to :
<Example countryOptions={state.countryOptions:countryOptions} val={state.val} />
Then in your example.js update const DropdownExampleClearableMultiple to:
const DropdownExampleClearableMultiple = ({ countryOptions, val }) => (
<Dropdown
clearable
fluid
search
closeOnChange
selection
options={countryOptions}
placeholder="Select Country"
value={val}
/>
);
Use the "value" prop.
// index.js
const App = ({ children }) => {
const val = "ax";
const [state, setState] = useState([]);
setTimeout(() => {
setState(countryOptions);
}, 2000);
return (
<Container style={{ margin: 20 }}>
<Example countryOptions={state} value={val} />
</Container>
);
};
// example.js
const DropdownExampleClearableMultiple = ({ countryOptions, value }) => (
<Dropdown
clearable
fluid
search
closeOnChange
selection
value={value}
options={countryOptions}
placeholder="Select Country"
/>
);
You can pass value to value property of Dropdown. Use state property for selected value.
Make sure that you change/update state on onchange event.