I just started learning React and I have a couple cards I want to toggle a className from the component and remove the className from Parent. How do I do this? This is the child component
import React from 'react'
import { useState} from 'react'
const Card = ({check,cardID}) => {
const [isToggled,setToggle] = useState(false)
const flip = () => {
setToggle(!isToggled)
check()
}
return (
<div className='flip-card' >
<div id={`card${cardID}`} onClick={flip} className={`flip-card-inner ${isToggled ? "flipclass":""}`}>
<div className="flip-card-front">
<span>Flip for surprise</span>
</div>
<div className="flip-card-back">
<img className="card-img" src="/firstsect.jpg" alt="Avatar" height="100%" width="100%" />
</div>
</div>
</div>
)}
export default Card
and in the parent component I want to set the isToggled state to false so the flipclass wont be added
import React from 'react'
import Card from './Card'
import { useState, useRef } from 'react'
export const Cards = ({cardsarray,cardID}) => {
const [Counter,setCounter] = useState(0)
const check = ()=> {
setCounter(count => count+ 1)
if (Counter === 1) {
//Change isToggled on the other component to false
setCounter(0)
}
}
return (
<div className="all-cards">
{
cardsarray.map((_user,index)=>(
<Card cardID={index} check={check} key={index}></Card>
))
}
</div>
)}
I already tried using the state in the parent component and passing the istoggled as props but the whole array of cards gets the class instead of the particular card that calls the function. How do i go about this?
Related
I'm new to React and am attempting to set up a Bootstrap modal to show alert messages.
In my parent App.js file I have an error handler that sends a Modal.js component a prop that triggers the modal to show, eg:
On App.js:
function App() {
const [modalShow, setModalShow] = useState(false);
// Some other handlers
const alertModalHandler = (modalMessage) => {
console.log(modalMessage);
setModalShow(true);
}
return (
// Other components.
<AlertModal modalOpen={modalShow}/>
)
}
And on Modal.js:
import React, { useState } from "react";
import Modal from "react-bootstrap/Modal";
import "bootstrap/dist/css/bootstrap.min.css";
const AlertModal = (props) => {
const [isOpen, setIsOpen] = useState(false);
if (props.modalOpen) {
setIsOpen(true);
}
return (
<Modal show={isOpen}>
<Modal.Header closeButton>Hi</Modal.Header>
<Modal.Body>asdfasdf</Modal.Body>
</Modal>
);
};
export default AlertModal;
However, this doesn't work. I get the error:
Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
If I change the Modal component to be a 'dumb' component and use the prop directly, eg:
const AlertModal = (props) => {
return (
<Modal show={props.modalOpen}>
<Modal.Header closeButton>Hi</Modal.Header>
<Modal.Body>asdfasdf</Modal.Body>
</Modal>
);
};
It does work, but I was wanting to change the show/hide state on the Modal.js component level as well, eg have something that handles modal close buttons in there.
I don't understand why is this breaking?
And does this mean I will have to handle the Modal close function at the parent App.js level?
Edit - full app.js contents
import React, { useState } from 'react';
import './App.css';
import 'bootstrap/dist/css/bootstrap.css';
import AddUserForm from './components/addUserForm';
import UserList from './components/userList';
import AlertModal from './components/modal';
function App() {
const [users, setUsers] = useState([]);
const [modalShow, setModalShow] = useState(false);
const addPersonHandler = (nameValue, ageValue) => {
console.log(nameValue, ageValue);
setUsers(prevUsers => {
const updatedUsers = [...prevUsers];
updatedUsers.unshift({ name: nameValue, age: ageValue });
return updatedUsers;
});
};
const alertModalHandler = (modalMessage) => {
console.log(modalMessage);
setModalShow(true);
}
let content = (
<p style={{ textAlign: 'center' }}>No users found. Maybe add one?</p>
);
if (users.length > 0) {
content = (
<UserList items={users} />
);
}
return (
<>
<div className="container">
<div className="row">
<div className="col-md-6 offset-md-3">
<AddUserForm onAddPerson={addPersonHandler} fireAlertModal={alertModalHandler}/>
</div>
</div>
<div className="row">
<div className="col-md-6 offset-md-3">
{content}
</div>
</div>
</div>
<AlertModal modalOpen={modalShow}/>
</>
);
}
export default App;
In your modal.js
you should put
if (props.modalOpen) {
setIsOpen(true);
}
in a useEffect.
React.useEffect(() => {if (props.modalOpen) {
setIsOpen(true);
}}, [props.modalOpen])
You should never call setState just like that. If you do it will run on every render and trigger another render, because you changed the state. You should put the setModalShow together with the if clause in a useEffect. E.g.:
useState(() => {
if (modalOpen) {
setIsOpen(true);
}
}, [modalOpen])
Note that I also restructered modalOpen out of props. That way the useEffect will only run when modalOpen changes.
If you already send a state called modalShow to the AlertModal component there is no reason to use another state which does the same such as isOpen.
Whenever modalShow is changed, it causes a re-render of the AlertModal component since you changed it's state, then inside if the prop is true you set another state, causing another not needed re-render when you set isOpen. Then, on each re-render if props.showModal has not changed (and still is true) you trigger setIsOpen again and again.
If you want control over the modal open/close inside AlertModal I would do as follows:
<AlertModal modalOpen={modalShow} setModalOpen={setModalShow}/>
Pass the set function of the showModal state to the modal component, and there use it as you see fit. For example, in an onClick handler.
modal.js:
import React, { useState } from "react";
import Modal from "react-bootstrap/Modal";
import "bootstrap/dist/css/bootstrap.min.css";
const AlertModal = (props) => {
const onClickHandler = () => {
props.setModalOpen(prevState => !prevState)
}
return (
<Modal show={props.modalOpen}>
<Modal.Header closeButton>Hi</Modal.Header>
<Modal.Body>asdfasdf</Modal.Body>
</Modal>
);
};
export default AlertModal;
I am trying to reducing my code complexity to express by defining just skeleton code bellow. have to trigger the toggleModel of the child component
import React, { useState } from "react";
import "./styles.css";
const ChildComponent = (props) => {
// .... some useStates
const toggleModel = () => {
// have to trigger this methoud once user clicks on button
// have to change some states here
};
return (
<div>
{props.children}
...... other things .......
</div>
);
};
export default function ParentComponet() {
return (
<div className="App">
Hello
<ChildComponent>
<button
type="button"
onClick={() => {
// here i have to trigger the toggleModel function of ChildComponent
}}
>
Toggle Model
</button>
</ChildComponent>
</div>
);
}
i am rendering child component by sending children elements, have to trigger the toggleModel of the child component it will reduce my 70 % redundant code at our application. is there any way to achieve the same codesandbox. Thank you in advance
You can use useState and useEffect to pass state down and react to it.
import React, { useState } from "react";
import "./styles.css";
const ChildComponent = ({visible, children, setVisible}) => {
React.useEffect(() => {
const toggleModel = () => {
alert('Visible changes to ' + visible )
};
toggleModel()
}, [visible])
return <div>{children}</div>;
};
export default function ParentComponet() {
const [visible, setVisible] = React.useState(false)
return (
<div className="App">
Hello
<ChildComponent visible={visible} setVisible={setVisible}>
<button
type="button"
onClick={()=> setVisible(!visible)}
>
Toggle Model
</button>
</ChildComponent>
</div>
);
}
https://codesandbox.io/s/objective-ramanujan-j3eqg
The alternative is use #yaiks answer.
You can take a look at this question here, it can help you.
But I would say it's not a good practice to call a child function from the parent. Usually what I would do is to "lift up" the method to the parent, and pass down to the child if possible.
Here is another way to call your ChilComponent's function - using forwardRef:
import React, { useState, useImperativeHandle, forwardRef } from "react";
import "./styles.css";
const ChildComponent = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
toggleModel() {
alert("alert from ChildComponent");
}
}));
return <div>{props.children}</div>;
});
export default function ParentComponet() {
return (
<div className="App">
Hello
<ChildComponent ref={ChildComponent}>
<button
type="button"
onClick={() => ChildComponent.current.toggleModel()}
>
Toggle Model
</button>
</ChildComponent>
</div>
);
}
Sandbox: https://codesandbox.io/s/pensive-jones-lw0pf?file=/src/App.js
My answer is courtesy of #rossipedia: https://stackoverflow.com/a/37950970/1927991
So i have this class based component:
import React, { Component } from 'react'
import './OptionsMenu.sass'
import DropdownBox from '../DropdownBox/DropdownBox'
import Icon from '../Icon/Icon'
class OptionsMenu extends Component {
constructor(){
super()
this.dropdownBoxRef = React.createRef()
}
handleClickOutside = event => {
if (this.dropdownBoxRef && !this.dropdownBoxRef.current.contains(event.target)) {
this.props.close()
}
}
componentDidMount() {
document.addEventListener('mousedown', this.handleClickOutside)
}
componentWillUnmount() {
document.removeEventListener('mousedown', this.handleClickOutside)
}
render() {
const options = this.props.options.map(option => (
<li className='OptionsList-Element'>
<div className='OptionsList-ElementIcon'>
<Icon name={option.icon} />
</div>
<span>{option.label}</span>
</li>
))
return (
<DropdownBox reference={this.dropdownBoxRef} styles={this.props.styles}>
<ul className='OptionsList'>{options}</ul>
</DropdownBox>
)
}
}
export default OptionsMenu
In constructor i'm creating ref, and then i want to pass it to DropdownBox component, that is rendered. In DropdownBox component i tried to use react hooks, but i think that i'ts wrong way. How to make it correctly?
Note, i dont want to switch my functional component to classbased!
Here is the code of DropdownBox component:
const dropdownBox = props => {
const dropdownBoxRef = useRef(props.reference)
return (
<div ref={dropdownBoxRef} className='DropdownBox-Container' style={props.styles}>
<div className='DropdownBox'>
<div className='DropdownBox-Triangle' />
{props.children}
</div>
</div>
)
}
You can use forwarding refs to get a ref to the underlying element outside the child component. For example:
const DropdownBox = React.forwardRef((props, ref) => (
<div ref={ref} className='DropdownBox-Container' style={props.styles}>
<div className='DropdownBox'>
<div className='DropdownBox-Triangle' />
{props.children}
</div>
</div>
));
Then in OptionsMenu:
return (
<DropdownBox ref={this.dropdownBoxRef} styles={this.props.styles}>
<ul className='OptionsList'>{options}</ul>
</DropdownBox>
)
Just assign the reference prop in your DropdownBox component to the ref prop of the div you want the reference of.
const dropdownBox = props => {
return (
<div ref={props.reference} className='DropdownBox-Container' style={props.styles}>
{ /* ... */ }
</div>
)
}
React will assign the component to the dropdownRef variable on its own.
You can give a the dropdownBox a function that will set the ref. To do this you can add in your menu component a function like the following:
const onDropdownRef = (ref) => {
this.dropdownBoxRef.current = ref;
}
Then you give this function to your dropdown component like so: <DropdownBox onRef={this.onDropdownRef} styles={this.props.styles}>
Then finally in your dropdown component you give this function to the div like so:
<div ref={props.onRef} className='DropdownBox-Container' style={props.styles}>
This will make sure your menu will have a ref to the top div from the dropdownbox.
I'm having an issue where react-loadable is causing one of my input components to re-render and lose focus after a state update. I've done some digging and I can't find anyone else having this issue, so I think that I'm missing something here.
I am attempting to use react-loadable to dynamically include components into my app based on a theme that the user has selected. This is working fine.
./components/App
import React from 'react';
import Loadable from 'react-loadable';
/**
* Import Containers
*/
import AdminBar from '../../containers/AdminBar';
import AdminPanel from '../../components/AdminPanel';
import 'bootstrap/dist/css/bootstrap.css';
import './styles.css';
const App = ({ isAdmin, inEditMode, theme }) => {
const MainContent = Loadable({
loader: () => import('../../themes/' + theme.name + '/components/MainContent'),
loading: () => (<div>Loading...</div>)
});
const Header = Loadable({
loader: () => import('../../themes/' + theme.name + '/components/Header'),
loading: () => (<div>Loading...</div>)
});
return (
<div>
{
(isAdmin) ? <AdminBar
className='admin-bar'
inEditMode={inEditMode} /> : ''
}
<Header
themeSettings={theme.settings.Header} />
<div className='container-fluid'>
<div className='row'>
{
(isAdmin && inEditMode) ? <AdminPanel
className='admin-panel'
theme={theme} /> : ''
}
<MainContent
inEditMode={inEditMode} />
</div>
</div>
</div>
);
};
export default App;
./components/AdminPanel
import React from 'react';
import Loadable from 'react-loadable';
import './styles.css';
const AdminPanel = ({ theme }) => {
const ThemedSideBar = Loadable({
loader: () => import('../../themes/' + theme.name + '/components/SideBar'),
loading: () => null
});
return (
<div className='col-sm-3 col-md-2 sidebar'>
<ThemedSideBar
settings={theme.settings} />
</div>
);
};
export default AdminPanel;
This is what my <ThemedSideBar /> components looks like:
./themes/Default/components/SideBar
import React from 'react';
import ThemeSettingPanel from '../../../../components/ThemeSettingPanel';
import ThemeSetting from '../../../../containers/ThemeSetting';
import './styles.css';
const SideBar = ({ settings }) => {
return (
<ThemeSettingPanel
name='Header'>
<ThemeSetting
name='Background Color'
setting={settings.Header}
type='text'
parent='Header' />
<ThemeSetting
name='Height'
setting={settings.Header}
type='text'
parent='Header' />
</ThemeSettingPanel>
);
};
export default SideBar;
./components/ThemeSettingPanel
import React from 'react';
import { PanelGroup, Panel } from 'react-bootstrap';
const ThemeSettingPanel = ({ name, children }) => {
return (
<PanelGroup accordion id='sidebar-accordion-panelGroup'>
<Panel>
<Panel.Heading>
<Panel.Title toggle>{name}</Panel.Title>
</Panel.Heading>
<Panel.Body collapsible>
{children}
</Panel.Body>
</Panel>
</PanelGroup>
);
};
export default ThemeSettingPanel;
./containers/ThemeSetting
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { themeSettingChange } from '../App/actions';
import ThemeSetting from '../../components/ThemeSetting';
class ThemeSettingContainer extends Component {
constructor(props) {
super(props);
this.handleOnChange = this.handleOnChange.bind(this);
}
handleOnChange(name, parent, value) {
const payload = {
name: name,
parent,
value: value
};
this.props.themeSettingChange(payload);
}
render() {
return (
<ThemeSetting
name={this.props.name}
setting={this.props.setting}
parent={this.props.parent}
type={this.props.type}
handleOnChange={this.handleOnChange} />
);
}
}
//----Redux Mappings----//
const mapStateToProps = (state) => ({
});
const mapDispatchToProps = {
themeSettingChange: (value) => themeSettingChange(value)
};
export default connect(mapStateToProps, mapDispatchToProps)(ThemeSettingContainer);
./component/ThemeSetting
import React from 'react';
import TextField from '../common/TextField';
import './styles.css';
const ThemeSetting = ({ name, setting, type, parent, handleOnChange }) => {
return (
<div className='row theme-setting'>
<div className='col-xs-7'>
{name}
</div>
<div className='col-xs-5'>
{
generateField(type, setting, name, parent, handleOnChange)
}
</div>
</div>
);
};
function generateField(type, setting, name, parent, handleOnChange) {
const value = setting ? setting[name] : '';
switch (type) {
case 'text':
return <TextField
value={value}
name={name}
parent={parent}
handleOnChange={handleOnChange} />;
default:
break;
}
}
export default ThemeSetting;
./components/common/TextField
import React from 'react';
import { FormControl } from 'react-bootstrap';
const TextField = ({ value, name, parent, handleOnChange }) => {
return (
<FormControl
type='text'
value={value}
onChange={(e) => {
handleOnChange(name, parent, e.target.value);
}} />
);
};
export default TextField;
When a field inside of my Admin Panel is updated, a state change is triggered. It seems like this triggers react-loadable to re-render my <ThemedSideBar /> components which destroys my input and creates a new one with the updated value. Has anyone else had this issue? Is there a way to stop react-loadable from re-rendering?
EDIT: Here is the requested link to the repo.
EDIT: As per conversation in the comments, my apologies, I misread the question. Answer here is updated (original answer below updated answer)
Updated answer
From looking at the react-loadable docs, it appears that the Loadable HOC is intended to be called outside of a render method. In your case, you are loading ThemedSideBar in the render method of AdminPanel. I suspect that the change in your TextEdit's input, passed to update your Redux state, and then passed back through the chain of components was causing React to consider re-rendering AdminPanel. Because your call to Loadable was inside the render method (i.e. AdminPanel is a presentational component), react-loadable was presenting a brand new loaded component every time React hit that code path. Thus, React thinks it needs to destroy the prior component to appropriately bring the components up to date with the new props.
This works:
import React from 'react';
import Loadable from 'react-loadable';
import './styles.css';
const ThemedSideBar = Loadable({
loader: () => import('../../themes/Default/components/SideBar'),
loading: () => null
});
const AdminPanel = ({ theme }) => {
return (
<div className='col-sm-3 col-md-2 sidebar'>
<ThemedSideBar
settings={theme.settings} />
</div>
);
};
export default AdminPanel;
Original answer
It seems that your problem is likely related to the way you've built TextField and not react-loadable.
The FormControl is taking value={value} and the onChange handler as props. This means you've indicated it is a controlled (as opposed to uncontrolled) component.
If you want the field to take on an updated value when the user types input, you need to propagate the change caught by your onChange handler and make sure it gets fed back to the value in the value={value} prop.
Right now, it looks like value will always be equal to theme.settings.Height or the like (which is presumably null/empty).
An alternative would be to make that FormControl an uncontrolled component, but I'm guessing you don't want to do that.
Child
import React, {Component} from 'react'
class Card extends Component {
render() {
let props = this.props;
return(
<div className="card-main">
<img src={`http://image.tmdb.org/t/p/w342/${props.path}`} alt="Poster" />
<div className="card-deatils">
<h4 className="card-name">{props.name}</h4>
<h4 className="id card-name">{props.id}</h4>
</div>
</div>
);
}
}
export default Card;
I want to get the id stored in with class "id". The problem is that from previous component the amount cards are at minimum 20 in the page and what I ideally want is to pass id back to its parent component. Most of the methods I tried give value undefined.
Parent
import React, {Component} from 'react'
import Card from './Card'
class CardArray extends Component {
render() {
var props = this.props
return (
<div className="pop-movie-container">
<div className="card">
{
props.popNames.map((val,index) => {
return(
<Card
key ={props.popId[index]}
id={props.popId[index]}
name={props.popNames[index]}
path={props.popPath[index]}
/>
);
})
}
</div>
<div className="load-more">
<button className="btn btn-12" onClick={props.change}>LOAD MORE</button>
</div>
</div>
);
}
}
export default CardArray;
popNames is an array containing 20 names at minimum and increases by 20 on click of load more.
So, ideally what I want is that to get id from Card passed to CardArray.
output
So, when someone clicks on card more information can be fetched from the api using id of movie
parent
import React, { Component } from "react";
import Card from "./Card";
class CardArray extends Component {
fetchDetails = (id) => {
// fetching logic goes here
}
render() {
var props = this.props;
return (
<div className="pop-movie-container">
<div className="card">
{props.popNames.map((val, index) => {
return (
<Card
key={props.popId[index]}
id={props.popId[index]}
name={props.popNames[index]}
path={props.popPath[index]}
handelClick={this.fetchDetails}
/>
);
})}
</div>
<div className="load-more">
<button className="btn btn-12" onClick={props.change}>
LOAD MORE
</button>
</div>
</div>
);
}
}
export default CardArray;
child
import React, { Component } from "react";
class Card extends Component {
render() {
let props = this.props;
return (
<div className="card-main" onClick={() => this.props.handelClick(this.props.id)}>
<img
src={`http://image.tmdb.org/t/p/w342/${props.path}`}
alt="Poster"
/>
<div className="card-deatils">
<h4 className="card-name">{props.name}</h4>
<h4 className="id card-name">{props.id}</h4>
</div>
</div>
);
}
}
export default Card;
u can pass an function as props to child and child can call that function with id.
if you need more explanation let me know.
also once quick suggestion you can directly pass
name={val} instead of name={props.popNames[index]}