The useState's value will change when the current button is clicked, which will call the child component.
Child component is Modal.
However, when the parent's button is repressed, the changed value is not changed again and will not be recalled.
https://codesandbox.io/s/patient-snowflake-wdm78
You can send the callback function to the Child Component like this.
StepComp.js
const StepComp = () => {
const [isModalVisible, setIsModalVisible] = useState(false);
const btnStyled = {
margin: "10rem"
};
const setVisible = visible => {
setIsModalVisible(visible);
};
return (
<div>
<Button onClick={() => setIsModalVisible(true)} style={btnStyled}>
jump
</Button>
<LoginModal open={isModalVisible} setVisible={setVisible} />
</div>
);
};
LoginModal.js
const LoginModal = ({ open, setVisible }) => {
const inputStyled = {
borderColor: "none",
borderBottom: "1px solid #EBEBEB",
marginBottom: "1rem"
};
return (
<div>
<Modal
title="login"
centered
visible={open}
onOk={() => setVisible(false)}
onCancel={() => setVisible(false)}
>
<div>
<input type="text" style={inputStyled} /> <br />
<input type="text" style={inputStyled} />
</div>
</Modal>
</div>
);
};
You're using isModelVisible and visible state properties, and initializing them a a boolean, then setting them to the opposite. This works, but only the first time.
What you need to do is setVisible(!visible) and setIsModalVisible(!isModalVisible) respectively.
While this will make it work, what I suggest doing instead is actually only having one state property, in the parent (StepComp), which then is passed as a callback to the child (Modal) along with the property whether it is open, then you render the child, if it's true, and use the same setState function in the child to close it.
This approach keeps all the logic for opening the Modal in one component.
Related
To expand the visual, I need to pass the Onclick function to antd Expand icon(shown below).
const showModal = () => {
setIsModalOpen(true);
};
const handleOk = () => {
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
This is how i passed it to normal button currently.
<div className="expandButton">
<button
type="button"
className="fullScreenButton"
onClick={showModal}
>
Click
</button>
</div>
Instead of this way can i pass the onclick function directly to react antd icon?
Current antd icon.
<div>
<ExpandAltOutlined style={{ fontSize: "150%" }} />
</div>
Simply add an onClick event
<ExpandAltOutlined style={{ fontSize: "150%" }} onClick={()=>console.log('hj')} />
I'm working with React Hook Form. I have a higher order component that is using FormProvider and managing the submitting of the form. I want to trigger this function from a child component. The problem I'm having is that when I call the function in the child component the data in the onSubmit function returns undefined. However it works as expected when I click submit in the same component. What am I doing wrong?
const FormGroup = () {
const onSubmit: SubmitHandler<Inputs> = data => {
console.log('data', data);
dispatch(setEntityInformation({
data
}));
}
};
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)}>
<section className="sars__sideBar">
<TabBar
controls={TAB_CONTROLS.controls}
activeIndex={index}
setActiveTab={i => setActiveTab(i)}
onSubmit={onSubmit}
/>
</section>
<PrimaryButton onClick={() => {
setActiveTab(index + 1);
}}
>
<input type="submit" value="Continue" /> // submits form as expected
</PrimaryButton>
</form>
</FormProvider>
}
// Child component
const TabBar = ({ activeIndex, setActiveTab, controls, onSubmit }) => (
<Tabs>
{controls.map((control, i) => (
<div
className={`tab__wrapper ${i === activeIndex ? 'active' : ''}`}
key={control.key}
onClick={() => {
setActiveTab(i);
if (i > activeIndex) {
onSubmit(); // function is called, but data is undefined
}
}}
>
<div className="u-alignCenter">
<p>{control.label}</p>
</div>
<div className="tab__status" />
</div>
))}
</Tabs>
);
Maybe calling onSubmit() through handleSubmit would help? Like this: handleSubmit(onSubmit)();
You need this, because handleSubmit is ran asynchronously and with the second parentheses you actually call the returned function.
You can read more about it here
I'm using a custom hook to open a modal which will perform different actions.
To open and close the modal im using the custom hook useSongUtils methods openModal and closeModal:
export const useSongUtils = () => {
const [isEditing, setIsEditing] = useState(false);
const openModal = ({ cellData }) => {
// outputs undefined
console.log('cell data is', cellData);
setIsEditing(true);
};
const closeModal = () => {
setIsEditing(false);
};
return {
closeModal, isEditing, setIsEditing, openModal,
};
};
And then importing the returned object into my component, where I have a method for a VirtualTable that renders some action links.
The cell data is an id and it is displayed correctly in the optionsRender method (both links work - I get the id). However, the idea is that clicking the Button element, calls the openModal method from usesongUtils and sets isEditing to true. That works.
However I'm also trying to get the cellData arguments in openModal method and it is not working. I'm getting undefined if I try to console.log.
// SongList.js
const optionsRender = ({ cellData }) => (
<div className='songs-list__options'>
<Link to={`/songs/${cellData}/edit`}>
<Icon name='edit' style={{ margin: '0 .2rem .2rem 0' }} />
</Link>
<a rel='noreferrer' target='_blank' href={`localhost/client/song/${cellData}`}>
<Icon name='play' style={{ margin: '0 .2rem' }} />
</a>
{cellData} // I can see the data!!
// im trying to pass cellData to openModal
<Button size='tiny' className='ui button' fluid icon='setting' circular onClick={() => openModal(cellData)} />
</div>
);
const {
closeModal, isEditing, openModal,
} = useSongUtils();
So im working on a React app, it's basically a CRUD app pretty much (serving right now as a learning practice project).
Anyways, I have a modal that appears when the user needs to edit some basic information on a resource (right now it's just a "name" that's being edited). The modal is a presentational/dumb component that takes in props from it's parent (Which is essentially just a table of the resources/items).
If im literally only going to be submitting the form to change 1 field I would only need a state with 1 item (for that input field to edit).......does it make sense to STILL make it a class just because it has a state? From what i've read if you have a state you should automatically make it a class?
Is this always true?
Are you talking like this?
When you click on any list item it goes to child(functional) component as you were asking.
You can check working live example Live demo
export default function App() {
const [arr, setArr] = useState(["One", "Two", "Three"]);
const [name, setName] = useState("");
const handleChange = e => {
setName(e.target.value);
};
const handleSubmit = e => {
e.preventDefault();
setArr([...arr, name]);
setName("");
};
return (
<div>
{arr.map(a => (
<div style={myStyle} key={a}>
{a} <button onClick={() => setName(a)}>Edit</button>
</div>
))}
<Child
handleChange={handleChange}
handleSubmit={handleSubmit}
name={name}
/>
</div>
);
}
const Child = ({ handleChange, name, handleSubmit }) => {
return (
<form onSubmit={handleSubmit}>
<input type="text" value={name} onChange={handleChange} />
<input type="submit" value="Save" />
</form>
);
};
const myStyle = {
margin: 5,
cursor: "pointer",
border: "1px solid lightgrey",
padding: "5px",
display: "flex",
justifyContent: "space-between"
};
Yes whenever you use a state you should make it a class (way back then) but now we have this thing called HOOKS that allows you to use states within functional components.
Simple sample implementation below.
import React, { useState } from 'react';
const Example = () => {
//creates a state name and a setter for it. Then initialize it with empty string
const [name, setName] = useState("");
return (
<div>
//suppose you have input here
<button onClick={() => setName("Your name value")}>
Click me
</button>
</div>
);
}
I'm not yet a React master, hence my question. Why there is still invoking a parent function if in child component I'm writing new characters in input fields? I want to call parent method only when I clicked Search button in my child component.
Parent component:
class MainPage extends Component {
render() {
let searchOffersBar = (
<MuiThemeProvider>
<SearchOffer
offersFound={this.props.onOffersFound}
/>
</MuiThemeProvider>
);
let searchResults = (
<SearchResults
offers={this.props.offers}
/>
);
return (
<Aux>
<div className={classes.container}>
<Intro/>
<div className={classes.contentSection}>
{searchOffersBar}
{searchResults}
</div>
</div>
</Aux>
)
}
}
const mapStateToProps = state => {
return {
offers: state.offers.offers
}
}
const mapDispatchToProps = dispatch => {
return {
onOffersFound: (searchParams) => dispatch(actions.fetchOffersByCriteria(searchParams))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MainPage);
<SearchOffer> is my child component with a search section (input fields and button "Search offers"). I want to fill some data in my inputs and then click the button. I though that clicking the button will invoke a method in child component: onOffersFound:
const searchOffer = props => {
let currentDate = new Date();
const [searchCriteria, setSearchCriteria] = useState({
brand: 'xxx',
capacity: 100
})
const [drawerIsOpen, setDrawerIsOpen] = useState(false);
const handleToggle = () => setDrawerIsOpen(!drawerIsOpen);
const handleBrand = (event) => {
let mergedState = updateObject(searchCriteria, {brand: event.target.value})
setSearchCriteria(mergedState);
}
const handleCapacity = (event) => {
let mergedState = updateObject(searchCriteria, {capacity: event.target.value});
setSearchCriteria(mergedState);
}
const handleBookingFrom = (bookingFromValue) => {
let mergedState = updateObject(searchCriteria, {bookingFrom: bookingFromValue});
setSearchCriteria(mergedState);
}
const handleBookingTo = (bookingToValue) => {
let mergedState = updateObject(searchCriteria, {bookingTo: bookingToValue});
setSearchCriteria(mergedState);
}
return (
<div className={classes.sideNav}>
<Button variant={"outlined"} onClick={handleToggle} className={classes.sideNavBtn}>Search</Button>
<Drawer
className={classes.drawer}
containerStyle={{top: 55}}
docked={false}
width={200}
open={drawerIsOpen}
onRequestChange={handleToggle}
>
<AppBar title="Search"/>
<form noValidate autoComplete="off" onSubmit={props.offersFound(searchCriteria)}>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<Grid container justify="space-around">
<TextField
id="brand"
label="Brand"
margin="normal"
onChange={handleBrand}
/>
<TextField
id="capacity"
label="Capacity"
margin="normal"
onChange={handleCapacity}
/>
<Button variant="contained" color="primary">
Search
</Button>
</Grid>
</MuiPickersUtilsProvider>
</form>
</Drawer>
</div>
);
}
export default searchOffer;
onOffersFound in my action creator looks like:
export const fetchOffersByCriteria = (searchParams) => {
return dispatch => {
let queryParams = '?brand='+searchParams.brand + '&capacity='+searchParams.capacity;
axios.get('/getFilteredOffers' + queryParams)
.then(response => {
dispatch(saveFoundOffers(response.data)); --> saves the state
})
.catch(error => {
console.log(error);
})
}
}
My question is why the above method fetchOffersByCriteria is invoked every time I enter new character in my child component? I want to invoke this method only when I click the Search button in child component. Maybe my approach is bad?
Thanks for all tips!
The issue is that props.offersFound(searchCriteria) is being invoked every render. The onSubmit prop should be a function to be invoked when submitted. Currently, it's being invoked immediately.
This line:
onSubmit={props.offersFound(searchCriteria)}
Should be (or something similar):
onSubmit={() => props.offersFound(searchCriteria)}
Currently, when typing in the brand (or capacity) field, the handleBrand change callback is invoked. This invokes setSearchCriteria (a state update) which triggers a re-render of the component. While this component is re-rendering, it's immediately invoking props.offersFound(searchCriteria) and passing the return value to the onSubmit prop. You likely want the onSubmit prop to be a function to be invoked at the time of submitting.
See the documentation for controlled components for more de3tails.
<form
noValidate
autoComplete="off"
onSubmit={props.offersFound(searchCriteria)}>
You are immediately invoking prop and trying to use result returned as event listener. It should be
<form
noValidate
autoComplete="off"
onSubmit={() => props.offersFound(searchCriteria)}>
instead