Call child hook function from parent using refs - javascript

I'm switching over to building components using Hooks and i'm struggling to setup refs with useRef()
Parent (The ref is only added to one component currently, as I'd like to ensure this is working before extending functionality to others):
export default function UserPanels({ selected_client } ) {
const classes = useStyles();
const [value, setValue] = useState(0);
const container = useRef( null );
function handleChange(event, newValue) {
setValue(newValue);
container.current.displayData();
}
return (
<div className={classes.root}>
<UserTabs
value={value}
onChange={handleChange}
indicatorColor="primary"
textColor="primary"
variant="fullWidth"
aria-label="full width tabs example"
>
<Tab label='Service User' {...a11yProps(0)} />
<Tab label='Care Plan' {...a11yProps(1)} />
<Tab label='Contacts' {...a11yProps(2)} />
<Tab label='Property' {...a11yProps(3)} />
<Tab label='Schedule' {...a11yProps(4)} />
<Tab label='Account' {...a11yProps(5)} />
<Tab label='Invoices' {...a11yProps(6)} />
<Tab label='Notes' {...a11yProps(7)} />
<Tab label='eMAR' {...a11yProps(8)} />
</UserTabs>
<TabPanel value={value} index={0}>
<UserDetailsContainer
selected_client={ selected_client }
/>
</TabPanel>
<TabPanel value={value} index={1}>
Care Plan
</TabPanel>
<TabPanel value={value} index={2}>
<ContactsContainer
ref={ container }
selected_client={ selected_client }
/>
</TabPanel>
<TabPanel value={value} index={3}>
Property
</TabPanel>
<TabPanel value={value} index={4}>
Schedule
</TabPanel>
<TabPanel value={value} index={5}>
Account
</TabPanel>
<TabPanel value={value} index={6}>
Invoices
</TabPanel>
<TabPanel value={value} index={7}>
Notes
</TabPanel>
<TabPanel value={value} index={8}>
eMAR
</TabPanel>
</div>
);
}
Child:
export default function ContactsContainer( props ) {
const [ state, setState ] = useState({
contact_records: contacts,
data_ready: true
});
function displayData() {
console.log( 'display time' );
}
if ( !state.data_ready ) return null
return (
<>
{
state.contact_records.map( ( contact ) => {
return <ContactRecord contact={ contact } />
} )
}
</>
)
}
Essentially, I'm trying to call a child function from the parent but ref.current evaluates to null and when handleChange() is invoked I receive the error container.current is null and I regularly see the error Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
As a note, I've already tested forwardRef:
<ContactsContainer
ref={ container }
selected_client={ selected_client }
/>
And, while this removes the error, it does not solve the issue. I've never had an issue using refs with class components but I seem to be missing something here.

First of all, don't overuse refs ( react doc). You must not control your child component by call directly its functions (and honestly you can't do this with function components).
If you need to display something in your children, you have to prepare data in your parent component and pass that data by props. Children should be as simple as possible. They should get props and displaying some data.
In parent:
const [value, setValue] = useState(0);
const [time, setTime] = useState(null);
function handleChange(event, newValue) {
setValue(newValue);
setTime('display time');
}
return (
....
<ContactsContainer
time={time}
selected_client={ selected_client }
/>
....
)
If you need to make some side effects (e.g. make HTTP calls, dispatch Redux actions) in your child when props changes, you have to use useEffect hook.
In parent:
<ContactsContainer
value={value}
selected_client={ selected_client }
/>
In child:
useEffect(() => {
console.log('display time action');
}, [props.value]);

you can pass a ref as a prop:
// ...
const infoRef=useRef(null)
useEffect(()=>{
if(infoRef.current) infoRef.current()
},[])
return <ChildComponent infoRef={infoRef} />
and then in child:
useEffect(()=>{
infoRef.current=childFunctionToExecuteInParent
},[])

Related

Material-UI TextField loses focus on every onChange

I am creating the following component:
It will contain an array of objects, where each object is a prescription, with the medicine name from the select and a TextField for the Dosis.
My problem is that the TextField loses focus on every onChange() and is very frustrating because it cannot be edited on a single focus.
This is my component :
const MedicineSelect = ({ medications, setMedications, ...props }) => {
const { medicines } = useMedicines()
const classes = useStyles()
const handleChange = (index, target) => {
// setAge(event.target.value)
const newMedications = cloneDeep(medications)
newMedications[index][target.name] = target.value
setMedications(newMedications)
}
const handleAddMedicine = () => {
const newMedications = cloneDeep(medications)
newMedications.push({ medicine: '', dosis: '', time: '' })
setMedications(newMedications)
}
const handleDeleteMedicine = (index) => {
console.log('DELETE: ', index)
const newMedications = cloneDeep(medications)
newMedications.splice(index, 1)
setMedications(newMedications)
}
return (
<Paper style={{ padding: 5 }}>
<List>
{medications.map((medication, index) => (
<ListItem key={nanoid()} divider alignItems='center'>
<ListItemIcon>
<Tooltip title='Eliminar'>
<IconButton
className={classes.iconButton}
onClick={() => handleDeleteMedicine(index)}
>
<HighlightOffOutlinedIcon />
</IconButton>
</Tooltip>
</ListItemIcon>
<FormControl className={classes.formControl}>
<InputLabel
id={`${index}-select-${medication}-label`}
>
Medicamento
</InputLabel>
<Select
labelId={`${index}-select-${medication}-label`}
id={`${index}-select-${medication}`}
name='medicine'
value={medication.medicine}
onChange={(event) =>
handleChange(index, event.target)
}
>
{medicines.map((medicine) => (
<MenuItem
key={nanoid()}
value={medicine.name}
>
{medicine.name}
</MenuItem>
))}
</Select>
</FormControl>
<TextField
// fullWidth
id={`${index}-text-${medication}`}
label='Dosis'
name='dosis'
onChange={(event) =>
handleChange(index, event.target)
}
value={medication.dosis}
/>
</ListItem>
))}
<Button onClick={handleAddMedicine}>+ agregar</Button>
</List>
</Paper>
)
}
And here is where I set the component:
const [medications, setMedications] = useState([
{ medicine: '', dosis: '', time: '' },
])
...
<Grid item md={12} xs={12}>
<Accordion>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls='panel1a-content'
id='panel1a-header'
>
<Typography variant='h4'>
Tratamiento:
</Typography>
</AccordionSummary>
<AccordionDetails>
<Container disableGutters>
<MedicineSelect
medications={medications}
setMedications={setMedications}
/>
</Container>
</AccordionDetails>
</Accordion>
</Grid>
...
Adding and removing objects from the array works perfect. selecting the medicine from the select, also works perfect. the only problem I have is when editing the Dosis TextField, with every character, the focus is lost and I have to click again on the TextField.
Please help me getting this fixed!!!
After searching a lot, finally I found the solution. Actually when using nanoid() to create unique keys, on every state update React re-renders all components and since the id of both the List and the TextField component are regenerated by nanoid on every render, React loses track of the original values, that is why Focus was lost.
What I did was keeping the keys unmuttable:
<ListItem key={`medication-${index}`} divider alignItems='center'>
and
<TextField
key={`dosis-${index}`}
fullWidth
// id={`${index}-dosis-${medication}`}
label='Dosis'
name='dosis'
onChange={(event) =>
handleChange(index, event.target)
}
value={medication.dosis}
/>

How do i pass a value from child component to parent component using function?

How do i pass a validation value from child component to parents component?
i tried to use props but it didn't work . i tried to pass the 'isValidValue' status
Child Component :
function MilikSendiri({isValidValue}) {
const { register, handleSubmit } = useForm()
function sweetAlertclick(){
Swal.fire({
icon: 'success',
title: 'Data anda sudah tersimpan ',
})
}
return (
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={onSubmit}
// validateOnMount
>
{
formik => {
const isValidValue = formik.isValid? ("Data Completed") : ("DData incomplete");
return(
<div>
<div>
Status : {isValidValue}
<label htmlFor="luasTanah"> Luas Tanah </label>
<Field className="formBiodata"
type="text" id="outlined-basic"
placeholder="luasTanah"
fullWidth
id="luasTanah"
name="luasTanah"
margin="normal" variant="outlined"
/>
<ErrorMessage name='luasTanah' component={TextError}/>
</div>
<div>
<label htmlFor="BiayaPBB"> Biaya PBB </label>
<Field className="formBiodata"
type="text" id="outlined-basic"
placeholder="BiayaPBB"
fullWidth
id="BiayaPBB"
name="BiayaPBB"
margin="normal" variant="outlined"
/>
<ErrorMessage name='BiayaPBB' component={TextError}/>
</div>
<Button onClick={sweetAlertclick} type ="submit"
variant="contained" startIcon={<SaveIcon />} color="primary" style={{
marginLeft: '25rem', marginTop: '20px', width: '20rem', height: 45,
fontSize: 22, backgroundColor: '#22689F'}}
disabled={!formik.isDirty && !formik.isValid} >Simpan
</div>
)
}
}
</Formik>
)
}
Parent Component :
function UKTRumah ({isValidValue}) {
return (
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={onSubmit}
// validateOnMount
>
{
formik => {
console.log('Formik props', formik)
return(
<div className ="IsiBiodata">
<Accordion square expanded={expanded === 'panel1'} onChange=.
{handleChange('panel1')} style={{marginLeft: '15rem', marginRight:
'15rem', marginTop: '3rem'}}>
<AccordionSummary aria-controls="panel1d-content" id="panel1d-
header">
<PersonIcon/>
<Typography> Data Rumah</Typography>
<Typography}> { isValidValue }
</Typography>
</AccordionSummary>
<AccordionDetails>
<div className ="IsiBiodata">
<Form>
</div>
</Form>
</div>
</AccordionDetails>
</Accordion>
</div>
)}}
</Formik>
)}
Thank you
Your example code seems to be lacking some key lines to answer the question specifically.
However, generally if it is data that Parent should be aware of, but that the child will make use of, it should be a value of state in the parent, then handed to the child as props. Here's a very small example using functional components:
const Child = ({ formik, setIsValid, isValid }) => {
useEffect(() => {
setIsValid(formik.isValid)
}, [formik.isValid]);
return <input />;
}
const Parent = () => {
const [isValid, setIsValid] = useState(true);
return <Child isValid={isValid} setIsValid={setIsValid} />
}
You can hold the value on your parent and pass a function to change it to your child. I can't really show you that with the code you posted, but I can show an example of what I mean. The parent has a state with an update function setIsValid and passes that to the child. The child can call setIsValid and that will update the isValid value on the parent.
parent
function Parent() {
const [isValid, setIsValid] = useState(false);
return <div>
<Child setIsValid={setIsValid} />
IsValid {isValid}
</div>
}
child
function Child({ setIsValid }) {
return <button onClick={() => setIsValid(true)}>Set Valid</button>
}

saga fetch data after component rendered

hi sorry for my bad english.i am using react and redux.i dispatch getTags action in layout component.problem is after getData action called,getDataSuccess action called after my components rendered.so my data is null.
how i can be sure that data is fetched and render my components?
layout:
function DashboardLayout({
children,
showSideBar,
backgroundColor,
getTagsFromServer,
getCategoriesFromServer,
}) {
getTagsFromServer();
getCategoriesFromServer();
return (
<StyledDashbordLayout>
<NavBar />
<SideBarDrawer />
<Grid container className="container">
{showSideBar && (
<Grid item className="sidebar-section">
<SideBar />
</Grid>
)}
<Grid item className="content">
{children}
</Grid>
</Grid>
</StyledDashbordLayout>
);
}
DashboardLayout.propTypes = {
children: PropTypes.node.isRequired,
showSideBar: PropTypes.bool.isRequired,
backgroundColor: PropTypes.string,
getTagsFromServer: PropTypes.func,
getCategoriesFromServer: PropTypes.func,
};
function mapDispatchToProps(dispatch) {
return {
dispatch,
getTagsFromServer: () => dispatch(getTags()),
getCategoriesFromServer: () => dispatch(getCategories()),
};
}
const withConnect = connect(
null,
mapDispatchToProps,
);
export default compose(withConnect)(DashboardLayout);
saga:
import { call, put, takeLatest } from 'redux-saga/effects';
function* uploadVideo({ file }) {
try {
const { data } = yield call(uploadVideoApi, { file });
yield put(uploadFileSuccess(data));
} catch (err) {
yield put(uploadFileFail(err));
}
}
function* getTags() {
const { data } = yield call(getTagsApi);
console.log(data, 'app saga');
yield put(getTagsSuccess(data));
}
function* getCategories() {
const { data } = yield call(getTCategoriesApi);
yield put(getCategoriesSuccess(data));
}
// Individual exports for testing
export default function* appSaga() {
yield takeLatest(UPLOAD_VIDEO, uploadVideo);
yield takeLatest(GET_TAGS, getTags);
yield takeLatest(GET_CATEGORIES, getCategories);
}
this is my select box component which gets null data from store:
import React, { useState } from 'react';
function UploadFileInfo({ tags, categories }) {
return (
<Paper square className={classes.paper}>
<Tabs
onChange={handleChange}
aria-label="disabled tabs example"
classes={{ indicator: classes.indicator, root: classes.root }}
value={tab}
>
<Tab
label="مشخصات ویدیو"
classes={{
selected: classes.selected,
}}
/>
<Tab
label="تنظیمات پیشرفته"
classes={{
selected: classes.selected,
}}
/>
</Tabs>
{tab === 0 && (
<Grid container className={classes.info}>
<Grid item xs={12} sm={6} className={classes.formControl}>
<label htmlFor="title" className={classes.label}>
عنوان ویدیو
</label>
<input
id="title"
type="text"
className={classes.input}
onChange={e => setValue('title', e.target.value)}
defaultValue={data.title}
/>
</Grid>
<Grid item xs={12} sm={6} className={classes.formControl}>
<SelectBox
onChange={e => setValue('category', e.target.value)}
value={data.category}
label="دسته بندی"
options={converItems(categories)}
/>
</Grid>
<Grid item xs={12} className={classes.textAreaWrapper}>
<label htmlFor="info" className={classes.label}>
توضیحات
</label>
<TextField
id="info"
multiline
rows={4}
defaultValue={data.info}
variant="outlined"
classes={{ root: classes.textArea }}
onChange={e => setValue('info', e.target.value)}
/>
</Grid>
<Grid item xs={12} sm={6} className={classes.formControl}>
<SelectBox
onChange={e => {
if (e.target.value.length > 5) {
console.log('hi');
setError(
'tags',
'تعداد تگ ها نمی تواند بیشتر از پنج عدد باشد',
);
return;
}
setValue('tags', e.target.value);
}}
value={data.tags}
label="تگ ها"
options={converItems(tags)}
multiple
onDelete={id => deleteTagHandler(id)}
error={errors.tags}
/>
</Grid>
</Grid>
)}
{tab === 1 && 'دومی'}
<Dump data={data} />
</Paper>
);
}
const mapStateToProps = createStructuredSelector({
tags: makeSelectTags(),
categories: makeSelectCategories(),
});
const withConnect = connect(
mapStateToProps,
null,
);
export default compose(withConnect)(UploadFileInfo);
Question Summary
If I understand your question correctly, you are asking how to guard the passing of options, options={converItems(tags)}, in the SelectBox in UploadFileInfo against null or undefined values when the data hasn't been fetch yet.
Solutions
There are a few options for either guarding against null or undefined values.
Easiest is to provide a default fallback value for tags. Here I am making an assumption the tags are an array, but they can be anything, so please adjust to match your needs.
Inline when passed options={converItems(tags || []) or options={converItems(tags ?? [])
In the function signature function UploadFileInfo({ tags = [], categories })
As part of a fallback return value in makeSelectTags
Another common pattern is conditional rendering where null may be the initial redux state value, so you simply wait until it is not null to render your UI.
Early return null if no tags
function UploadFileInfo({ tags, categories }) {
if (!tags) return null;
return (
<Paper square className={classes.paper}>
...
Conditional render SelectBox
{tags ? (
<SelectBox
...
/>
) : null
}
Side Note about fetching data calls in DashboardLayout
When you place function invocations directly in the function body they will be invoked any time react decides to "render" the component to do any DOM diffing, etc..., pretty much any time DashboardLayout renders the data fetches are made, which could have unintended effects. For this reason, react functional component bodies are supposed to be pure functions without side effects. Place any data fetching calls in an effect hook that is called only once when the component mounts (or appropriate dependency if it needs to be called under other specific conditions).
useEffect(() => {
getTagsFromServer();
getCategoriesFromServer();
}, []);
Use your functions to call the API inside React.useEffect.
All your API calls should be inside the useEffect hook.
For more on useEffect, read this
function DashboardLayout({
children,
showSideBar,
backgroundColor,
getTagsFromServer,
getCategoriesFromServer,
}) {
React.useEffect(() => {
getTagsFromServer();
getCategoriesFromServer();
}, []);
return (
<StyledDashbordLayout>
<NavBar />
<SideBarDrawer />
<Grid container className="container">
{showSideBar && (
<Grid item className="sidebar-section">
<SideBar />
</Grid>
)}
<Grid item className="content">
{children}
</Grid>
</Grid>
</StyledDashbordLayout>
);
}

How can I pass a child component's state up to the parent?

I am new to ReactJS. Please forgive me if it is so simple.
I am trying to inject the radio button component (RadioButton.js) into home page. So that the radio button appear on home page. It like a child. As you can see from RadioButton.js, I have two radio buttons. Their values are buttonOne and buttonTwo.
What I am trying to achieve is that when buttonOne is selected, I would like to show <TablePage/> components. otherwise, <StickyHeadTable />
RadioButton.js
export default function FormControlLabelPosition() {
const [value, setValue] = React.useState("female");
const handleChange = event => {
setValue(event.target.value);
};
return (
<FormControl component="fieldset">
<RadioGroup
aria-label="position"
name="position"
value={value}
onChange={handleChange}
row
>
<FormControlLabel
value="buttonOne"
control={<Radio color="primary" />}
label="F1"
/>
<FormControlLabel
value="buttonTwo"
control={<Radio color="primary" />}
label="F2"
/>
</RadioGroup>
</FormControl>
);
}
RadioButton is injected in homepage. How can i get the values from RadioButton.js. So that I can use the condition.
HomePage.js
return (
<div className="home-page">
<RadioButton values={values} handleChange={handleChange}></RadioButton>
{values.flight === "buttonOne" ? <TablePage /> : <StickyHeadTable />}
</div>
);
RadioButton.js
export default function FormControlLabelPosition(props) {
return (
<FormControl component="fieldset">
<RadioGroup
aria-label="position"
name="position"
value={props.value}
onChange={props.handleChange}
row
>
<FormControlLabel
value="buttonOne"
control={<Radio color="primary" />}
label="F1"
/>
<FormControlLabel
value="buttonTwo"
control={<Radio color="primary" />}
label="F2"
/>
</RadioGroup>
</FormControl>
);
}
HomePage.js
const [value, setValue] = React.useState("female");
const handleChange = event => {
setValue(event.target.value);
};
return (
<div className="home-page">
<RadioButton values={values} handleChange={handleChange}></RadioButton>
{values.flight === "buttonOne" ? <TablePage /> : <StickyHeadTable />}
</div>
);
If you want to use the value from the RadioButton component, you should create it as an uncontrolled form component meaning that its value would come from it's parent, in this case the HomePage component.
So the RadioButton.js would be:
export default function RadioButton({ value, onChange }) {
return (
<FormControl component="fieldset">
<RadioGroup
aria-label="position"
name="position"
value={value}
onChange={onChange}
row
>
<FormControlLabel
value="buttonOne"
control={<Radio color="primary" />}
label="F1"
/>
<FormControlLabel
value="buttonTwo"
control={<Radio color="primary" />}
label="F2"
/>
</RadioGroup>
</FormControl>
);
}
RadioButton.propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired
};
And the HomePage.js
export default function HomePage() {
const [value, setValue] = React.useState("buttonOne");
const handleChange = event => {
setValue(event.target.value);
};
return (
<div className="home-page">
<RadioButton value={value} onChange={handleChange}></RadioButton>
{value === "buttonOne" ? <TablePage /> : <StickyHeadTable />}
</div>
);
}
On the HomePage.js you can use state for showing up the table conditionally based on radio button value.
I assume RadioButton.js component is called in HomePage.js as component.
RadioButton.js
export default function FormControlLabelPosition(props) {
const [value, setValue] = React.useState("female");
const handleChange = event => {
setValue(event.target.value);
> //Send your radio button value to parent component i.e HomePage.js
props.handleChange(event.target.value);
};
return (
<FormControl component="fieldset">
<RadioGroup
aria-label="position"
name="position"
value={value}
onChange={handleChange}
row
>
<FormControlLabel
value="buttonOne"
control={<Radio color="primary" />}
label="F1"
/>
<FormControlLabel
value="buttonTwo"
control={<Radio color="primary" />}
label="F2"
/>
</RadioGroup>
</FormControl>
);
}
HomePage.js
state = {
radioButtonValue: '';
}
render () {
return (
<div className="home-page">
<RadioButton handleChange={this.handleChange} />
{this.state.radioButtonValue === "buttonOne" ?
<TablePage /> : <StickyHeadTable />}
</div>
);
}
handleChange = (radioButtonValue) => {
this.setState({radioButtonValue});
}
One the above code, we are sending handleChange as a props and change the state as soon as radio-button is clicked and then rendering the table based on the state.

Props not recognized

When clicking a button i have an event error, the problem is i dont know how to convert this with hooks
const Header = () => {
const [value, setValue] = React.useState(0);
function handleChange(event, newValue) {
setValue(newValue);
}
function onLogoutClick(e) {
e.preventDefault();
this.props.logoutUser();
}
//this.onLogoutClick = this.onLogoutClick.bind(this);
return (
<div className={classes.root}>
<AppBar position="sticky">
<Tabs value={value} onChange={handleChange} centered>
{" "}
{/* <Tabs value={value} onChange={handleChange}>
{sections.map(section => (
<Tab label={section} />
))}
</Tabs> */}{" "}
<Tab label="Indicadores Globais" />
<Tab label="Indicadores Colaboradores" />
<Tab label="Indicadores Produto" />
<Tab label="Indicadores Temporais" />
<Button
color="inherit"
className={classes.classesButton}
onClick={onLogoutClick}
>
Logout
</Button>
TypeError: Cannot read property 'props' of undefined when clicking on the button. I know the problem is on the onClick={onLogoutClick} but im not sure how to solve this. Any help?
An event handler will override this in the callback with the event object. To make sure the component is scoped in the callback, you need to bind it to the function.
<Button
color="inherit"
className={classes.classesButton}
onClick={onLogoutClick.bind(this)}
>
this inside onLogoutClick points to the html element. If you want to access the component as this, You need to bind the action with this.
<Button
color="inherit"
className={classes.classesButton}
onClick={onLogoutClick.bind(this)}
>
But this is not the recommended method as it binds this with action every time component renders. The recommended method is to bind the action with this in the constructor.
constructor(props) {
super(props);
this.state = {
};
this.onLogoutClick = this.onLogoutClick.bind(this);
}

Categories

Resources