Toggle state from custom state variables - javascript

I have a reactstrap Collapse component that I'm trying to toggle state from an external Button that sits within a loop of mapped items using custom state variables.
My question:
Why does it only open and not toggle the collapse component when I have the state on my openCollapse method to setState to !state.collapse?
My code:
// some_items.js (brief example)
// State
this.state = {
toggleCollapse: false
}
// my custom state variable that I want to have toggle
openCollapse(itemId) {
this.setState(state => ({
[`toggleCollapse-${itemId}`]: !state.collapse
}));
}
// mapped item with button trigger for toggling the collapse
<div key={item.id>
<Button
onClick={() => {
this.openCollapse(item.id);
}}
>
View Listed Item Info
</Button>
//
// Some extra content that belongs here in between..
//
<ItemInfoCollapse
show={this.state[`toggleCollapse-${item.id}`]}
item={item}
/>
</div>
// My item_collapse.js
class ItemInfoCollapse extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
collapse: false,
isOpen: this.props.show
};
}
componentWillReceiveProps(nextProps) {
this.setState({ isOpen: nextProps.show });
}
toggle = () => {
this.setState({ collapse: !this.state.collapse });
};
render() {
return (
<Collapse isOpen={this.state.isOpen} className={this.props.className}>
// Some JSX markup
</Collapse>
)
}

What dictates whether your Collapse component gets open or closed is based on the show prop that you are passing into it from your parent component.
It appears you have everything set up correctly, with the exception of your state variable that you're using in your openToggle function - !state.collapse. I don't see the collapse variable anywhere which means it's undefined so it's actually running !undefined which always evaluates to true (you can test this in a browser console).
I think what you mean is !state[toggleCollapse-${itemId}] instead of !state.collapse

Related

Unable to open/close modal with parent class component and child functional component

I have a parent component in which I'm struggling to properly open/close the child component (modal). The two code boxes below are simplified examples of my components.
EDIT: Here is a code sandbox with the following code -- there isn't an actual modal, however i've logged all of the stateful values that I assume will have an effect on this problem and you can see how they change/don't change as I hope they would.
Code Sandbox
When the parent component is open, I can click the MenuItem and I can see the state change, however the modal doesn't open unless I close the parent component temporarily and reopen it (then the parent component opens with the modal open already)
When the modal is open, and I try to close by clicking the close button (which has the state changing function from parent inside of the onClick method. this.state.showModal remains true, and doesn't change to false.
If I add a closeModal stateful value to the child component and change it during the close buttons onClick, this.state.showModal still remains true.
Thanks to whoever reaches out, and if you have any clarifying questions feel free to ask!
class Parent extends Component {
constructor(props) {
super(props);
this.showModal = this.showModal.bind(this);
this.closeModal = this.closeModal.bind(this)
this.state = {
showModal: false
};
this.showModal = this.showModal.bind(this)
this.closeModal = this.closeModal.bind(this)
}
showModal() {
this.setState({ showModal: true });
}
closeModal() {
this.setState({ showModal: false });
}
render() {
return (
<MenuItem onClick={this.showModal}>
<ChildComponent
prop1={prop1}
isOpen={this.state.showModal}
closeModal={this.closeModal}
/>
</MenuItem>
)}
const ChildComponent = ({
prop1,
isOpen,
closeModal
}) => {
const [modalOpen, setModalOpen] = useState(isOpen)
useEffect(() => {
setModalOpen(isOpen)
},[isOpen])
console.log('isopen on child', isOpen)
console.log('modalOpen', modalOpen)
return (
<div>
{modalOpen && (
<button
onClick={() => {
setModalOpen(false)
closeModal()
}}
>
{'click to close modal'}
</button>
)}
</div>
)}
)}
I figured out my problem!
In my parent component the onClick handler that sets the modal open wrapped my child component. I needed to remove it and conditionally render it separately like so:
<div>
<div onClick={this.showModal}>{"Click here to open modal"}</div>
{this.state.showModal && (
<ChildComponent
prop1={prop1}
isOpen={this.state.showModal}
closeModal={this.closeModal}
/>
)}
</div>

React: problem calling child function in parent component [duplicate]

This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 1 year ago.
I made a games page in react. When you win one, it displays a form dialog (with material-UI: https://material-ui.com/components/dialogs/#form-dialogs). Component's visibility depends on the open attribute, which is changed with "handleClickOpen" when you push the button. I wanted to reuse code so I made a component that contains the dialog. Here is my code so far (child class):
class Pop_dialog extends Component {
constructor(props) {
super(props)
this.state = {
open: false
}
}
handleOpen() {
console.log('A')
this.setState({ open: true })
}
handleClose() {
console.log('B')
this.setState({ open: false })
}
render() {
return (
<Dialog open={this.state.open} onClose={this.handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Subscribe</DialogTitle>
<DialogContent>
<DialogContentText>
To subscribe to this website, please enter your email address here. We will send updates
occasionally.
</DialogContentText>
<TextField
autoFocus
margin="dense"
id="name"
label="Email Address"
type="email"
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color="primary">
Cancel
</Button>
<Button onClick={this.handleClose} color="primary">
Subscribe
</Button>
</DialogActions>
</Dialog>
)
}
I call "handleOpen" within a function in the parent class:
triggerDialog() { this.dialogRef.current.handleOpen(); }
render ()
{
...
<Pop_dialog ref={this.dialogRef}/>
}
The triggerDialog function is called when I win/lost the game and it opens the form dialog fine. The problem comes when I try to close it (with the Cancel or Subscribe buttons). The page throws the next error:
I couldnĀ“t find why it fails here but not when it use "handleOpen". By the way, this is the 4th solution that i use. I also tried using a function component with the useContext hood (It didn't work at all), passing 'open' like a prop to the child (I also could open the dialog but not close it) and turn 'open' in a session var, defined in the parent component and called in the child (I couldn't open the dialog).
I don't know if some of this ideas would work or if I need a completely new one. I am open to any kind of idea, as long as it keeps Pop_dialog reusable.
It doesn't seem as though you've bound this to the handlers in Pop_dialog. The result is that this is undefined in the callback handlers.
Bind in the constructor:
class Pop_dialog extends Component {
constructor(props) {
super(props)
this.state = {
open: false
}
this.handleOpen = this.handleOpen.bind(this);
this.handleClose = this.handleClose.bind(this);
}
handleOpen() {
console.log('A')
this.setState({ open: true })
}
handleClose() {
console.log('B')
this.setState({ open: false })
}
...
Or convert the handlers to arrow functions so this of the class is bound automatically.
class Pop_dialog extends Component {
constructor(props) {
super(props)
this.state = {
open: false
}
}
handleOpen = () => {
console.log('A')
this.setState({ open: true })
}
handleClose = () => {
console.log('B')
this.setState({ open: false })
}
...

React.js Getting State to Immediately Update

I'd like for my state to update changes to immediately be shown when state changes, but I can't seem to figure out why It isn't. Basically when a user clicks on a dropdown item from the menu, the items inner text ... that they clicked on should appear as an h1 on the screen, but instead it doesn't appear until the next click. How can I change this? Hopefully I made sense. Code can be found here.
Parent Component (APP):
class App extends React.Component {
state = {
loading: true,
bases: ['USD', 'EUR', 'AUD', 'CAD', 'JPY', 'NZD'],
selectedBase: null
};
// When Component Mounts Overlay goes for 3 Seconds
componentDidMount() {
setTimeout(() => this.setState({
loading: false,
}), 3000)
this.onBaseChange('USD');
}
// When User selects a new Base in Search Component, state is updated
onBaseChange = newBase => {
this.setState({ selectedBase: newBase });
}
// need to find out how to see state change immediatly after its updated!
// Rendered Content:
render(){
return (
<>
{this.state.loading === false ? (
<div className="App">
<div id="one">
<h1>{this.state.selectedBase}</h1>
<Search bases = {this.state.bases} selectedBase = {this.state.selectedBase} onBaseChange = {this.onBaseChange}/>
</div>
</div>
) : (
<Overlay />
)}
</>
);
}
}
export default App;
Child Component (Search):
class Search extends Component {
state = {
dropdownVisible: false,
term: '',
selectedBase: this.props.selectedBase
};
// when a base is clicked from dropdown, the selectedBase is updated, term is set back to empty, and dropdown back to non-visible.
// passing state of child up to parent through prop
// clearing input search on click
onBaseSelect = (event) => {
// when an base is clicked from dropdown, the selectedBase is updated, term is set back to empty, and dropdown back to nonvisible.
this.setState({
selectedBase: event.target.innerHTML,
term: '',
dropdownVisible: false
})
// passing state of child up to parent through prop
this.props.onBaseChange(this.state.selectedBase)
// clearing input search on click
document.getElementById("input_search").value = "";
}
render(){
return(
<div id="search">
<div id="dropdown" style={{display: this.state.dropdownVisible ? "block" : "none"}}>
<ul>
{/* filterng out base array based on users input */}
{this.props.bases.filter(base => base.includes(this.state.term.toUpperCase())).map((filteredBase, index) => (
<li onClick = {this.onBaseSelect} key={index}>{filteredBase}</li>
))}
</ul>
</div>
</div>
)
}
}
export default Search
this.setState is an asynchronous function, so when you do
// passing state of child up to parent through prop
this.props.onBaseChange(this.state.selectedBase)
// clearing input search on click
document.getElementById("input_search").value = "";
the state is not yet updated. So send that code as as a callback to this.setState like this,
onBaseSelect = (event) => {
// when an base is clicked from dropdown, the selectedBase is updated, term is set
back to empty, and dropdown back to nonvisible.
this.setState({
selectedBase: event.target.innerHTML,
term: '',
dropdownVisible: false
},
()=>{
// passing state of child up to parent through prop
this.props.onBaseChange(this.state.selectedBase)
// clearing input search on click
document.getElementById("input_search").value = "";
);
}

Updating parent component state from child component

I have one parent component that holds state of clicking : if file is clicked or not.
Files come from child component.
I know that I can use props and call function from parent, but doing that, I get this.props.handleStateChange is not a function
export class Parent extends Component {
constructor(props) {
super(props);
this.state = {
clickable: false
};
this.handleStateChange = this.handleStateChange.bind(this);
}
handleStateChange = (val) => {
this.setState({ clickable: val })
}
render() {
return (
<Child handleStateChange={this.handleStateChange} />
);
}
}
class Child extends Component {
constructor(props) {
super(props);
this.state = {
clickable: false
};
}
handleClick = () => {
this.state.clickable ? this.setState({ clickable: false }) :
this.setState({ clickable: true });
this.props.handleStateChange(this.state.clickable)
}
render() {
return (
<div className={this.state.clickable ? 'clickable' : null}
>
<img className="item" src={file} alt="file" onClick=
{this.handleClick} />
</div>
);
}
}
Any ideas what am I missing there?
Here everything is working fine, no errors.
If the code you add in your question isn't the real code you are working with, maybe try checking for typos.
Probably you are passing the prop with the wrong/different name.
Some tips that aren't related to the question
Instead of
this.state.clickable
? this.setState({ clickable: false })
: this.setState({ clickable: true });
You should do
this.setState(prevState => ({clickable: !prevState.clickable}))
setState is asynchronous, so using your newly set state immediately after isn't guaranteed to work. Instead, try this for handleClick:
handleClick = () => {
this.setState(prevState => {
this.props.handleStateChange({ !prevState.clickable });
return { clickable: !prevState.clickable };
})
}
That said, you're maintaining the same state in the parent and child. Probably better to set it in the parent (from the child) and pass it to the child as a prop.
Also also, since you're using an arrow function, you don't need to bind any of your functions in the constructor.

React can't figure out how to send data to my parent accordingly

I want to create this React Vending Machine and I need to send some id values from a child to the parent and then make some functions that require that value
I've managed to make the proper function but it doesn't behave like I want to. When I click the button the first time my query is updated but it is not transferred to the parent component. Only when I click the second time my parent gets updated with the state that it was before. So its always one state late.
class App extends Component {
constructor() {
super()
this.handleData = this.handleData.bind(this);
this.state = {
fromChild: ""
}
}
handleData(data) {
this.setState({
fromChild: data
});
}
render() {
return (
<div className="App">
<Glass />
<FrontPanel handlerFromParent={this.handleData.bind(this)}/>
<h5>Received by parent:<br />{this.state.fromChild}</h5>
</div>
);
}
}
class FrontPanel extends Component {
constructor(){
super()
this.state = {
query: ""
}
}
/* function that manages the keybord */
addToQuery = (id) => {
this.setState((prevState) => ({
query: prevState.query + id
}))
this.onChangeQuery();
}
/* function that sends data to parent */
onChangeQuery() {
this.props.handlerFromParent(this.state.query)
}
render() {
return (
<div>
<Screen
credit={this.state.credit}
query={this.state.query}/>
<div className="keybord-layout">
<div className="keybord">
<button onClick={e => this.addToQuery(e.target.id)} id="1">1</button>
<button onClick={e => this.addToQuery(e.target.id)} id="2">2</button>
<button onClick={e => this.addToQuery(e.target.id)} id="3">3</button>
</div>
</div>
</div>
)
}
}
export default FrontPanel;
I want when I click a button to transfer the state to the parent immediately and not wait for another button press. Also, I don't want the state to be behind in the parent.
A setState call is asynchronous so this.state.query may not yet be updated when you called this.onChangeQuery(); that calls your parent component handler.
Try calling your parent handler in the callback function that you can pass to setState.
this.setState(
prevState => ({
query: prevState.query + id
}),
() => this.onChangeQuery()
);
This way, you're ensured that query is the new value when you pass it back to parent.

Categories