Passing a prop into a custom component - javascript

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

React Native - react hook form - setValue not re-render Controller component

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

Setting the values of an object in an array in React?

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>
)

Autocomplete scroll not working with react-window

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

How to persist data selection on page refresh in react js

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.

how to set value in dropdown in react js?

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.

Categories